| 1 |
=========== |
|---|
| 2 |
Form wizard |
|---|
| 3 |
=========== |
|---|
| 4 |
|
|---|
| 5 |
**New in Django development version.** |
|---|
| 6 |
|
|---|
| 7 |
Django comes with an optional "form wizard" application that splits forms_ |
|---|
| 8 |
across multiple Web pages. It maintains state in hashed HTML |
|---|
| 9 |
``<input type="hidden">`` fields, and the data isn't processed server-side |
|---|
| 10 |
until the final form is submitted. |
|---|
| 11 |
|
|---|
| 12 |
You might want to use this if you have a lengthy form that would be too |
|---|
| 13 |
unwieldy for display on a single page. The first page might ask the user for |
|---|
| 14 |
core information, the second page might ask for less important information, |
|---|
| 15 |
etc. |
|---|
| 16 |
|
|---|
| 17 |
The term "wizard," in this context, is `explained on Wikipedia`_. |
|---|
| 18 |
|
|---|
| 19 |
.. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29 |
|---|
| 20 |
.. _forms: ../forms/ |
|---|
| 21 |
|
|---|
| 22 |
How it works |
|---|
| 23 |
============ |
|---|
| 24 |
|
|---|
| 25 |
Here's the basic workflow for how a user would use a wizard: |
|---|
| 26 |
|
|---|
| 27 |
1. The user visits the first page of the wizard, fills in the form and |
|---|
| 28 |
submits it. |
|---|
| 29 |
2. The server validates the data. If it's invalid, the form is displayed |
|---|
| 30 |
again, with error messages. If it's valid, the server calculates a |
|---|
| 31 |
secure hash of the data and presents the user with the next form, |
|---|
| 32 |
saving the validated data and hash in ``<input type="hidden">`` fields. |
|---|
| 33 |
3. Step 1 and 2 repeat, for every subsequent form in the wizard. |
|---|
| 34 |
4. Once the user has submitted all the forms and all the data has been |
|---|
| 35 |
validated, the wizard processes the data -- saving it to the database, |
|---|
| 36 |
sending an e-mail, or whatever the application needs to do. |
|---|
| 37 |
|
|---|
| 38 |
Usage |
|---|
| 39 |
===== |
|---|
| 40 |
|
|---|
| 41 |
This application handles as much machinery for you as possible. Generally, you |
|---|
| 42 |
just have to do these things: |
|---|
| 43 |
|
|---|
| 44 |
1. Define a number of ``django.forms`` ``Form`` classes -- one per wizard |
|---|
| 45 |
page. |
|---|
| 46 |
2. Create a ``FormWizard`` class that specifies what to do once all of your |
|---|
| 47 |
forms have been submitted and validated. This also lets you override some |
|---|
| 48 |
of the wizard's behavior. |
|---|
| 49 |
3. Create some templates that render the forms. You can define a single, |
|---|
| 50 |
generic template to handle every one of the forms, or you can define a |
|---|
| 51 |
specific template for each form. |
|---|
| 52 |
4. Point your URLconf at your ``FormWizard`` class. |
|---|
| 53 |
|
|---|
| 54 |
Defining ``Form`` classes |
|---|
| 55 |
========================= |
|---|
| 56 |
|
|---|
| 57 |
The first step in creating a form wizard is to create the ``Form`` classes. |
|---|
| 58 |
These should be standard ``django.forms`` ``Form`` classes, covered in the |
|---|
| 59 |
`forms documentation`_. |
|---|
| 60 |
|
|---|
| 61 |
These classes can live anywhere in your codebase, but convention is to put them |
|---|
| 62 |
in a file called ``forms.py`` in your application. |
|---|
| 63 |
|
|---|
| 64 |
For example, let's write a "contact form" wizard, where the first page's form |
|---|
| 65 |
collects the sender's e-mail address and subject, and the second page collects |
|---|
| 66 |
the message itself. Here's what the ``forms.py`` might look like:: |
|---|
| 67 |
|
|---|
| 68 |
from django import forms |
|---|
| 69 |
|
|---|
| 70 |
class ContactForm1(forms.Form): |
|---|
| 71 |
subject = forms.CharField(max_length=100) |
|---|
| 72 |
sender = forms.EmailField() |
|---|
| 73 |
|
|---|
| 74 |
class ContactForm2(forms.Form): |
|---|
| 75 |
message = forms.CharField(widget=forms.Textarea) |
|---|
| 76 |
|
|---|
| 77 |
**Important limitation:** Because the wizard uses HTML hidden fields to store |
|---|
| 78 |
data between pages, you may not include a ``FileField`` in any form except the |
|---|
| 79 |
last one. |
|---|
| 80 |
|
|---|
| 81 |
.. _forms documentation: ../forms/ |
|---|
| 82 |
|
|---|
| 83 |
Creating a ``FormWizard`` class |
|---|
| 84 |
=============================== |
|---|
| 85 |
|
|---|
| 86 |
The next step is to create a ``FormWizard`` class, which should be a subclass |
|---|
| 87 |
of ``django.contrib.formtools.wizard.FormWizard``. |
|---|
| 88 |
|
|---|
| 89 |
As your ``Form`` classes, this ``FormWizard`` class can live anywhere in your |
|---|
| 90 |
codebase, but convention is to put it in ``forms.py``. |
|---|
| 91 |
|
|---|
| 92 |
The only requirement on this subclass is that it implement a ``done()`` method, |
|---|
| 93 |
which specifies what should happen when the data for *every* form is submitted |
|---|
| 94 |
and validated. This method is passed two arguments: |
|---|
| 95 |
|
|---|
| 96 |
* ``request`` -- an HttpRequest_ object |
|---|
| 97 |
* ``form_list`` -- a list of ``django.forms`` ``Form`` classes |
|---|
| 98 |
|
|---|
| 99 |
In this simplistic example, rather than perform any database operation, the |
|---|
| 100 |
method simply renders a template of the validated data:: |
|---|
| 101 |
|
|---|
| 102 |
from django.shortcuts import render_to_response |
|---|
| 103 |
from django.contrib.formtools.wizard import FormWizard |
|---|
| 104 |
|
|---|
| 105 |
class ContactWizard(FormWizard): |
|---|
| 106 |
def done(self, request, form_list): |
|---|
| 107 |
return render_to_response('done.html', { |
|---|
| 108 |
'form_data': [form.cleaned_data for form in form_list], |
|---|
| 109 |
}) |
|---|
| 110 |
|
|---|
| 111 |
Note that this method will be called via ``POST``, so it really ought to be a |
|---|
| 112 |
good Web citizen and redirect after processing the data. Here's another |
|---|
| 113 |
example:: |
|---|
| 114 |
|
|---|
| 115 |
from django.http import HttpResponseRedirect |
|---|
| 116 |
from django.contrib.formtools.wizard import FormWizard |
|---|
| 117 |
|
|---|
| 118 |
class ContactWizard(FormWizard): |
|---|
| 119 |
def done(self, request, form_list): |
|---|
| 120 |
do_something_with_the_form_data(form_list) |
|---|
| 121 |
return HttpResponseRedirect('/page-to-redirect-to-when-done/') |
|---|
| 122 |
|
|---|
| 123 |
See the section "Advanced ``FormWizard`` methods" below to learn about more |
|---|
| 124 |
``FormWizard`` hooks. |
|---|
| 125 |
|
|---|
| 126 |
.. _HttpRequest: request_response/#httprequest-objects |
|---|
| 127 |
|
|---|
| 128 |
Creating templates for the forms |
|---|
| 129 |
================================ |
|---|
| 130 |
|
|---|
| 131 |
Next, you'll need to create a template that renders the wizard's forms. By |
|---|
| 132 |
default, every form uses a template called ``forms/wizard.html``. (You can |
|---|
| 133 |
change this template name by overriding ``FormWizard.get_template()``, which is |
|---|
| 134 |
documented below. This hook also allows you to use a different template for |
|---|
| 135 |
each form.) |
|---|
| 136 |
|
|---|
| 137 |
This template expects the following context: |
|---|
| 138 |
|
|---|
| 139 |
* ``step_field`` -- The name of the hidden field containing the step. |
|---|
| 140 |
* ``step0`` -- The current step (zero-based). |
|---|
| 141 |
* ``step`` -- The current step (one-based). |
|---|
| 142 |
* ``step_count`` -- The total number of steps. |
|---|
| 143 |
* ``form`` -- The ``Form`` instance for the current step (either empty or |
|---|
| 144 |
with errors). |
|---|
| 145 |
* ``previous_fields`` -- A string representing every previous data field, |
|---|
| 146 |
plus hashes for completed forms, all in the form of hidden fields. Note |
|---|
| 147 |
that you'll need to run this through the ``safe`` template filter, to |
|---|
| 148 |
prevent auto-escaping, because it's raw HTML. |
|---|
| 149 |
|
|---|
| 150 |
It will also be passed any objects in ``extra_context``, which is a dictionary |
|---|
| 151 |
you can specify that contains extra values to add to the context. You can |
|---|
| 152 |
specify it in two ways: |
|---|
| 153 |
|
|---|
| 154 |
* Set the ``extra_context`` attribute on your ``FormWizard`` subclass to a |
|---|
| 155 |
dictionary. |
|---|
| 156 |
|
|---|
| 157 |
* Pass ``extra_context`` as extra parameters in the URLconf. |
|---|
| 158 |
|
|---|
| 159 |
Here's a full example template:: |
|---|
| 160 |
|
|---|
| 161 |
{% extends "base.html" %} |
|---|
| 162 |
|
|---|
| 163 |
{% block content %} |
|---|
| 164 |
<p>Step {{ step }} of {{ step_count }}</p> |
|---|
| 165 |
<form action="." method="post"> |
|---|
| 166 |
<table> |
|---|
| 167 |
{{ form }} |
|---|
| 168 |
</table> |
|---|
| 169 |
<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" /> |
|---|
| 170 |
{{ previous_fields|safe }} |
|---|
| 171 |
<input type="submit"> |
|---|
| 172 |
</form> |
|---|
| 173 |
{% endblock %} |
|---|
| 174 |
|
|---|
| 175 |
Note that ``previous_fields``, ``step_field`` and ``step0`` are all required |
|---|
| 176 |
for the wizard to work properly. |
|---|
| 177 |
|
|---|
| 178 |
Hooking the wizard into a URLconf |
|---|
| 179 |
================================= |
|---|
| 180 |
|
|---|
| 181 |
Finally, give your new ``FormWizard`` object a URL in ``urls.py``. The wizard |
|---|
| 182 |
takes a list of your form objects as arguments:: |
|---|
| 183 |
|
|---|
| 184 |
from django.conf.urls.defaults import * |
|---|
| 185 |
from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard |
|---|
| 186 |
|
|---|
| 187 |
urlpatterns = patterns('', |
|---|
| 188 |
(r'^contact/$', ContactWizard([ContactForm1, ContactForm2])), |
|---|
| 189 |
) |
|---|
| 190 |
|
|---|
| 191 |
Advanced ``FormWizard`` methods |
|---|
| 192 |
=============================== |
|---|
| 193 |
|
|---|
| 194 |
Aside from the ``done()`` method, ``FormWizard`` offers a few advanced method |
|---|
| 195 |
hooks that let you customize how your wizard works. |
|---|
| 196 |
|
|---|
| 197 |
Some of these methods take an argument ``step``, which is a zero-based counter |
|---|
| 198 |
representing the current step of the wizard. (E.g., the first form is ``0`` and |
|---|
| 199 |
the second form is ``1``.) |
|---|
| 200 |
|
|---|
| 201 |
``prefix_for_step`` |
|---|
| 202 |
~~~~~~~~~~~~~~~~~~~ |
|---|
| 203 |
|
|---|
| 204 |
Given the step, returns a ``Form`` prefix to use. By default, this simply uses |
|---|
| 205 |
the step itself. For more, see the `form prefix documentation`_. |
|---|
| 206 |
|
|---|
| 207 |
Default implementation:: |
|---|
| 208 |
|
|---|
| 209 |
def prefix_for_step(self, step): |
|---|
| 210 |
return str(step) |
|---|
| 211 |
|
|---|
| 212 |
.. _form prefix documentation: ../forms/#prefixes-for-forms |
|---|
| 213 |
|
|---|
| 214 |
``render_hash_failure`` |
|---|
| 215 |
~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 216 |
|
|---|
| 217 |
Renders a template if the hash check fails. It's rare that you'd need to |
|---|
| 218 |
override this. |
|---|
| 219 |
|
|---|
| 220 |
Default implementation:: |
|---|
| 221 |
|
|---|
| 222 |
def render_hash_failure(self, request, step): |
|---|
| 223 |
return self.render(self.get_form(step), request, step, |
|---|
| 224 |
context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'}) |
|---|
| 225 |
|
|---|
| 226 |
``security_hash`` |
|---|
| 227 |
~~~~~~~~~~~~~~~~~ |
|---|
| 228 |
|
|---|
| 229 |
Calculates the security hash for the given request object and ``Form`` instance. |
|---|
| 230 |
|
|---|
| 231 |
By default, this uses an MD5 hash of the form data and your |
|---|
| 232 |
`SECRET_KEY setting`_. It's rare that somebody would need to override this. |
|---|
| 233 |
|
|---|
| 234 |
Example:: |
|---|
| 235 |
|
|---|
| 236 |
def security_hash(self, request, form): |
|---|
| 237 |
return my_hash_function(request, form) |
|---|
| 238 |
|
|---|
| 239 |
.. _SECRET_KEY setting: ../settings/#secret-key |
|---|
| 240 |
|
|---|
| 241 |
``parse_params`` |
|---|
| 242 |
~~~~~~~~~~~~~~~~ |
|---|
| 243 |
|
|---|
| 244 |
A hook for saving state from the request object and ``args`` / ``kwargs`` that |
|---|
| 245 |
were captured from the URL by your URLconf. |
|---|
| 246 |
|
|---|
| 247 |
By default, this does nothing. |
|---|
| 248 |
|
|---|
| 249 |
Example:: |
|---|
| 250 |
|
|---|
| 251 |
def parse_params(self, request, *args, **kwargs): |
|---|
| 252 |
self.my_state = args[0] |
|---|
| 253 |
|
|---|
| 254 |
``get_template`` |
|---|
| 255 |
~~~~~~~~~~~~~~~~ |
|---|
| 256 |
|
|---|
| 257 |
Returns the name of the template that should be used for the given step. |
|---|
| 258 |
|
|---|
| 259 |
By default, this returns ``'forms/wizard.html'``, regardless of step. |
|---|
| 260 |
|
|---|
| 261 |
Example:: |
|---|
| 262 |
|
|---|
| 263 |
def get_template(self, step): |
|---|
| 264 |
return 'myapp/wizard_%s.html' % step |
|---|
| 265 |
|
|---|
| 266 |
If ``get_template`` returns a list of strings, then the wizard will use the |
|---|
| 267 |
template system's ``select_template()`` function, `explained in the template docs`_. |
|---|
| 268 |
This means the system will use the first template that exists on the filesystem. |
|---|
| 269 |
For example:: |
|---|
| 270 |
|
|---|
| 271 |
def get_template(self, step): |
|---|
| 272 |
return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html'] |
|---|
| 273 |
|
|---|
| 274 |
.. _explained in the template docs: ../templates_python/#the-python-api |
|---|
| 275 |
|
|---|
| 276 |
``render_template`` |
|---|
| 277 |
~~~~~~~~~~~~~~~~~~~ |
|---|
| 278 |
|
|---|
| 279 |
Renders the template for the given step, returning an ``HttpResponse`` object. |
|---|
| 280 |
|
|---|
| 281 |
Override this method if you want to add a custom context, return a different |
|---|
| 282 |
MIME type, etc. If you only need to override the template name, use |
|---|
| 283 |
``get_template()`` instead. |
|---|
| 284 |
|
|---|
| 285 |
The template will be rendered with the context documented in the |
|---|
| 286 |
"Creating templates for the forms" section above. |
|---|
| 287 |
|
|---|
| 288 |
``process_step`` |
|---|
| 289 |
~~~~~~~~~~~~~~~~ |
|---|
| 290 |
|
|---|
| 291 |
Hook for modifying the wizard's internal state, given a fully validated ``Form`` |
|---|
| 292 |
object. The Form is guaranteed to have clean, valid data. |
|---|
| 293 |
|
|---|
| 294 |
This method should *not* modify any of that data. Rather, it might want to set |
|---|
| 295 |
``self.extra_context`` or dynamically alter ``self.form_list``, based on |
|---|
| 296 |
previously submitted forms. |
|---|
| 297 |
|
|---|
| 298 |
Note that this method is called every time a page is rendered for *all* |
|---|
| 299 |
submitted steps. |
|---|
| 300 |
|
|---|
| 301 |
The function signature:: |
|---|
| 302 |
|
|---|
| 303 |
def process_step(self, request, form, step): |
|---|
| 304 |
# ... |
|---|