Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added docs/form_wizard.txt

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7265 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 642f42bf71848edfa8b5bea2cb58f638e046a86b 1 parent fac84c0
@adrianholovaty adrianholovaty authored
Showing with 295 additions and 0 deletions.
  1. +295 −0 docs/form_wizard.txt
View
295 docs/form_wizard.txt
@@ -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):
+ # ...
Please sign in to comment.
Something went wrong with that request. Please try again.