mirror of https://github.com/django/django.git
582 lines
21 KiB
Plaintext
582 lines
21 KiB
Plaintext
===========
|
|
Form wizard
|
|
===========
|
|
|
|
.. module:: django.contrib.formtools.wizard.views
|
|
:synopsis: Splits forms across multiple Web pages.
|
|
|
|
Django comes with an optional "form wizard" application that splits
|
|
:doc:`forms </topics/forms/index>` across multiple Web pages. It maintains
|
|
state in one of the backends so that the full server-side processing can be
|
|
delayed until the submission of the final form.
|
|
|
|
You might want to use this if you have a lengthy form that would be too
|
|
unwieldy for display on a single page. The first page might ask the user for
|
|
core information, the second page might ask for less important information,
|
|
etc.
|
|
|
|
The term "wizard", in this context, is `explained on Wikipedia`_.
|
|
|
|
.. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29
|
|
|
|
How it works
|
|
============
|
|
|
|
Here's the basic workflow for how a user would use a wizard:
|
|
|
|
1. The user visits the first page of the wizard, fills in the form and
|
|
submits it.
|
|
2. The server validates the data. If it's invalid, the form is displayed
|
|
again, with error messages. If it's valid, the server saves the current
|
|
state of the wizard in the backend and redirects to the next step.
|
|
3. Step 1 and 2 repeat, for every subsequent form in the wizard.
|
|
4. Once the user has submitted all the forms and all the data has been
|
|
validated, the wizard processes the data -- saving it to the database,
|
|
sending an email, or whatever the application needs to do.
|
|
|
|
Usage
|
|
=====
|
|
|
|
This application handles as much machinery for you as possible. Generally,
|
|
you just have to do these things:
|
|
|
|
1. Define a number of :class:`~django.forms.Form` classes -- one per
|
|
wizard page.
|
|
|
|
2. Create a :class:`WizardView` subclass that specifies what to do once
|
|
all of your forms have been submitted and validated. This also lets
|
|
you override some of the wizard's behavior.
|
|
|
|
3. Create some templates that render the forms. You can define a single,
|
|
generic template to handle every one of the forms, or you can define a
|
|
specific template for each form.
|
|
|
|
4. Add ``django.contrib.formtools.wizard`` to your
|
|
:setting:`INSTALLED_APPS` list in your settings file.
|
|
|
|
5. Point your URLconf at your :class:`WizardView` :meth:`~WizardView.as_view` method.
|
|
|
|
Defining ``Form`` classes
|
|
-------------------------
|
|
|
|
The first step in creating a form wizard is to create the
|
|
:class:`~django.forms.Form` classes. These should be standard
|
|
:class:`django.forms.Form` classes, covered in the :doc:`forms documentation
|
|
</topics/forms/index>`. These classes can live anywhere in your codebase,
|
|
but convention is to put them in a file called :file:`forms.py` in your
|
|
application.
|
|
|
|
For example, let's write a "contact form" wizard, where the first page's form
|
|
collects the sender's email address and subject, and the second page collects
|
|
the message itself. Here's what the :file:`forms.py` might look like::
|
|
|
|
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)
|
|
|
|
|
|
.. note::
|
|
|
|
In order to use :class:`~django.forms.FileField` in any form, see the
|
|
section :ref:`Handling files <wizard-files>` below to learn more about
|
|
what to do.
|
|
|
|
Creating a ``WizardView`` class
|
|
-------------------------------
|
|
|
|
The next step is to create a
|
|
:class:`django.contrib.formtools.wizard.view.WizardView` subclass. You can
|
|
also use the :class:`SessionWizardView` or :class:`CookieWizardView` class
|
|
which preselects the wizard storage backend.
|
|
|
|
.. note::
|
|
|
|
To use the :class:`SessionWizardView` follow the instructions
|
|
in the :doc:`sessions documentation </topics/http/sessions>` on
|
|
how to enable sessions.
|
|
|
|
We will use the :class:`SessionWizardView` in all examples but is is completly
|
|
fine to use the :class:`CookieWizardView` instead. As with your
|
|
:class:`~django.forms.Form` classes, this :class:`WizardView` class can live
|
|
anywhere in your codebase, but convention is to put it in :file:`views.py`.
|
|
|
|
The only requirement on this subclass is that it implement a
|
|
:meth:`~WizardView.done()` method.
|
|
|
|
.. method:: WizardView.done(form_list)
|
|
|
|
This method specifies what should happen when the data for *every* form is
|
|
submitted and validated. This method is passed a list of validated
|
|
:class:`~django.forms.Form` instances.
|
|
|
|
In this simplistic example, rather than performing any database operation,
|
|
the method simply renders a template of the validated data::
|
|
|
|
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],
|
|
})
|
|
|
|
Note that this method will be called via ``POST``, so it really ought to be a
|
|
good Web citizen and redirect after processing the data. Here's another
|
|
example::
|
|
|
|
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/')
|
|
|
|
See the section :ref:`Advanced WizardView methods <wizardview-advanced-methods>`
|
|
below to learn about more :class:`WizardView` hooks.
|
|
|
|
Creating templates for the forms
|
|
--------------------------------
|
|
|
|
Next, you'll need to create a template that renders the wizard's forms. By
|
|
default, every form uses a template called
|
|
:file:`formtools/wizard/wizard_form.html`. You can change this template name
|
|
by overriding either the :attr:`~WizardView.template_name` attribute or the
|
|
:meth:`~WizardView.get_template_names()` method, which is documented below.
|
|
This hook also allows you to use a different template for each form.
|
|
|
|
This template expects a ``wizard`` object that has various items attached to
|
|
it:
|
|
|
|
* ``form`` -- The :class:`~django.forms.Form` instance for the current
|
|
step (either empty or with errors).
|
|
|
|
* ``steps`` -- A helper object to access the various steps related data:
|
|
|
|
* ``step0`` -- The current step (zero-based).
|
|
* ``step1`` -- The current step (one-based).
|
|
* ``count`` -- The total number of steps.
|
|
* ``first`` -- The first step.
|
|
* ``last`` -- The last step.
|
|
* ``current`` -- The current (or first) step.
|
|
* ``next`` -- The next step.
|
|
* ``prev`` -- The previous step.
|
|
* ``index`` -- The index of the current step.
|
|
* ``all`` -- A list of all steps of the wizard.
|
|
|
|
You can supply additional context variables by using the
|
|
:meth:`~FormWizard.get_context_data` method of your :class:`FormWizard`
|
|
subclass.
|
|
|
|
Here's a full example template:
|
|
|
|
.. code-block:: html+django
|
|
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<p>Step {{ wizard.steps.current }} 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 %}
|
|
{% if wizard.steps.prev %}
|
|
<button name="wizard_prev_step" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
|
|
<button name="wizard_prev_step" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
|
|
{% endif %}
|
|
</table>
|
|
<input type="submit">
|
|
</form>
|
|
{% endblock %}
|
|
|
|
.. note::
|
|
|
|
Note that ``{{ wizard.management_form }}`` **must be used** for
|
|
the wizard to work properly.
|
|
|
|
.. _wizard-urlconf:
|
|
|
|
Hooking the wizard into a URLconf
|
|
---------------------------------
|
|
|
|
Finally, we need to specify which forms to use in the wizard, and then
|
|
deploy the new :class:`WizardView` object a URL in the ``urls.py``. The
|
|
wizard's :meth:`as_view` method takes a list of your
|
|
:class:`~django.forms.Form` classes as an argument during instantiation::
|
|
|
|
from django.conf.urls.defaults import patterns
|
|
|
|
from myapp.forms import ContactForm1, ContactForm2
|
|
from myapp.views import ContactWizard
|
|
|
|
urlpatterns = patterns('',
|
|
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
|
|
)
|
|
|
|
.. _wizardview-advanced-methods:
|
|
|
|
Advanced ``WizardView`` methods
|
|
===============================
|
|
|
|
.. class:: WizardView
|
|
|
|
Aside from the :meth:`~done()` method, :class:`WizardView` offers a few
|
|
advanced method hooks that let you customize how your wizard works.
|
|
|
|
Some of these methods take an argument ``step``, which is a zero-based
|
|
counter as string representing the current step of the wizard. (E.g., the
|
|
first form is ``'0'`` and the second form is ``'1'``)
|
|
|
|
.. method:: WizardView.get_form_prefix(step)
|
|
|
|
Given the step, returns a form prefix to use. By default, this simply uses
|
|
the step itself. For more, see the :ref:`form prefix documentation
|
|
<form-prefix>`.
|
|
|
|
.. method:: WizardView.process_step(form)
|
|
|
|
Hook for modifying the wizard's internal state, given a fully validated
|
|
:class:`~django.forms.Form` object. The Form is guaranteed to have clean,
|
|
valid data.
|
|
|
|
Note that this method is called every time a page is rendered for *all*
|
|
submitted steps.
|
|
|
|
The default implementation::
|
|
|
|
def process_step(self, form):
|
|
return self.get_form_step_data(form)
|
|
|
|
.. method:: WizardView.get_form_initial(step)
|
|
|
|
Returns a dictionary which will be passed to the form for ``step`` as
|
|
``initial``. If no initial data was provied while initializing the
|
|
form wizard, a empty dictionary should be returned.
|
|
|
|
The default implementation::
|
|
|
|
def get_form_initial(self, step):
|
|
return self.initial_dict.get(step, {})
|
|
|
|
.. method:: WizardView.get_form_instance(step)
|
|
|
|
Returns a object which will be passed to the form for ``step`` as
|
|
``instance``. If no instance object was provied while initializing
|
|
the form wizard, None be returned.
|
|
|
|
The default implementation::
|
|
|
|
def get_form_instance(self, step):
|
|
return self.instance_dict.get(step, None)
|
|
|
|
.. method:: WizardView.get_context_data(form, **kwargs)
|
|
|
|
Returns the template context for a step. You can overwrite this method
|
|
to add more data for all or some steps. This method returns a dictionary
|
|
containing the rendered form step.
|
|
|
|
The default template context variables are:
|
|
|
|
* Any extra data the storage backend has stored
|
|
* ``form`` -- form instance of the current step
|
|
* ``wizard`` -- the wizard instance itself
|
|
|
|
Example to add extra variables for a specific step::
|
|
|
|
def get_context_data(self, form, **kwargs):
|
|
context = super(MyWizard, self).get_context_data(form, **kwargs)
|
|
if self.steps.current == 'my_step_name':
|
|
context.update({'another_var': True})
|
|
return context
|
|
|
|
.. method:: WizardView.get_wizard_name()
|
|
|
|
This method can be used to change the wizard's internal name.
|
|
|
|
Default implementation::
|
|
|
|
def get_wizard_name(self):
|
|
return normalize_name(self.__class__.__name__)
|
|
|
|
.. method:: WizardView.get_prefix()
|
|
|
|
This method returns a prefix for the storage backends. These backends use
|
|
the prefix to fetch the correct data for the wizard. (Multiple wizards
|
|
could save their data in one session)
|
|
|
|
You can change this method to make the wizard data prefix more unique to,
|
|
e.g. have multiple instances of one wizard in one session.
|
|
|
|
Default implementation::
|
|
|
|
def get_prefix(self):
|
|
return self.wizard_name
|
|
|
|
.. method:: WizardView.get_form(step=None, data=None, files=None)
|
|
|
|
This method constructs the form for a given ``step``. If no ``step`` is
|
|
defined, the current step will be determined automatically.
|
|
The method gets three arguments:
|
|
|
|
* ``step`` -- The step for which the form instance should be generated.
|
|
* ``data`` -- Gets passed to the form's data argument
|
|
* ``files`` -- Gets passed to the form's files argument
|
|
|
|
You can override this method to add extra arguments to the form instance.
|
|
|
|
Example code to add a user attribute to the form on step 2::
|
|
|
|
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
|
|
|
|
.. method:: WizardView.process_step(form)
|
|
|
|
This method gives you a way to post-process the form data before the data
|
|
gets stored within the storage backend. By default it just passed the
|
|
form.data dictionary. You should not manipulate the data here but you can
|
|
use the data to do some extra work if needed (e.g. set storage extra data).
|
|
|
|
Default implementation::
|
|
|
|
def process_step(self, form):
|
|
return self.get_form_step_data(form)
|
|
|
|
.. method:: WizardView.process_step_files(form)
|
|
|
|
This method gives you a way to post-process the form files before the
|
|
files gets stored within the storage backend. By default it just passed
|
|
the ``form.files`` dictionary. You should not manipulate the data here
|
|
but you can use the data to do some extra work if needed (e.g. set storage
|
|
extra data).
|
|
|
|
Default implementation::
|
|
|
|
def process_step_files(self, form):
|
|
return self.get_form_step_files(form)
|
|
|
|
.. method:: WizardView.render_revalidation_failure(step, form, **kwargs)
|
|
|
|
When the wizard thinks, all steps passed it revalidates all forms with the
|
|
data from the backend storage.
|
|
|
|
If any of the forms don't validate correctly, this method gets called.
|
|
This method expects two arguments, ``step`` and ``form``.
|
|
|
|
The default implementation resets the current step to the first failing
|
|
form and redirects the user to the invalid form.
|
|
|
|
Default implementation::
|
|
|
|
def render_revalidation_failure(self, step, form, **kwargs):
|
|
self.storage.current_step = step
|
|
return self.render(form, **kwargs)
|
|
|
|
.. method:: WizardView.get_form_step_data(form)
|
|
|
|
This method fetches the form data from and returns the dictionary. You
|
|
can use this method to manipulate the values before the data gets stored
|
|
in the storage backend.
|
|
|
|
Default implementation::
|
|
|
|
def get_form_step_data(self, form):
|
|
return form.data
|
|
|
|
.. method:: WizardView.get_form_step_files(form)
|
|
|
|
This method returns the form files. You can use this method to manipulate
|
|
the files before the data gets stored in the storage backend.
|
|
|
|
Default implementation::
|
|
|
|
def get_form_step_files(self, form):
|
|
return form.files
|
|
|
|
.. method:: WizardView.render(form, **kwargs)
|
|
|
|
This method gets called after the get or post request was handled. You can
|
|
hook in this method to, e.g. change the type of http response.
|
|
|
|
Default implementation::
|
|
|
|
def render(self, form=None, **kwargs):
|
|
form = form or self.get_form()
|
|
context = self.get_context_data(form, **kwargs)
|
|
return self.render_to_response(context)
|
|
|
|
Providing initial data for the forms
|
|
====================================
|
|
|
|
.. attribute:: WizardView.initial_dict
|
|
|
|
Initial data for a wizard's :class:`~django.forms.Form` objects can be
|
|
provided using the optional :attr:`~Wizard.initial_dict` keyword argument.
|
|
This argument should be a dictionary mapping the steps to dictionaries
|
|
containing the initial data for each step. The dictionary of initial data
|
|
will be passed along to the constructor of the step's
|
|
:class:`~django.forms.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!'}
|
|
|
|
The ``initial_dict`` can also take a list of dictionaries for a specific
|
|
step if the step is a ``FormSet``.
|
|
|
|
.. _wizard-files:
|
|
|
|
Handling files
|
|
==============
|
|
|
|
To handle :class:`~django.forms.FileField` within any step form of the wizard,
|
|
you have to add a :attr:`file_storage` to your :class:`WizardView` subclass.
|
|
|
|
This storage will temporarilyy store the uploaded files for the wizard. The
|
|
:attr:`file_storage` attribute should be a
|
|
:class:`~django.core.files.storage.Storage` subclass.
|
|
|
|
.. warning::
|
|
|
|
Please remember to take care of removing old files as the
|
|
:class:`WizardView` won't remove any files, whether the wizard gets
|
|
finished corretly or not.
|
|
|
|
Conditionally view/skip specific steps
|
|
======================================
|
|
|
|
.. attribute:: WizardView.condition_dict
|
|
|
|
The :meth:`~WizardView.as_view` accepts a ``condition_dict`` argument. You can pass a
|
|
dictionary of boolean values or callables. The key should match the steps
|
|
name (e.g. '0', '1').
|
|
|
|
If the value of a specific step is callable it will be called with the
|
|
:class:`WizardView` instance as the only argument. If the return value is true,
|
|
the step's form will be used.
|
|
|
|
This example provides a contact form including a condition. The condition is
|
|
used to show a message form only if a checkbox in the first step was checked.
|
|
|
|
The steps are defined in a ``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)
|
|
|
|
We define our wizard in a ``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],
|
|
})
|
|
|
|
We need to add the ``ContactWizard`` to our ``urls.py`` file::
|
|
|
|
from django.conf.urls.defaults 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}
|
|
)),
|
|
)
|
|
|
|
As you can see, we defined a ``show_message_form_condition`` next to our
|
|
:class:`WizardView` subclass and added a ``condition_dict`` argument to the
|
|
:meth:`~WizardView.as_view` method. The key refers to the second wizard step
|
|
(because of the zero based step index).
|
|
|
|
How to work with ModelForm and ModelFormSet
|
|
===========================================
|
|
|
|
The WizardView supports :class:`~django.forms.ModelForm` and
|
|
:class:`~django.forms.ModelFormSet`. Additionally to the ``initial_dict``,
|
|
the :meth:`~WizardView.as_view` method takes a ``instance_dict`` argument
|
|
with a list of instances for the ``ModelForm`` and ``ModelFormSet``.
|
|
|
|
Usage of NamedUrlWizardView
|
|
===========================
|
|
|
|
.. class:: NamedUrlWizardView
|
|
|
|
There is a :class:`WizardView` subclass which adds named-urls support to the wizard.
|
|
By doing this, you can have single urls for every step.
|
|
|
|
To use the named urls, you have to change the ``urls.py``.
|
|
|
|
Below you will see an example of a contact wizard with two steps, step 1 with
|
|
"contactdata" as its name and step 2 with "leavemessage" as its name.
|
|
|
|
Additionally you have to pass two more arguments to the
|
|
:meth:`~WizardView.as_view` method:
|
|
|
|
* ``url_name`` -- the name of the url (as provided in the urls.py)
|
|
* ``done_step_name`` -- the name in the url for the done step
|
|
|
|
Example code for the changed ``urls.py`` file::
|
|
|
|
from django.conf.urls.defaults 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'),
|
|
)
|