diff options
Diffstat (limited to 'parts/django/docs/ref/contrib/formtools')
-rw-r--r-- | parts/django/docs/ref/contrib/formtools/form-preview.txt | 121 | ||||
-rw-r--r-- | parts/django/docs/ref/contrib/formtools/form-wizard.txt | 312 | ||||
-rw-r--r-- | parts/django/docs/ref/contrib/formtools/index.txt | 10 |
3 files changed, 443 insertions, 0 deletions
diff --git a/parts/django/docs/ref/contrib/formtools/form-preview.txt b/parts/django/docs/ref/contrib/formtools/form-preview.txt new file mode 100644 index 0000000..a2cbea7 --- /dev/null +++ b/parts/django/docs/ref/contrib/formtools/form-preview.txt @@ -0,0 +1,121 @@ +============ +Form preview +============ + +.. module:: django.contrib.formtools + :synopsis: Displays an HTML form, forces a preview, then does something + with the submission. + +Django comes with an optional "form preview" application that helps automate +the following workflow: + +"Display an HTML form, force a preview, then do something with the submission." + +To force a preview of a form submission, all you have to do is write a short +Python class. + +Overview +========= + +Given a :class:`django.forms.Form` subclass that you define, this +application takes care of the following workflow: + + 1. Displays the form as HTML on a Web page. + 2. Validates the form data when it's submitted via POST. + a. If it's valid, displays a preview page. + b. If it's not valid, redisplays the form with error messages. + 3. When the "confirmation" form is submitted from the preview page, calls + a hook that you define -- a + :meth:`~django.contrib.formtools.FormPreview.done()` method that gets + passed the valid data. + +The framework enforces the required preview by passing a shared-secret hash to +the preview page via hidden form fields. If somebody tweaks the form parameters +on the preview page, the form submission will fail the hash-comparison test. + +How to use ``FormPreview`` +========================== + + 1. Point Django at the default FormPreview templates. There are two ways to + do this: + + * Add ``'django.contrib.formtools'`` to your + :setting:`INSTALLED_APPS` setting. This will work if your + :setting:`TEMPLATE_LOADERS` setting includes the + ``app_directories`` template loader (which is the case by + default). See the :ref:`template loader docs <template-loaders>` + for more. + + * Otherwise, determine the full filesystem path to the + :file:`django/contrib/formtools/templates` directory, and add that + directory to your :setting:`TEMPLATE_DIRS` setting. + + 2. Create a :class:`~django.contrib.formtools.FormPreview` subclass that + overrides the :meth:`~django.contrib.formtools.FormPreview.done()` + method:: + + from django.contrib.formtools.preview import FormPreview + from myapp.models import SomeModel + + class SomeModelFormPreview(FormPreview): + + def done(self, request, cleaned_data): + # Do something with the cleaned_data, then redirect + # to a "success" page. + return HttpResponseRedirect('/form/success') + + This method takes an :class:`~django.http.HttpRequest` object and a + dictionary of the form data after it has been validated and cleaned. + It should return an :class:`~django.http.HttpResponseRedirect` that + is the end result of the form being submitted. + + 3. Change your URLconf to point to an instance of your + :class:`~django.contrib.formtools.FormPreview` subclass:: + + from myapp.preview import SomeModelFormPreview + from myapp.forms import SomeModelForm + from django import forms + + ...and add the following line to the appropriate model in your URLconf:: + + (r'^post/$', SomeModelFormPreview(SomeModelForm)), + + where ``SomeModelForm`` is a Form or ModelForm class for the model. + + 4. Run the Django server and visit :file:`/post/` in your browser. + +``FormPreview`` classes +======================= + +.. class:: FormPreview + +A :class:`~django.contrib.formtools.FormPreview` class is a simple Python class +that represents the preview workflow. +:class:`~django.contrib.formtools.FormPreview` classes must subclass +``django.contrib.formtools.preview.FormPreview`` and override the +:meth:`~django.contrib.formtools.FormPreview.done()` method. They can live +anywhere in your codebase. + +``FormPreview`` templates +========================= + +By default, the form is rendered via the template :file:`formtools/form.html`, +and the preview page is rendered via the template :file:`formtools/preview.html`. +These values can be overridden for a particular form preview by setting +:attr:`~django.contrib.formtools.FormPreview.preview_template` and +:attr:`~django.contrib.formtools.FormPreview.form_template` attributes on the +FormPreview subclass. See :file:`django/contrib/formtools/templates` for the +default templates. + +Advanced ``FormPreview`` methods +================================ + +.. versionadded:: 1.2 + +.. method:: FormPreview.process_preview + + Given a validated form, performs any extra processing before displaying the + preview page, and saves any extra data in context. + + By default, this method is empty. It is called after the form is validated, + but before the context is modified with hash information and rendered. diff --git a/parts/django/docs/ref/contrib/formtools/form-wizard.txt b/parts/django/docs/ref/contrib/formtools/form-wizard.txt new file mode 100644 index 0000000..390d575 --- /dev/null +++ b/parts/django/docs/ref/contrib/formtools/form-wizard.txt @@ -0,0 +1,312 @@ +=========== +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 +:doc:`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 :class:`~django.forms.Form` classes -- one per wizard + page. + + 2. Create a :class:`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:`FormWizard` class. + +Defining ``Form`` classes +========================= + +The first step in creating a form wizard is to create the +:class:`~django.forms.Form` classes. These should be standard +:class:`django.forms.Form` classes, covered in the :doc:`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.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` subclass. As with your +:class:`~django.forms.Form` classes, this :class:`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:`~FormWizard.done()` method. + +.. method:: FormWizard.done + + This method 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 :class:`~django.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:`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:`~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 :class:`~django.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 :tfilter:`safe` template filter, + to prevent auto-escaping, because it's raw HTML. + +You can supply extra context to this template in two ways: + + * Set the :attr:`~FormWizard.extra_context` attribute on your + :class:`FormWizard` subclass to a dictionary. + + * Pass a dictionary as a parameter named ``extra_context`` to your wizard's + URL pattern in your URLconf. See :ref:`hooking-wizard-into-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">{% csrf_token %} + <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-wizard-into-urlconf: + +Hooking the wizard into a URLconf +================================= + +Finally, we need to specify which forms to use in the wizard, and then +deploy the new :class:`FormWizard` object a URL in ``urls.py``. The +wizard takes a list of your :class:`~django.forms.Form` objects as +arguments when you instantiate the Wizard:: + + from django.conf.urls.defaults import * + from testapp.forms import ContactForm1, ContactForm2, ContactWizard + + urlpatterns = patterns('', + (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])), + ) + +Advanced ``FormWizard`` methods +=============================== + +.. class:: FormWizard + + Aside from the :meth:`~done()` method, :class:`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 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.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. + 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'] + +.. method:: FormWizard.render_template + + Renders the template for the given step, returning an + :class:`~django.http.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 :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.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): + # ... diff --git a/parts/django/docs/ref/contrib/formtools/index.txt b/parts/django/docs/ref/contrib/formtools/index.txt new file mode 100644 index 0000000..f364706 --- /dev/null +++ b/parts/django/docs/ref/contrib/formtools/index.txt @@ -0,0 +1,10 @@ +django.contrib.formtools +======================== + +A set of high-level abstractions for Django forms (:mod:`django.forms`). + +.. toctree:: + :maxdepth: 1 + + form-preview + form-wizard |