123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- ===========
- 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 completely
- 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.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 %}
- {% 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 provided while initializing the
- form wizard, an empty dictionary should be returned.
- The default implementation::
- def get_form_initial(self, step):
- return self.initial_dict.get(step, {})
- .. method:: WizardView.get_form_kwargs(step)
- Returns a dictionary which will be used as the keyword arguments when
- instantiating the form instance on given ``step``.
- The default implementation::
- def get_form_kwargs(self, step):
- return {}
- .. method:: WizardView.get_form_instance(step)
- Returns an object which will be passed to the form for ``step`` as
- ``instance``. If no instance object was provided 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 correctly 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'),
- )
|