mirror of
				https://github.com/django/django.git
				synced 2025-10-29 08:36:09 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@8987 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| .. _ref-contrib-formtools-form-wizard:
 | |
| 
 | |
| ===========
 | |
| Form wizard
 | |
| ===========
 | |
| 
 | |
| .. module:: django.contrib.formtools.wizard
 | |
|     :synopsis: Splits forms across multiple Web pages.
 | |
| 
 | |
| .. versionadded:: 1.0
 | |
| 
 | |
| Django comes with an optional "form wizard" application that splits
 | |
| :ref:`forms <topics-forms-index>` across multiple Web pages. It maintains
 | |
| state in hashed HTML :samp:`<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 :samp:`<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 :mod:`django.forms`
 | |
|        :class:`~django.forms.forms.Form` classes -- one per wizard page.
 | |
|        
 | |
|     2. Create a :class:`~django.contrib.formtools.wizard.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
 | |
|        :class:`~django.contrib.formtools.wizard.FormWizard` class.
 | |
| 
 | |
| Defining ``Form`` classes
 | |
| =========================
 | |
| 
 | |
| The first step in creating a form wizard is to create the :class:`~django.forms.forms.Form` classes.
 | |
| These should be standard :mod:`django.forms`
 | |
| :class:`~django.forms.forms.Form` classes, covered in the
 | |
| :ref:`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 e-mail 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)
 | |
| 
 | |
| **Important limitation:** Because the wizard uses HTML hidden fields to store
 | |
| data between pages, you may not include a :class:`~django.forms.fields.FileField`
 | |
| in any form except the last one.
 | |
| 
 | |
| Creating a ``FormWizard`` class
 | |
| ===============================
 | |
| 
 | |
| The next step is to create a :class:`~django.contrib.formtools.wizard.FormWizard`
 | |
| class, which should be a subclass of ``django.contrib.formtools.wizard.FormWizard``.
 | |
| 
 | |
| As your :class:`~django.forms.forms.Form` classes, this
 | |
| :class:`~django.contrib.formtools.wizard.FormWizard` class can live anywhere
 | |
| in your codebase, but convention is to put it in :file:`forms.py`.
 | |
| 
 | |
| The only requirement on this subclass is that it implement a
 | |
| :meth:`~django.contrib.formtools.wizard.FormWizard.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 :class:`~django.http.HttpRequest` object
 | |
|     * ``form_list`` -- a list of :mod:`django.forms`
 | |
|       :class:`~django.forms.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
 | |
| :class:`~django.contrib.formtools.wizard.FormWizard` 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:`forms/wizard.html`. (You can
 | |
| change this template name by overriding
 | |
| :meth:`~django.contrib.formtools.wizard..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 :class:`~django.forms.forms.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
 | |
|       :meth:`~django.template.defaultfilters.safe` template filter, to prevent
 | |
|       auto-escaping, because it's raw HTML.
 | |
| 
 | |
| It will also be passed any objects in :data:`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 :attr:`~django.contrib.formtools.wizard.FormWizard.extra_context`
 | |
|       attribute on your :class:`~django.contrib.formtools.wizard.FormWizard`
 | |
|       subclass to a dictionary.
 | |
| 
 | |
|     * Pass :attr:`~django.contrib.formtools.wizard.FormWizard.extra_context`
 | |
|       as extra parameters in the URLconf.
 | |
| 
 | |
| Here's a full example template:
 | |
| 
 | |
| .. code-block:: html+django
 | |
| 
 | |
|     {% 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 :class:`~django.contrib.formtools.wizard.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
 | |
| ===========================
 | |
| 
 | |
| .. class:: FormWizard
 | |
| 
 | |
|     Aside from the :meth:`~django.contrib.formtools.wizard.FormWizard.done()`
 | |
|     method, :class:`~django.contrib.formtools.wizard.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``.)
 | |
| 
 | |
| .. method:: FormWizard.prefix_for_step
 | |
| 
 | |
|     Given the step, returns a :class:`~django.forms.forms.Form` prefix to
 | |
|     use. By default, this simply uses the step itself. For more, see the
 | |
|     :ref:`form prefix documentation <form-prefix>`.
 | |
| 
 | |
|     Default implementation::
 | |
| 
 | |
|         def prefix_for_step(self, step):
 | |
|             return str(step)
 | |
| 
 | |
| .. method:: FormWizard.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.'})
 | |
| 
 | |
| .. method:: FormWizard.security_hash
 | |
| 
 | |
|     Calculates the security hash for the given request object and :class:`~django.forms.forms.Form` instance.
 | |
| 
 | |
|     By default, this uses an MD5 hash of the form data and your
 | |
|     :setting:`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)
 | |
| 
 | |
| .. method:: FormWizard.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]
 | |
| 
 | |
| .. method:: FormWizard.get_template
 | |
| 
 | |
|     Returns the name of the template that should be used for the given step.
 | |
| 
 | |
|     By default, this returns :file:`'forms/wizard.html'`, regardless of step.
 | |
| 
 | |
|     Example::
 | |
| 
 | |
|         def get_template(self, step):
 | |
|             return 'myapp/wizard_%s.html' % step
 | |
| 
 | |
|     If :meth:`~FormWizard.get_template` returns a list of strings, then the wizard will use the
 | |
|     template system's :func:`~django.template.loader.select_template()`
 | |
|     function,
 | |
|     :ref:`explained in the template docs <ref-templates-api-the-python-api>`.
 | |
|     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
 | |
| 
 | |
| .. method:: FormWizard.render_template
 | |
| 
 | |
|     Renders the template for the given step, returning an
 | |
|     :class:`~django.http.HttpResponseRedirect` 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
 | |
|     :meth:`~FormWizard.get_template` instead.
 | |
| 
 | |
|     The template will be rendered with the context documented in the
 | |
|     "Creating templates for the forms" section above.
 | |
| 
 | |
| .. method:: FormWizard.process_step
 | |
| 
 | |
|     Hook for modifying the wizard's internal state, given a fully validated
 | |
|     :class:`~django.forms.forms.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):
 | |
|             # ...
 |