Pull to refresh
669.35
OTUS
Цифровые навыки от ведущих экспертов

Как создать волшебника в Django: многошаговые формы

Level of difficultyEasy
Reading time7 min
Views5.4K

Привет, Хабр!

Сегодня мы поговорим о магии, но не той, что преподают в Хогвартсе. Речь пойдет о создании волшебника, точнее многошагового мастера форм в Django, который позволяет пользователю шаг за шагом продвигаться к желаемому результату. Эта статья расскажет, как использовать django-formtools для реализации волшебства на вашем сайте.

А причем тут магия? Узнаете чуть позже.

Немного про многошаговые формы

Многошаговые формы в пользовательских интерфейсах предлагают ряд фич, которые могут улучшить общий пользовательский опыт.

Многошаговые формы разбивают длинные или сложные процессы на более мелкие, управляемые шаги. Это делает форму менее устрашающей и упрощает её заполнение. Пользователи точно видят, где они находятся в процессе и сколько еще шагов осталось.

Использование условной логики позволяет форме адаптироваться к предыдущим ответам пользователя, показывая только релевантные вопросы или поля.

На мобилках прокрутка длинной одностраничной формы может быть неудобной. Многошаговые формы предлагают отдельные секции по мере продвижения.

Установим и настроим

Для начала необходимо установить пакет django-formtools:

pip install django-formtools

Далее добавляемformtools в список установленных приложений в проекте Django:

INSTALLED_APPS = [
    ...
    'formtools',
    ...
]

Так можно корректно интегрировать django-formtools с проектом и использовать его функциональность.

SessionWizardView и CookieWizardView

Да, именно из-за слова "Wizard" ведение было таким, какое оно есть.

django-formtools имеет два основных класса для создания многошаговых форм: SessionWizardView и CookieWizardView. Выбор между этими классами зависит от того, как хочется управлять данными формы на протяжении разных шагов.

  • SessionWizardView хранит данные формы на стороне сервера в сессии пользователя. Таким образом имеем высокий уровень безопасности, так как данные не передаются обратно клиенту, но требует, чтобы сессии были активно управляемы на сервере.

  • CookieWizardView сохраняет данные формы непосредственно в куках пользователя. Метод удобен для масштабируемых приложений, где серверы не должны поддерживать состояние, но может быть менее безопасен, т.к данные хранятся на стороне клиента.

Выбор между SessionWizardView и CookieWizardView должен основываться на требованиях проекта к безопасности и масштабируемости.

Для старта работ с SessionWizardView, необходимо создать несколько форм, каждая из которых будет соответствовать шагу в мастере форм. Затем эти формы интегрируются в класс, наследующий от SessionWizardView. В этом классе можно определить метод done, который будет вызываться после успешного заполнения всех шагов формы. В этом методе обычно происходит обработка данных, например, их сохранение в БД или выполнение других действий.

Пример многошаговой формы с SessionWizardView:

from django import forms
from django.http import HttpResponseRedirect
from formtools.wizard.views import SessionWizardView

class ContactForm1(forms.Form):
    name = forms.CharField(max_length=100)

class ContactForm2(forms.Form):
    age = forms.IntegerField()

class ContactWizard(SessionWizardView):
    template_name = "contact_form.html"
    form_list = [ContactForm1, ContactForm2]

    def done(self, form_list, **kwargs):
        form_data = [form.cleaned_data for form in form_list]
        # обработка данных
        return HttpResponseRedirect('/finished/')

Конфигурация CookieWizardView аналогична SessionWizardView, но данные форм сохраняются в куках, а не в серверной сессии.

Пример с CookieWizardView:

from django import forms
from formtools.wizard.views import CookieWizardView

class FeedbackForm1(forms.Form):
    feedback = forms.CharField(widget=forms.Textarea)

class FeedbackForm2(forms.Form):
    email = forms.EmailField()

class FeedbackWizard(CookieWizardView):
    template_name = "feedback_form.html"
    form_list = [FeedbackForm1, FeedbackForm2]

    def done(self, form_list, **kwargs):
        form_data = [form.cleaned_data for form in form_list]
        # обработка отзывов
        return HttpResponseRedirect('/feedback/thanks/')

Создание шаблона

Создание шаблонов для форм в многошаговых визардах Django предполагает настройку HTML-шаблона, который будет отрисовывать формы на каждом шаге процесса. По дефолту все формы используют шаблон formtools/wizard/wizard_form.html. Однако, можно изменить это поведение, переопределив атрибут template_name или метод get_template_names(). Последний позволяет использовать разные шаблоны для каждой формы.

В этих шаблонах используется объект wizard, который содержит ряд атрибутов:

  • wizard.form — экземпляр текущей формы или набора форм на данном шаге, который может быть как пустым, так и содержать ошибки.

  • wizard.steps — вспомогательный объект, который предоставляет доступ к различной информации о шагах:

    • wizard.steps.step0 — текущий шаг (индексация с нуля).

    • wizard.steps.step1 — текущий шаг (индексация с единицы).

    • wizard.steps.count — общее количество шагов.

    • wizard.steps.first — первый шаг.

    • wizard.steps.last — последний шаг.

    • wizard.steps.current — текущий или первый шаг.

    • wizard.steps.next — следующий шаг.

    • wizard.steps.prev — предыдущий шаг.

    • wizard.steps.index — индекс текущего шага.

    • wizard.steps.all — список всех шагов визарда.

Можно добавилять дополнительные переменные контекста, используя get_context_data() подкласса WizardView.

Пример полного шаблона для многошагового визарда:

