diff --git a/docs/form_wizard.txt b/docs/form_wizard.txt new file mode 100644 index 0000000000..b7144ebef2 --- /dev/null +++ b/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 +```` 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 ```` 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 %} +
Step {{ step }} of {{ step_count }}
+ + {% 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): + # ...