summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJannis Leidel <jannis@leidel.info>2011-06-01 13:47:00 +0000
committerJannis Leidel <jannis@leidel.info>2011-06-01 13:47:00 +0000
commit632dfa233839f8c76bd6b065b8420fe0ab96175a (patch)
tree518a076f9846d71e6ac13f6ee4025f79ecf90131 /docs
parent1a951fa8d474fc9e6114cf63b8ba012233c9afcd (diff)
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
Diffstat (limited to 'docs')
-rw-r--r--docs/internals/deprecation.txt4
-rw-r--r--docs/ref/contrib/formtools/form-wizard.txt576
-rw-r--r--docs/releases/1.4.txt16
3 files changed, 431 insertions, 165 deletions
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 01e4500178..4d81cb7ba3 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -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
diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt
index cbacd594bb..2434c8b2b7 100644
--- a/docs/ref/contrib/formtools/form-wizard.txt
+++ b/docs/ref/contrib/formtools/form-wizard.txt
@@ -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 import FormWizard
+ from django.http import HttpResponseRedirect
+ from django.contrib.formtools.wizard.views import SessionWizardView
- 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/')
+ 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 `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 patterns
- from django.conf.urls.defaults import *
- from testapp.forms import ContactForm1, ContactForm2, ContactWizard
+ 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>`.
+.. 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 prefix_for_step(self, step):
- return str(step)
+ def get_wizard_name(self):
+ return normalize_name(self.__class__.__name__)
-.. method:: FormWizard.render_hash_failure
+.. method:: WizardView.get_prefix()
- Renders a template if the hash check fails. It's rare that you'd need to
- override this.
+ 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 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.'})
+ def get_prefix(self):
+ return self.wizard_name
-.. method:: FormWizard.security_hash
+.. method:: WizardView.get_form(step=None, data=None, files=None)
- Calculates the security hash for the given request object and
- :class:`~django.forms.Form` instance.
+ 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:
- 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.
+ * ``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
- Example::
+ You can override this method to add extra arguments to the form instance.
- def security_hash(self, request, form):
- return my_hash_function(request, form)
+ Example code to add a user attribute to the form on step 2::
-.. method:: FormWizard.parse_params
+ 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
- A hook for saving state from the request object and ``args`` / ``kwargs``
- that were captured from the URL by your URLconf.
+.. method:: WizardView.process_step(form)
- By default, this does nothing.
+ 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).
- Example::
+ Default implementation::
- def parse_params(self, request, *args, **kwargs):
- self.my_state = args[0]
+ def process_step(self, form):
+ return self.get_form_step_data(form)
-.. method:: FormWizard.get_template
+.. method:: WizardView.process_step_files(form)
- Returns the name of the template that should be used for the given step.
+ 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).
- By default, this returns :file:`'forms/wizard.html'`, regardless of step.
+ Default implementation::
- Example::
+ def process_step_files(self, form):
+ return self.get_form_step_files(form)
- def get_template(self, step):
- return 'myapp/wizard_%s.html' % step
+.. method:: WizardView.render_revalidation_failure(step, form, **kwargs)
- 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::
+ When the wizard thinks, all steps passed it revalidates all forms with the
+ data from the backend storage.
- def get_template(self, step):
- return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html']
+ If any of the forms don't validate correctly, this method gets called.
+ This method expects two arguments, ``step`` and ``form``.
-.. method:: FormWizard.render_template
+ The default implementation resets the current step to the first failing
+ form and redirects the user to the invalid form.
- Renders the template for the given step, returning an
- :class:`~django.http.HttpResponse` object.
+ Default implementation::
- 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.
+ def render_revalidation_failure(self, step, form, **kwargs):
+ self.storage.current_step = step
+ return self.render(form, **kwargs)
- The template will be rendered with the context documented in the
- "Creating templates for the forms" section above.
+.. method:: WizardView.get_form_step_data(form)
-.. method:: FormWizard.process_step
+ 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.
- 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.
+ Default implementation::
- 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.
+ def get_form_step_data(self, form):
+ return form.data
- Note that this method is called every time a page is rendered for *all*
- submitted steps.
+.. method:: WizardView.get_form_step_files(form)
- The function signature::
+ 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 process_step(self, request, form, step):
- # ...
+ 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'),
+ )
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 7fdf0d7e1c..2a2090a9b1 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~