{% extends "base.html" %}
{% load i18n %}

{% block head %}
{{ wizard.form.media }}
{% endblock %}

{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
    {{ wizard.form.management_form }}
    {% for form in wizard.form.forms %}
        {{ form.as_table }}
    {% endfor %}
{% else %}
    {{ wizard.form.as_table }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% translate "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% translate "prev step" %}</button>
{% endif %}
<input type="submit" value="{% translate "submit" %}"/>
</form>
{% endblock %}

Важно юзать {{ wizard.management_form }} в шаблоне, чтобы визард мог корректно управлять процессом перехода между шагами.

Настройка URL

Можно интегрировать многошаговый визард в конфигурацию URL Django.

Для начала, необходимо определить, какие формы будут использоваться в визарде. Затем следует создать новый объект WizardView, который будет размещен по определенному URL в файле urls.py. Метод as_view() объекта визарда принимает список классов форм как аргумент при создании:

from django.urls import path
from myapp.forms import ContactForm1, ContactForm2
from myapp.views import ContactWizard

urlpatterns = [
    path('contact/', ContactWizard.as_view([ContactForm1, ContactForm2])),
]

Также можно передать список форм как атрибут класса form_list:

class ContactWizard(WizardView):
    form_list = [ContactForm1, ContactForm2]

Допустим, требуется реализовать многошаговый процесс оформления заказа в интернет-магазине. На первом шаге пользователь указывает адрес доставки и оплаты. На втором шаге выбирается способ оплаты. Если выбрана оплата кредитной картой, на следующем шаге необходимо ввести данные кредитной карты. На последнем шаге происходит подтверждение покупки.

Пример кода визарда:

from django.http import HttpResponseRedirect
from formtools.wizard.views import SessionWizardView

FORMS = [("address", myapp.forms.AddressForm),
         ("paytype", myapp.forms.PaymentChoiceForm),
         ("cc", myapp.forms.CreditCardForm),
         ("confirmation", myapp.forms.OrderForm)]

TEMPLATES = {"address": "checkout/billingaddress.html",
             "paytype": "checkout/paymentmethod.html",
             "cc": "checkout/creditcard.html",
             "confirmation": "checkout/confirmation.html"}

class OrderWizard(SessionWizardView):
    def get_template_names(self):
        return [TEMPLATES[self.steps.current]]

    def done(self, form_list, **kwargs):
        do_something_with_the_form_data(form_list)
        return HttpResponseRedirect('/page-to-redirect-to-when-done/')

В файле urls.py это будет выглядеть так:

urlpatterns = [
    path('checkout/', OrderWizard.as_view(FORMS, condition_dict={'cc': pay_by_credit_card})),
]

condition_dict может быть передан как атрибут метода as_view() или как атрибут класса condition_dict:

class OrderWizard(WizardView):
    condition_dict = {'cc': pay_by_credit_card}

Метод get_template_names() возвращает список, содержащий один шаблон, который выбирается на основе имени текущего шага.

Прочие методы WizardView

Помимо метода done(), который вызывается по завершении всех шагов, есть ряд других методов, позволяющих настроить поведение визарда на различных этапах.

Этот метод возвращает префикс, который будет использоваться при вызове формы для данного шага. Это позволяет избежать конфликтов между формами на разных шагах. Префикс по умолчанию основан на имени шага, а параметр формы не используется:

get_form_prefix(step=None, form=None)

Следующий метод возвращает ловарь, который будет передан как аргумент initial при создании экземпляра формы для данного шага. Если начальные данные не были предоставлены при инициализации визарда, должен возвращаться пустой словарь:

get_form_initial(step)

get_form_kwargs(step) возвращает словарь, который будет использоваться как ключевые аргументы при создании экземпляра формы на данном шаге.

get_form_instance(step) вызывается, если для шага используется ModelForm. Возвращает экземпляр модели, который будет передан как аргумент instance при создании ModelForm для данного шага.

Следующий метод возвращает контекст шаблона для шага. Можно переопределить этот метод, чтобы добавить доп. данные для всех или некоторых шагов. Метод возвращает словарь, содержащий отрисованный шаг формы:

def get_context_data(self, form, **kwargs):
    context = super().get_context_data(form=form, **kwargs)
    if self.steps.current == 'my_step_name':
        context.update({'another_var': True})
    return context

get_prefix(request, *args, **kwargs) возвращает префикс для использования хранилищем данных. Хранилища используют префикс как механизм для разделения данных для каждого визарда. Пример:

from django.http import HttpResponseRedirect
from formtools.wizard.views import SessionWizardView

FORMS = [("address", myapp.forms.AddressForm),
         ("payment", myapp.forms.PaymentForm)]

class CheckoutWizard(SessionWizardView):
    def done(self, form_list, **kwargs):
        process_order(form_list)
        return HttpResponseRedirect('/order/complete/')

urlpatterns = [
    path('checkout/', CheckoutWizard.as_view(FORMS)),
]

Здесь CheckoutWizard обрабатывает два шага: ввод адреса и оплату. Метод done обрабатывает данные после завершения всех шагов и перенаправляет пользователя на страницу завершения заказа.


Итак, с помощью многошаговых форм можно тонко настраивать поведение визардов, адаптируя их под конкретные требования и контекст использования. Мы в очередной раз убедились, насколько хорош Django.

В заключение напоминаю про открытый урок 3 июня, на котором можно познакомитсья с библиотекой FastAPI-JSON:API. Записывайтесь на странице курса «Django-разработчик».

Tags:
Hubs:
Total votes 7: ↑7 and ↓0+9
Comments1

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS