Added docs/form_wizard.txt
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7265 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
fac84c0bf4
commit
642f42bf71
|
@ -0,0 +1,295 @@
|
|||
===========
|
||||
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: ../newforms/
|
||||
|
||||
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:
|
||||
|
||||
# Define a number of ``django.newforms`` ``Form`` classes -- one per wizard
|
||||
page.
|
||||
# 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.
|
||||
# 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.
|
||||
# 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.newforms`` ``Form`` classes, covered in the
|
||||
`newforms 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 newforms as 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.
|
||||
|
||||
.. _newforms documentation: ../newforms/
|
||||
|
||||
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.newforms`` ``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.
|
||||
|
||||
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: ../newforms/#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):
|
||||
# ...
|
Loading…
Reference in New Issue