123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- ===========
- Form wizard
- ===========
- **New in Django development version.**
- Django comes with an optional "form wizard" application that splits forms_
- across multiple Web pages. It maintains state in hashed HTML
- ``<input type="hidden">`` fields, and the data isn't processed server-side
- until the final form is submitted.
- 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
- .. _forms: ../forms/
- 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 calculates a
- secure hash of the data and presents the user with the next form,
- saving the validated data and hash in ``<input type="hidden">`` fields.
- 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 e-mail, 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 ``django.forms`` ``Form`` classes -- one per wizard
- page.
- 2. Create a ``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.
- 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 ``FormWizard`` class.
- Defining ``Form`` classes
- =========================
- The first step in creating a form wizard is to create the ``Form`` classes.
- These should be standard ``django.forms`` ``Form`` classes, covered in the
- `forms documentation`_.
- These classes can live anywhere in your codebase, but convention is to put them
- in a file called ``forms.py`` in your application.
- For example, let's write a "contact form" wizard, where the first page's form
- collects the sender's e-mail address and subject, and the second page collects
- the message itself. Here's what the ``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)
- **Important limitation:** Because the wizard uses HTML hidden fields to store
- data between pages, you may not include a ``FileField`` in any form except the
- last one.
- .. _forms documentation: ../forms/
- Creating a ``FormWizard`` class
- ===============================
- The next step is to create a ``FormWizard`` class, which should be a subclass
- of ``django.contrib.formtools.wizard.FormWizard``.
- As your ``Form`` classes, this ``FormWizard`` class can live anywhere in your
- codebase, but convention is to put it in ``forms.py``.
- The only requirement on this subclass is that it implement a ``done()`` method,
- which specifies what should happen when the data for *every* form is submitted
- and validated. This method is passed two arguments:
- * ``request`` -- an HttpRequest_ object
- * ``form_list`` -- a list of ``django.forms`` ``Form`` classes
- 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 import FormWizard
- 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::
- from django.http import HttpResponseRedirect
- from django.contrib.formtools.wizard import FormWizard
- 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
- ``FormWizard`` hooks.
- .. _HttpRequest: request_response/#httprequest-objects
- 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 ``forms/wizard.html``. (You can
- change this template name by overriding ``FormWizard.get_template()``, which is
- documented below. This hook also allows you to use a different template for
- each form.)
- This template expects the following context:
- * ``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 ``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 ``safe`` template filter, to
- prevent auto-escaping, because it's raw HTML.
- It will also be passed any objects in ``extra_context``, which is a dictionary
- you can specify that contains extra values to add to the context. You can
- specify it in two ways:
- * Set the ``extra_context`` attribute on your ``FormWizard`` subclass to a
- dictionary.
- * Pass ``extra_context`` as extra parameters in the URLconf.
- Here's a full example template::
- {% extends "base.html" %}
- {% block content %}
- <p>Step {{ step }} of {{ step_count }}</p>
- <form action="." method="post">
- <table>
- {{ form }}
- </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.
- Hooking the wizard into a URLconf
- =================================
- Finally, give your new ``FormWizard`` object a URL in ``urls.py``. The wizard
- takes a list of your form objects as arguments::
- from django.conf.urls.defaults import *
- from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard
- urlpatterns = patterns('',
- (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
- )
- Advanced ``FormWizard`` methods
- ===============================
- Aside from the ``done()`` method, ``FormWizard`` 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``.)
- ``prefix_for_step``
- ~~~~~~~~~~~~~~~~~~~
- Given the step, returns a ``Form`` prefix to use. By default, this simply uses
- the step itself. For more, see the `form prefix documentation`_.
- Default implementation::
- def prefix_for_step(self, step):
- return str(step)
- .. _form prefix documentation: ../forms/#prefixes-for-forms
- ``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.'})
- ``security_hash``
- ~~~~~~~~~~~~~~~~~
- Calculates the security hash for the given request object and ``Form`` instance.
- By default, this uses an MD5 hash of the form data and your
- `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)
- .. _SECRET_KEY setting: ../settings/#secret-key
- ``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]
- ``get_template``
- ~~~~~~~~~~~~~~~~
- Returns the name of the template that should be used for the given step.
- By default, this returns ``'forms/wizard.html'``, regardless of step.
- Example::
- def get_template(self, step):
- return 'myapp/wizard_%s.html' % step
- If ``get_template`` returns a list of strings, then the wizard will use the
- template system's ``select_template()`` function, `explained in the template docs`_.
- 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']
- .. _explained in the template docs: ../templates_python/#the-python-api
- ``render_template``
- ~~~~~~~~~~~~~~~~~~~
- Renders the template for the given step, returning an ``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
- ``get_template()`` instead.
- The template will be rendered with the context documented in the
- "Creating templates for the forms" section above.
- ``process_step``
- ~~~~~~~~~~~~~~~~
- Hook for modifying the wizard's internal state, given a fully validated ``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::
- def process_step(self, request, form, step):
- # ...
|