summaryrefslogtreecommitdiff
path: root/parts/django/docs/ref/contrib/formtools
diff options
context:
space:
mode:
Diffstat (limited to 'parts/django/docs/ref/contrib/formtools')
-rw-r--r--parts/django/docs/ref/contrib/formtools/form-preview.txt121
-rw-r--r--parts/django/docs/ref/contrib/formtools/form-wizard.txt312
-rw-r--r--parts/django/docs/ref/contrib/formtools/index.txt10
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