Django поставляется с приложением «мастер форм», которое распределяет формы по нескольким страницам сайта. Приложение хранит своё состояние в одном из бэкэндов, таким образом обработка введённой информации может быть отложена до заполнения последней формы.
Вам может потребоваться это приложение для отображения длинной формы, которая является слишком громоздкой для отображения на одной странице. Первая страница может запрашивать основную информацию, а вторая страница может спрашивать дополнительную информацию и так далее.
Термин «мастер» в этом контексте описан на Википедии.
Рассмотрим основные шаги использования мастера пользователем:
Пользователь открывает первую страницу мастера, заполняет форму и отправляет её.
Сервер проверяет данные. Если они содержат ошибку, то форма отображается заново, показывая ошибки. Если данные прошли проверку, сервер сохраняет текущее состояние мастера в бэкэнде и перенаправляет пользователя на следующую страницу.
Оба шага повторяются для каждой формы мастера.
После того, как пользователь заполнит все формы и все данные пройдут проверку, мастер обрабатывает данные, сохраняя их в базе данных, отправляя почту или производит другие требуемые действия.
Это приложение максимально автоматизирует данный процесс. В общем случае, вам надо сделать следующее:
Определить ряд классов Form, по одному на каждую страницу мастера.
Создать наследника класса WizardView, который будет определять , что надо сделать когда все формы будут заполнены и проверены. Класс также позволяет изменить поведение мастера.
Создать несколько шаблонов, которые будут отображать формы. Вы можете определить либо один общий шаблон для обработки каждой формы, либо создать собственный шаблон для каждой формы.
Добавить django.contrib.formtools в параметр конфигурации INSTALLED_APPS.
Зарегистрировать URL, который будет вызывать метод as_view() вашего класса WizardView.
Первым шагом создания мастера форм будет определение классов Form. Это обычные классы django.forms.Form, описанные в соответствующем разделе. Код этих классов может располагаться в любом месте вашего проекта, но по принятым соглашениям лучше его располагать в файле forms.py вашего приложения.
Давайте для примера напишем мастер для «контактной формы», где на первой странице будем запрашивать адрес электронной почты и тему, а на второй — само тело сообщения. Вот как файл forms.py может выглядеть:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Примечание
Для того, чтобы использовать класс FileField на любой форме, обратитесь к расположенному далее разделу :ref:` Обработка файлов <wizard-files>`.
Следующим шагом будет создание класса django.contrib.formtools.wizard.views.WizardView. Вы можете также использовать классы SessionWizardView или CookieWizardView, которые используют соответствующий бэкэнд для хранения информации во время работы мастера (как видно по их именам, для хранения информации в сессии на сервере или в куках браузера).
Примечание
Для использования класса SessionWizardView следуйте инструкциям документации по сессиям для активации поддержки сессий.
Мы будем использовать класс SessionWizardView во всех примерах, но легко могли бы использовать и класс CookieWizardView. Как и в случае с классами Form, код класса WizardView может располагаться в любом месте проекта, но по соглашению его надо размещать в файле views.py.
Единственное требование к вашему классу — он должен реализовывать метод done().
Этот метод определяет, что должно происходить при отправке и проверке данных каждой формы. Этот метод принимает список проверенных экземпляров класса Form.
Это упрощённый пример. Вместо выполнения всех операций с базой данных, метод просто заполняет шаблон проверенными данными:
from django.shortcuts import render_to_response
from django.contrib.formtools.wizard.views import SessionWizardView
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Следует отметить, что этот метод будет выполняться через POST, поэтому по правилам хорошего тона после обработки следует перенаправлять пользователя на другую страницу. Вот другой пример:
from django.http import HttpResponseRedirect
from django.contrib.formtools.wizard.views import SessionWizardView
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
Обратитесь к разделу Дополнительные методы WizardView для получения информации по обработчикам класса WizardView.
Далее, необходимо создать шаблон для отображения форм мастера. По умолчанию каждая форма использует шаблон formtools/wizard/wizard_form.html. Вы можете изменить имя этого шаблона, переопределив либо атрибут template_name, либо метод get_template_names(), которые описаны в документации на класс TemplateResponseMixin. Этот класс позволяет использовать отдельные шаблоны для каждой формы.
Шаблон ожидает объект wizard, к которому привязаны различные элементы:
form – это экземпляр классов Form или BaseFormSet для текущего шага мастера (либо пустой, либо с ошибками).
steps – вспомогательный объект для доступа к данным других шагов мастера:
step0 – номер текущего шага (начинается с нуля).
step1 – номер текущего шага (начинается с единицы).
count – общее количество шагов.
first – признак первого шага.
last – признак последнего шага.
current – текущий (или первый) шаг.
next – следующий шаг.
prev – предыдущий шаг.
index – индекс текущего шага.
all – список всех шагов мастера.
Вы можете добавлять в контекст дополнительные переменные с помощью метода get_context_data() вашего потомка класса WizardView.
Вот полный пример шаблона:
{% extends "base.html" %}
{% 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 }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}
Примечание
Следует отметить, что таг {{ wizard.management_form }} должен обязательно присутствовать в шаблоне, иначе мастер будет работать неправильно.
Наконец, нам надо указать какие формы надо использовать в мастере и затем подключить новый объект WizardView к URL в файле urls.py. Метод мастера as_view() принимает список экземпляров класса Form в качестве аргумента:
from django.conf.urls import patterns
from myapp.forms import ContactForm1, ContactForm2
from myapp.views import ContactWizard
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
)
Кроме метода done(), класс WizardView предоставляет несколько дополнительных методов (обработчиков), которые позволяют настроить поведение мастера.
Некоторые из этих методов принимают аргумент step, который является начинающимся с нуля строковым значением текущего шага мастера. (Т.е., первая форма — '0', а вторая — '1'.)
Получая шаг, возвращает префикс формы. По умолчанию просто использует сам шаг. Подробности смотрите в документации на префиксы форм.
Возвращает словарь, который будет передан в качестве аргумента initial при создании экземпляра формы для шага step. Если во время создания мастера начальные данные не были предоставлены, то возвращается пустой словарь.
Стандартная реализация:
def get_form_initial(self, step):
return self.initial_dict.get(step, {})
Возвращает словарь, который будет использоваться в качестве именованных аргументов при создании экземпляра формы для шага step.
Стандартная реализация:
def get_form_kwargs(self, step):
return {}
Этот метод применяется только при использовании класса ModelForm для создания формы на шаге step.
Возвращает объект Model, который передаётся в качестве аргумента instance при создании модельной формы на шаге step. Если при создании мастера не был передан экземпляр модели, то возвращается None.
Стандартная реализация:
def get_form_instance(self, step):
return self.instance_dict.get(step, None)
Возвращает шаблонный контекст для шага. Вы можете переопределить этот метод для добавления дополнительных данных на всех или на определённых шагах мастера. Этот метод возвращает словарь контекста для текущего шага.
Стандартные контекстные переменные шаблона:
Бэкэнд хранения сохраняет все дополнительные данные
form – экземпляр формы текущего шага.
wizard – экземпляр самого мастера.
Пример добавления дополнительных переменных для указанного шага:
def get_context_data(self, form, **kwargs):
context = super(MyWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'my_step_name':
context.update({'another_var': True})
return context
Этот метод возвращает префикс, который использует хранилище. Бэкэнд использует префикс для хранения данных нескольких мастеров в одном хранилище. Это позволяет мастерам сохранять свои данные, не мешая друг другу.
Вы можете изменить этот метод, чтобы сделать префикс данных мастера более уникальным, т.е., чтобы позволить работать нескольким экземплярам одного мастера в рамках одной сессии.
Стандартная реализация:
def get_prefix(self, *args, **kwargs):
# use the lowercase underscore version of the class name
return normalize_name(self.__class__.__name__)
Этот метод создает форму для указанного шага step. Если шаг не указан, то текущий шаг определяется автоматически. Метод принимает три аргумента:
step – шаг, для которого должен быть создан экземпляр формы.
data – передаётся в аргумент данных формы.
files – передаётся в аргумент файлов формы
Вы можете переопределить этот метод для передачи дополнительных аргументов в экземпляр формы.
Пример кода, который добавляет атрибут пользователя к форме на вопрос шаге.
def get_form(self, step=None, data=None, files=None):
form = super(MyWizard, self).get_form(step, data, files)
if step == '1':
form.user = self.request.user
return form
Обработчик для изменения внутреннего состояния мастера, получающий полностью проверенный объект Form. Форма гарантированно получает нормализованные и проверенные данные.
Этот метод позволяет организовать обработку данных формы перед их помещением в хранилище. По умолчанию метод просто возвращает словарь form.data. Вы не должны изменять полученные данные, но можете дополнять их.
Следует отметить, что этот метод вызывается при каждом отображении страницы для всех выполненных шагов.
Стандартная реализация:
def process_step(self, form):
return self.get_form_step_data(form)
Этот метод предоставляет возможность организовать обработку файлов формы перед их размещением в хранилище. По умолчанию метод просто возвращает словарь form.files. Вы не должны изменять полученные данные, но можете дополнять их.
Стандартная реализация:
def process_step_files(self, form):
return self.get_form_step_files(form)
Как только мастер пройдёт все шаги, он заново проверяет все формы, используя данные из хранилища.
Если хоть одна из форм не пройдёт проверку, то будет вызван этот метод. Метод принимает два аргумента, step и form.
Текущая реализация сбрасывает текущий шаг на первую из форм, которая не прошла проверку, и перенаправляет пользователя на неё.
Стандартная реализация:
def render_revalidation_failure(self, step, form, **kwargs):
self.storage.current_step = step
return self.render(form, **kwargs)
Этот метод получает данные из экземпляра формы form и возвращает словарь. Вы можете использовать этот метод для управления значениями перед сохранением данных в хранилище.
Стандартная реализация:
def get_form_step_data(self, form):
return form.data
Этот метод возвращает файлы формы. Вы можете использовать этот метод для управления файлами перед их размещением в хранилище.
Стандартная реализация:
def get_form_step_files(self, form):
return form.files
Этот метод вызывается после обработки POST или GET запроса. Вы можете внедриться в этот метод, например, для изменения типа HTTP запроса.
Стандартная реализация:
def render(self, form=None, **kwargs):
form = form or self.get_form()
context = self.get_context_data(form=form, **kwargs)
return self.render_to_response(context)
Начальные данные для экземпляров форм Form мастера могут быть предоставлены с помощью необязательного именованного аргумента initial_dict. Аргумент должен представлять собой словарь, который связывает шаги со словарями начальных данных. Словарь начальных данных будет передан конструктору формы Form соответствующего шага:
>>> from myapp.forms import ContactForm1, ContactForm2
>>> from myapp.views import ContactWizard
>>> initial = {
... '0': {'subject': 'Hello', 'sender': 'user@example.com'},
... '1': {'message': 'Hi there!'}
... }
>>> wiz = ContactWizard.as_view([ContactForm1, ContactForm2], initial_dict=initial)
>>> form1 = wiz.get_form('0')
>>> form2 = wiz.get_form('1')
>>> form1.initial
{'sender': 'user@example.com', 'subject': 'Hello'}
>>> form2.initial
{'message': 'Hi there!'}
Аргумент initial_dict также может принимать список словарей для определённого шага, если на этом шаге используется набор форм.
Для того, чтобы обработать поле FileField на любой форме мастера, вам потребуется добавить атрибут file_storage в вашу реализацию класса WizardView.
Это хранилище будет временно хранить загруженные файлы мастера. Атрибут file_storage должен быть потомком класса Storage.
Предупреждение
Не забывайте удалять старые файлы, так как WizardView не удаляет файлы , независимо от того, удачно ли отработал мастер или нет.
Метод as_view() принимает аргумент condition_dict. Вы можете передать словарь булевых значений или вызываемых методов. Ключ должен соответствовать имени шага (т.е. ‘0’, ‘1’).
Если значение определенного шага является функцией, она будет вызвана с экземпляром WizardView в качестве аргумента.Если эта функция вернёт true, то будет использована форма шага.
Этот пример показывает контактную форму со встроенным условием. Условие используется для отображения на форме сообщения в случае, если чекбокс на форме первого шага отмечен.
Шаги определяются в файле forms.py:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
leave_message = forms.BooleanField(required=False)
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Мы определяем наш мастер в файле views.py:
from django.shortcuts import render_to_response
from django.contrib.formtools.wizard.views import SessionWizardView
def show_message_form_condition(wizard):
# try to get the cleaned data of step 1
cleaned_data = wizard.get_cleaned_data_for_step('0') or {}
# check if the field ``leave_message`` was checked.
return cleaned_data.get('leave_message', True)
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Теперь надо добавить ContactWizard в файл urls.py:
from django.conf.urls import pattern
from myapp.forms import ContactForm1, ContactForm2
from myapp.views import ContactWizard, show_message_form_condition
contact_forms = [ContactForm1, ContactForm2]
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view(contact_forms,
condition_dict={'1': show_message_form_condition}
)),
)
Как вы можете видеть, мы определили show_message_form_condition сразу за нашей реализацией класса WizardView и добавили аргумент condition_dict в метод as_view(). Ключ ссылается на второй шаг мастера, т.к индекс шагов начинается с нуля.
Мастер может использовать ModelForms и ModelFormSets. В заполнение к атрибуту initial_dict, метод as_view() принимает аргумент instance_dict, который должен содержать экземпляры ModelForm и ModelFormSet. Аналогично атрибуту initial_dict, ключи этих словарей должны соответствовать номеру шага в списке форм.
Существует наследник класса WizardView, который добавляет к мастеру поддержку именованных URL. Используя этот класс, вы можете работать с мастером через один URL.
Для использования именованных URL, потребуется внести изменения в файл urls.py.
Ниже представлен пример мастера контактов с двумя шагами, первый шаг называется «contactdata», а второй — «leavemessage».
Дополнительно придётся передать два аргумента в метод as_view():
url_name – название URL (как указано в urls.py).
done_step_name – имя в URL для пройденного шага.
Пример измененного файла urls.py:
from django.conf.urls import url, patterns
from myapp.forms import ContactForm1, ContactForm2
from myapp.views import ContactWizard
named_contact_forms = (
('contactdata', ContactForm1),
('leavemessage', ContactForm2),
)
contact_wizard = ContactWizard.as_view(named_contact_forms,
url_name='contact_step', done_step_name='finished')
urlpatterns = patterns('',
url(r'^contact/(?P<step>.+)/$', contact_wizard, name='contact_step'),
url(r'^contact/$', contact_wizard, name='contact'),
)
Mar 30, 2016