mirror of
https://github.com/django/django.git
synced 2025-08-03 10:34:04 +00:00
Fixed #9200 -- Added new form wizard to formtools based on class based views. Many thanks to Stephan Jäkel, ddurham and ElliottM for their work.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16307 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
1a951fa8d4
commit
632dfa2338
39 changed files with 2614 additions and 364 deletions
|
@ -203,6 +203,10 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
settings have been superseded by :setting:`IGNORABLE_404_URLS` in
|
||||
the 1.4 release. They will be removed.
|
||||
|
||||
* The :doc:`form wizard </ref/contrib/formtools/form-wizard>` has been
|
||||
refactored to use class based views with pluggable backends in 1.4.
|
||||
The previous implementation will be deprecated.
|
||||
|
||||
* 2.0
|
||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
||||
|
|
|
@ -2,23 +2,22 @@
|
|||
Form wizard
|
||||
===========
|
||||
|
||||
.. module:: django.contrib.formtools.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 hashed HTML :samp:`<input type="hidden">` fields so that the full
|
||||
server-side processing can be delayed until the submission of the final form.
|
||||
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`_.
|
||||
The term "wizard", in this context, is `explained on Wikipedia`_.
|
||||
|
||||
.. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29
|
||||
.. _forms: ../forms/
|
||||
|
||||
How it works
|
||||
============
|
||||
|
@ -28,10 +27,8 @@ 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 calculates a
|
||||
secure hash of the data and presents the user with the next form,
|
||||
saving the validated data and hash in :samp:`<input type="hidden">`
|
||||
fields.
|
||||
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,
|
||||
|
@ -40,30 +37,33 @@ Here's the basic workflow for how a user would use a wizard:
|
|||
Usage
|
||||
=====
|
||||
|
||||
This application handles as much machinery for you as possible. Generally, you
|
||||
just have to do these things:
|
||||
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.
|
||||
1. Define a number of :class:`~django.forms.Form` classes -- one per
|
||||
wizard page.
|
||||
|
||||
2. Create a :class:`FormWizard` class 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.
|
||||
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. Point your URLconf at your :class:`FormWizard` class.
|
||||
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
|
||||
</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
|
||||
|
@ -79,85 +79,100 @@ the message itself. Here's what the :file:`forms.py` might look like::
|
|||
class ContactForm2(forms.Form):
|
||||
message = forms.CharField(widget=forms.Textarea)
|
||||
|
||||
**Important limitation:** Because the wizard uses HTML hidden fields to store
|
||||
data between pages, you may not include a :class:`~django.forms.FileField`
|
||||
in any form except the last one.
|
||||
|
||||
Creating a ``FormWizard`` class
|
||||
===============================
|
||||
.. 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.FormWizard` subclass. As with your
|
||||
:class:`~django.forms.Form` classes, this :class:`FormWizard` class can live
|
||||
anywhere in your codebase, but convention is to put it in :file:`forms.py`.
|
||||
: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:`~FormWizard.done()` method.
|
||||
:meth:`~WizardView.done()` method.
|
||||
|
||||
.. method:: FormWizard.done
|
||||
.. 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 two arguments:
|
||||
submitted and validated. This method is passed a list of validated
|
||||
:class:`~django.forms.Form` instances.
|
||||
|
||||
* ``request`` -- an :class:`~django.http.HttpRequest` object
|
||||
* ``form_list`` -- a list of :class:`~django.forms.Form` classes
|
||||
In this simplistic example, rather than performing any database operation,
|
||||
the method simply renders a template of the validated data::
|
||||
|
||||
In this simplistic example, rather than perform 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
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
from django.contrib.formtools.wizard import FormWizard
|
||||
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],
|
||||
})
|
||||
|
||||
class ContactWizard(FormWizard):
|
||||
def done(self, request, form_list):
|
||||
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::
|
||||
|
||||
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
|
||||
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.formtools.wizard import FormWizard
|
||||
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/')
|
||||
|
||||
class ContactWizard(FormWizard):
|
||||
def done(self, request, form_list):
|
||||
do_something_with_the_form_data(form_list)
|
||||
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
|
||||
|
||||
See the section `Advanced FormWizard methods`_ below to learn about more
|
||||
:class:`FormWizard` hooks.
|
||||
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:`forms/wizard.html`. (You can
|
||||
change this template name by overriding :meth:`~FormWizard.get_template()`,
|
||||
which is documented below. This hook also allows you to use a different
|
||||
template for each form.)
|
||||
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 the following context:
|
||||
This template expects a ``wizard`` object that has various items attached to
|
||||
it:
|
||||
|
||||
* ``step_field`` -- The name of the hidden field containing the step.
|
||||
* ``step0`` -- The current step (zero-based).
|
||||
* ``step`` -- The current step (one-based).
|
||||
* ``step_count`` -- The total number of steps.
|
||||
* ``form`` -- The :class:`~django.forms.Form` instance for the current step
|
||||
(either empty or with errors).
|
||||
* ``previous_fields`` -- A string representing every previous data field,
|
||||
plus hashes for completed forms, all in the form of hidden fields. Note
|
||||
that you'll need to run this through the :tfilter:`safe` template filter,
|
||||
to prevent auto-escaping, because it's raw HTML.
|
||||
* ``form`` -- The :class:`~django.forms.Form` instance for the current
|
||||
step (either empty or with errors).
|
||||
|
||||
You can supply extra context to this template in two ways:
|
||||
* ``steps`` -- A helper object to access the various steps related data:
|
||||
|
||||
* Set the :attr:`~FormWizard.extra_context` attribute on your
|
||||
:class:`FormWizard` subclass to a dictionary.
|
||||
* ``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.
|
||||
|
||||
* Pass a dictionary as a parameter named ``extra_context`` to your wizard's
|
||||
URL pattern in your URLconf. See :ref:`hooking-wizard-into-urlconf`.
|
||||
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:
|
||||
|
||||
|
@ -166,170 +181,401 @@ Here's a full example template:
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>Step {{ step }} of {{ step_count }}</p>
|
||||
<p>Step {{ wizard.steps.current }} of {{ wizard.steps.count }}</p>
|
||||
<form action="." method="post">{% csrf_token %}
|
||||
<table>
|
||||
{{ form }}
|
||||
{{ 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="hidden" name="{{ step_field }}" value="{{ step0 }}" />
|
||||
{{ previous_fields|safe }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
Note that ``previous_fields``, ``step_field`` and ``step0`` are all required
|
||||
for the wizard to work properly.
|
||||
.. note::
|
||||
|
||||
.. _hooking-wizard-into-urlconf:
|
||||
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:`FormWizard` object a URL in ``urls.py``. The
|
||||
wizard takes a list of your :class:`~django.forms.Form` objects as
|
||||
arguments when you instantiate the Wizard::
|
||||
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 *
|
||||
from testapp.forms import ContactForm1, ContactForm2, ContactWizard
|
||||
from django.conf.urls.defaults import patterns
|
||||
|
||||
from myapp.forms import ContactForm1, ContactForm2
|
||||
from myapp.views import ContactWizard
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
|
||||
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
|
||||
)
|
||||
|
||||
Advanced ``FormWizard`` methods
|
||||
.. _wizardview-advanced-methods:
|
||||
|
||||
Advanced ``WizardView`` methods
|
||||
===============================
|
||||
|
||||
.. class:: FormWizard
|
||||
.. class:: WizardView
|
||||
|
||||
Aside from the :meth:`~done()` method, :class:`FormWizard` offers a few
|
||||
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 representing the current step of the wizard. (E.g., the first form
|
||||
is ``0`` and the second form is ``1``.)
|
||||
counter as string representing the current step of the wizard. (E.g., the
|
||||
first form is ``'0'`` and the second form is ``'1'``)
|
||||
|
||||
.. method:: FormWizard.prefix_for_step
|
||||
.. method:: WizardView.get_form_prefix(step)
|
||||
|
||||
Given the step, returns a form prefix to use. By default, this simply uses
|
||||
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>`.
|
||||
|
||||
Default implementation::
|
||||
|
||||
def prefix_for_step(self, step):
|
||||
return str(step)
|
||||
|
||||
.. method:: FormWizard.render_hash_failure
|
||||
|
||||
Renders a template if the hash check fails. It's rare that you'd need to
|
||||
override this.
|
||||
|
||||
Default implementation::
|
||||
|
||||
def render_hash_failure(self, request, step):
|
||||
return self.render(self.get_form(step), request, step,
|
||||
context={'wizard_error':
|
||||
'We apologize, but your form has expired. Please'
|
||||
' continue filling out the form from this page.'})
|
||||
|
||||
.. method:: FormWizard.security_hash
|
||||
|
||||
Calculates the security hash for the given request object and
|
||||
:class:`~django.forms.Form` instance.
|
||||
|
||||
By default, this generates a SHA1 HMAC using your form data and your
|
||||
:setting:`SECRET_KEY` setting. It's rare that somebody would need to
|
||||
override this.
|
||||
|
||||
Example::
|
||||
|
||||
def security_hash(self, request, form):
|
||||
return my_hash_function(request, form)
|
||||
|
||||
.. method:: FormWizard.parse_params
|
||||
|
||||
A hook for saving state from the request object and ``args`` / ``kwargs``
|
||||
that were captured from the URL by your URLconf.
|
||||
|
||||
By default, this does nothing.
|
||||
|
||||
Example::
|
||||
|
||||
def parse_params(self, request, *args, **kwargs):
|
||||
self.my_state = args[0]
|
||||
|
||||
.. method:: FormWizard.get_template
|
||||
|
||||
Returns the name of the template that should be used for the given step.
|
||||
|
||||
By default, this returns :file:`'forms/wizard.html'`, regardless of step.
|
||||
|
||||
Example::
|
||||
|
||||
def get_template(self, step):
|
||||
return 'myapp/wizard_%s.html' % step
|
||||
|
||||
If :meth:`~FormWizard.get_template` returns a list of strings, then the
|
||||
wizard will use the template system's
|
||||
:func:`~django.template.loader.select_template` function.
|
||||
This means the system will use the first template that exists on the
|
||||
filesystem. For example::
|
||||
|
||||
def get_template(self, step):
|
||||
return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html']
|
||||
|
||||
.. method:: FormWizard.render_template
|
||||
|
||||
Renders the template for the given step, returning an
|
||||
:class:`~django.http.HttpResponse` object.
|
||||
|
||||
Override this method if you want to add a custom context, return a
|
||||
different MIME type, etc. If you only need to override the template name,
|
||||
use :meth:`~FormWizard.get_template` instead.
|
||||
|
||||
The template will be rendered with the context documented in the
|
||||
"Creating templates for the forms" section above.
|
||||
|
||||
.. method:: FormWizard.process_step
|
||||
.. 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.
|
||||
|
||||
This method should *not* modify any of that data. Rather, it might want to
|
||||
set ``self.extra_context`` or dynamically alter ``self.form_list``, based
|
||||
on previously submitted forms.
|
||||
|
||||
Note that this method is called every time a page is rendered for *all*
|
||||
submitted steps.
|
||||
|
||||
The function signature::
|
||||
The default implementation::
|
||||
|
||||
def process_step(self, request, form, step):
|
||||
# ...
|
||||
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:: FormWizard.initial
|
||||
.. attribute:: WizardView.initial_dict
|
||||
|
||||
Initial data for a wizard's :class:`~django.forms.Form` objects can be
|
||||
provided using the optional :attr:`~FormWizard.initial` keyword argument.
|
||||
This argument should be a dictionary mapping a step to a dictionary
|
||||
containing the initial data for that step. The dictionary of initial data
|
||||
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 testapp.forms import ContactForm1, ContactForm2, ContactWizard
|
||||
>>> from myapp.forms import ContactForm1, ContactForm2
|
||||
>>> from myapp.views import ContactWizard
|
||||
>>> initial = {
|
||||
... 0: {'subject': 'Hello', 'sender': 'user@example.com'},
|
||||
... 1: {'message': 'Hi there!'}
|
||||
... '0': {'subject': 'Hello', 'sender': 'user@example.com'},
|
||||
... '1': {'message': 'Hi there!'}
|
||||
... }
|
||||
>>> wiz = ContactWizard([ContactForm1, ContactForm2], initial=initial)
|
||||
>>> form1 = wiz.get_form(0)
|
||||
>>> form2 = wiz.get_form(1)
|
||||
>>> 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 from 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'),
|
||||
)
|
||||
|
|
|
@ -55,6 +55,22 @@ signing in Web applications.
|
|||
|
||||
See :doc:`cryptographic signing </topics/signing>` docs for more information.
|
||||
|
||||
New form wizard
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The previously shipped ``FormWizard`` of the formtools contrib app has been
|
||||
replaced with a new implementation that is based on the class based views
|
||||
introduced in Django 1.3. It features a pluggable storage API and doesn't
|
||||
require the wizard to pass around hidden fields for every previous step.
|
||||
|
||||
Django 1.4 ships with a session based storage backend and a cookie based
|
||||
storage backend. The latter uses the tools for
|
||||
:doc:`cryptographic signing </topics/signing>` also introduced in
|
||||
Django 1.4 to store the wizard state in the user's cookies.
|
||||
|
||||
See the :doc:`form wizard </ref/contrib/formtools/form-wizard>` docs for
|
||||
more information.
|
||||
|
||||
Simple clickjacking protection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue