diff options
| author | Jannis Leidel <jannis@leidel.info> | 2011-06-01 13:47:00 +0000 |
|---|---|---|
| committer | Jannis Leidel <jannis@leidel.info> | 2011-06-01 13:47:00 +0000 |
| commit | 632dfa233839f8c76bd6b065b8420fe0ab96175a (patch) | |
| tree | 518a076f9846d71e6ac13f6ee4025f79ecf90131 /docs | |
| parent | 1a951fa8d474fc9e6114cf63b8ba012233c9afcd (diff) | |
Fixed #9200 -- Added new form wizard to formtools based on class based views. Many thanks to Stephan Jäkel, ddurham and ElliottM for their work.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16307 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/internals/deprecation.txt | 4 | ||||
| -rw-r--r-- | docs/ref/contrib/formtools/form-wizard.txt | 576 | ||||
| -rw-r--r-- | docs/releases/1.4.txt | 16 |
3 files changed, 431 insertions, 165 deletions
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 01e4500178..4d81cb7ba3 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -203,6 +203,10 @@ their deprecation, as per the :ref:`Django deprecation policy settings have been superseded by :setting:`IGNORABLE_404_URLS` in the 1.4 release. They will be removed. + * The :doc:`form wizard </ref/contrib/formtools/form-wizard>` has been + refactored to use class based views with pluggable backends in 1.4. + The previous implementation will be deprecated. + * 2.0 * ``django.views.defaults.shortcut()``. This function has been moved to ``django.contrib.contenttypes.views.shortcut()`` as part of the diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index cbacd594bb..2434c8b2b7 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -2,23 +2,22 @@ Form wizard =========== -.. module:: django.contrib.formtools.wizard +.. module:: django.contrib.formtools.wizard.views :synopsis: Splits forms across multiple Web pages. 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 so that the full -server-side processing can be delayed until the submission of the final form. +state in one of the backends so that the full server-side processing can be +delayed until the submission of the final form. 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`_. +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 ============ @@ -28,10 +27,8 @@ 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. + again, with error messages. If it's valid, the server saves the current + state of the wizard in the backend and redirects to the next step. 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, @@ -40,30 +37,33 @@ Here's the basic workflow for how a user would use a wizard: Usage ===== -This application handles as much machinery for you as possible. Generally, you -just have to do these things: +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. + 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. + 2. Create a :class:`WizardView` subclass 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. + 4. Add ``django.contrib.formtools.wizard`` to your + :setting:`INSTALLED_APPS` list in your settings file. + + 5. Point your URLconf at your :class:`WizardView` :meth:`~WizardView.as_view` method. 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 +</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 @@ -79,85 +79,100 @@ the message itself. Here's what the :file:`forms.py` might look like:: 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 -=============================== +.. note:: + + In order to use :class:`~django.forms.FileField` in any form, see the + section :ref:`Handling files <wizard-files>` below to learn more about + what to do. + +Creating a ``WizardView`` 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`. +:class:`django.contrib.formtools.wizard.view.WizardView` subclass. You can +also use the :class:`SessionWizardView` or :class:`CookieWizardView` class +which preselects the wizard storage backend. + +.. note:: + + To use the :class:`SessionWizardView` follow the instructions + in the :doc:`sessions documentation </topics/http/sessions>` on + how to enable sessions. + +We will use the :class:`SessionWizardView` in all examples but is is completly +fine to use the :class:`CookieWizardView` instead. As with your +:class:`~django.forms.Form` classes, this :class:`WizardView` class can live +anywhere in your codebase, but convention is to put it in :file:`views.py`. The only requirement on this subclass is that it implement a -:meth:`~FormWizard.done()` method. +:meth:`~WizardView.done()` method. -.. method:: FormWizard.done +.. method:: WizardView.done(form_list) This method specifies what should happen when the data for *every* form is - submitted and validated. This method is passed two arguments: + submitted and validated. This method is passed a list of validated + :class:`~django.forms.Form` instances. - * ``request`` -- an :class:`~django.http.HttpRequest` object - * ``form_list`` -- a list of :class:`~django.forms.Form` classes + In this simplistic example, rather than performing any database operation, + the method simply renders a template of the validated data:: -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.views import SessionWizardView - from django.shortcuts import render_to_response - from django.contrib.formtools.wizard import FormWizard + class ContactWizard(SessionWizardView): + def done(self, form_list, **kwargs): + return render_to_response('done.html', { + 'form_data': [form.cleaned_data for form in form_list], + }) - 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:: + 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 + from django.http import HttpResponseRedirect + from django.contrib.formtools.wizard.views import SessionWizardView - 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/') + class ContactWizard(SessionWizardView): + def done(self, form_list, **kwargs): + 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. +See the section :ref:`Advanced WizardView methods <wizardview-advanced-methods>` +below to learn about more :class:`WizardView` 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.) +default, every form uses a template called +:file:`formtools/wizard/wizard_form.html`. You can change this template name +by overriding either the :attr:`~WizardView.template_name` attribute or the +:meth:`~WizardView.get_template_names()` method, which is documented below. +This hook also allows you to use a different template for each form. -This template expects the following context: +This template expects a ``wizard`` object that has various items attached to +it: - * ``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. + * ``form`` -- The :class:`~django.forms.Form` instance for the current + step (either empty or with errors). -You can supply extra context to this template in two ways: + * ``steps`` -- A helper object to access the various steps related data: - * Set the :attr:`~FormWizard.extra_context` attribute on your - :class:`FormWizard` subclass to a dictionary. + * ``step0`` -- The current step (zero-based). + * ``step1`` -- The current step (one-based). + * ``count`` -- The total number of steps. + * ``first`` -- The first step. + * ``last`` -- The last step. + * ``current`` -- The current (or first) step. + * ``next`` -- The next step. + * ``prev`` -- The previous step. + * ``index`` -- The index of the current step. + * ``all`` -- A list of all steps of the wizard. - * Pass a dictionary as a parameter named ``extra_context`` to your wizard's - URL pattern in your URLconf. See :ref:`hooking-wizard-into-urlconf`. +You can supply additional context variables by using the +:meth:`~FormWizard.get_context_data` method of your :class:`FormWizard` +subclass. Here's a full example template: @@ -166,170 +181,401 @@ Here's a full example template: {% extends "base.html" %} {% block content %} - <p>Step {{ step }} of {{ step_count }}</p> + <p>Step {{ wizard.steps.current }} of {{ wizard.steps.count }}</p> <form action="." method="post">{% csrf_token %} <table> - {{ form }} + {{ wizard.management_form }} + {% if wizard.form.forms %} + {{ wizard.form.management_form }} + {% for form in wizard.form.forms %} + {{ form }} + {% endfor %} + {% else %} + {{ wizard.form }} + {% endif %} + {% if wizard.steps.prev %} + <button name="wizard_prev_step" value="{{ wizard.steps.first }}">{% trans "first step" %}</button> + <button name="wizard_prev_step" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button> + {% endif %} </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. +.. note:: -.. _hooking-wizard-into-urlconf: + Note that ``{{ wizard.management_form }}`` **must be used** for + the wizard to work properly. + +.. _wizard-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:: +deploy the new :class:`WizardView` object a URL in the ``urls.py``. The +wizard's :meth:`as_view` method takes a list of your +:class:`~django.forms.Form` classes as an argument during instantiation:: + + from django.conf.urls.defaults import patterns - from django.conf.urls.defaults import * - from testapp.forms import ContactForm1, ContactForm2, ContactWizard + from myapp.forms import ContactForm1, ContactForm2 + from myapp.views import ContactWizard urlpatterns = patterns('', - (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])), + (r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])), ) -Advanced ``FormWizard`` methods +.. _wizardview-advanced-methods: + +Advanced ``WizardView`` methods =============================== -.. class:: FormWizard +.. class:: WizardView - Aside from the :meth:`~done()` method, :class:`FormWizard` offers a few + Aside from the :meth:`~done()` method, :class:`WizardView` 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``.) + counter as string 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 +.. method:: WizardView.get_form_prefix(step) - Given the step, returns a form prefix to use. By default, this simply uses + 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>`. +.. method:: WizardView.process_step(form) + + 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. + + Note that this method is called every time a page is rendered for *all* + submitted steps. + + The default implementation:: + + def process_step(self, form): + return self.get_form_step_data(form) + +.. method:: WizardView.get_form_initial(step) + + Returns a dictionary which will be passed to the form for ``step`` as + ``initial``. If no initial data was provied while initializing the + form wizard, a empty dictionary should be returned. + + The default implementation:: + + def get_form_initial(self, step): + return self.initial_dict.get(step, {}) + +.. method:: WizardView.get_form_instance(step) + + Returns a object which will be passed to the form for ``step`` as + ``instance``. If no instance object was provied while initializing + the form wizard, None be returned. + + The default implementation:: + + def get_form_instance(self, step): + return self.instance_dict.get(step, None) + +.. method:: WizardView.get_context_data(form, **kwargs) + + Returns the template context for a step. You can overwrite this method + to add more data for all or some steps. This method returns a dictionary + containing the rendered form step. + + The default template context variables are: + + * Any extra data the storage backend has stored + * ``form`` -- form instance of the current step + * ``wizard`` -- the wizard instance itself + + Example to add extra variables for a specific step:: + + def get_context_data(self, form, **kwargs): + context = super(MyWizard, self).get_context_data(form, **kwargs) + if self.steps.current == 'my_step_name': + context.update({'another_var': True}) + return context + +.. method:: WizardView.get_wizard_name() + + This method can be used to change the wizard's internal name. + Default implementation:: - def prefix_for_step(self, step): - return str(step) + def get_wizard_name(self): + return normalize_name(self.__class__.__name__) -.. method:: FormWizard.render_hash_failure +.. method:: WizardView.get_prefix() - Renders a template if the hash check fails. It's rare that you'd need to - override this. + This method returns a prefix for the storage backends. These backends use + the prefix to fetch the correct data for the wizard. (Multiple wizards + could save their data in one session) + + You can change this method to make the wizard data prefix more unique to, + e.g. have multiple instances of one wizard in one session. 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.'}) + def get_prefix(self): + return self.wizard_name -.. method:: FormWizard.security_hash +.. method:: WizardView.get_form(step=None, data=None, files=None) - Calculates the security hash for the given request object and - :class:`~django.forms.Form` instance. + This method constructs the form for a given ``step``. If no ``step`` is + defined, the current step will be determined automatically. + The method gets three arguments: - By default, this generates a SHA1 HMAC using your form data and your - :setting:`SECRET_KEY` setting. It's rare that somebody would need to - override this. + * ``step`` -- The step for which the form instance should be generated. + * ``data`` -- Gets passed to the form's data argument + * ``files`` -- Gets passed to the form's files argument - Example:: + You can override this method to add extra arguments to the form instance. - def security_hash(self, request, form): - return my_hash_function(request, form) + Example code to add a user attribute to the form on step 2:: -.. method:: FormWizard.parse_params + def get_form(self, step=None, data=None, files=None): + form = super(MyWizard, self).get_form(step, data, files) + if step == '1': + form.user = self.request.user + return form - A hook for saving state from the request object and ``args`` / ``kwargs`` - that were captured from the URL by your URLconf. +.. method:: WizardView.process_step(form) - By default, this does nothing. + This method gives you a way to post-process the form data before the data + gets stored within the storage backend. By default it just passed the + form.data dictionary. You should not manipulate the data here but you can + use the data to do some extra work if needed (e.g. set storage extra data). - Example:: + Default implementation:: - def parse_params(self, request, *args, **kwargs): - self.my_state = args[0] + def process_step(self, form): + return self.get_form_step_data(form) -.. method:: FormWizard.get_template +.. method:: WizardView.process_step_files(form) - Returns the name of the template that should be used for the given step. + This method gives you a way to post-process the form files before the + files gets stored within the storage backend. By default it just passed + the ``form.files`` dictionary. You should not manipulate the data here + but you can use the data to do some extra work if needed (e.g. set storage + extra data). - By default, this returns :file:`'forms/wizard.html'`, regardless of step. + Default implementation:: - Example:: + def process_step_files(self, form): + return self.get_form_step_files(form) - def get_template(self, step): - return 'myapp/wizard_%s.html' % step +.. method:: WizardView.render_revalidation_failure(step, form, **kwargs) - 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:: + When the wizard thinks, all steps passed it revalidates all forms with the + data from the backend storage. - def get_template(self, step): - return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html'] + If any of the forms don't validate correctly, this method gets called. + This method expects two arguments, ``step`` and ``form``. -.. method:: FormWizard.render_template + The default implementation resets the current step to the first failing + form and redirects the user to the invalid form. - Renders the template for the given step, returning an - :class:`~django.http.HttpResponse` object. + Default implementation:: - 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. + def render_revalidation_failure(self, step, form, **kwargs): + self.storage.current_step = step + return self.render(form, **kwargs) - The template will be rendered with the context documented in the - "Creating templates for the forms" section above. +.. method:: WizardView.get_form_step_data(form) -.. method:: FormWizard.process_step + This method fetches the form data from and returns the dictionary. You + can use this method to manipulate the values before the data gets stored + in the storage backend. - 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. + Default implementation:: - 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. + def get_form_step_data(self, form): + return form.data - Note that this method is called every time a page is rendered for *all* - submitted steps. +.. method:: WizardView.get_form_step_files(form) - The function signature:: + This method returns the form files. You can use this method to manipulate + the files before the data gets stored in the storage backend. + + Default implementation:: + + def get_form_step_files(self, form): + return form.files + +.. method:: WizardView.render(form, **kwargs) + + This method gets called after the get or post request was handled. You can + hook in this method to, e.g. change the type of http response. + + Default implementation:: - def process_step(self, request, form, step): - # ... + def render(self, form=None, **kwargs): + form = form or self.get_form() + context = self.get_context_data(form, **kwargs) + return self.render_to_response(context) Providing initial data for the forms ==================================== -.. attribute:: FormWizard.initial +.. attribute:: WizardView.initial_dict Initial data for a wizard's :class:`~django.forms.Form` objects can be - provided using the optional :attr:`~FormWizard.initial` keyword argument. - This argument should be a dictionary mapping a step to a dictionary - containing the initial data for that step. The dictionary of initial data + provided using the optional :attr:`~Wizard.initial_dict` keyword argument. + This argument should be a dictionary mapping the steps to dictionaries + containing the initial data for each step. The dictionary of initial data will be passed along to the constructor of the step's :class:`~django.forms.Form`:: - >>> from testapp.forms import ContactForm1, ContactForm2, ContactWizard + >>> from myapp.forms import ContactForm1, ContactForm2 + >>> from myapp.views import ContactWizard >>> initial = { - ... 0: {'subject': 'Hello', 'sender': 'user@example.com'}, - ... 1: {'message': 'Hi there!'} + ... '0': {'subject': 'Hello', 'sender': 'user@example.com'}, + ... '1': {'message': 'Hi there!'} ... } - >>> wiz = ContactWizard([ContactForm1, ContactForm2], initial=initial) - >>> form1 = wiz.get_form(0) - >>> form2 = wiz.get_form(1) + >>> wiz = ContactWizard.as_view([ContactForm1, ContactForm2], initial_dict=initial) + >>> form1 = wiz.get_form('0') + >>> form2 = wiz.get_form('1') >>> form1.initial {'sender': 'user@example.com', 'subject': 'Hello'} >>> form2.initial {'message': 'Hi there!'} + + The ``initial_dict`` can also take a list of dictionaries for a specific + step if the step is a ``FormSet``. + +.. _wizard-files: + +Handling files +============== + +To handle :class:`~django.forms.FileField` within any step form of the wizard, +you have to add a :attr:`file_storage` to your :class:`WizardView` subclass. + +This storage will temporarilyy store the uploaded files for the wizard. The +:attr:`file_storage` attribute should be a +:class:`~django.core.files.storage.Storage` subclass. + +.. warning:: + + Please remember to take care of removing old files as the + :class:`WizardView` won't remove any files, whether the wizard gets + finished corretly or not. + +Conditionally view/skip specific steps +====================================== + +.. attribute:: WizardView.condition_dict + +The :meth:`~WizardView.as_view` accepts a ``condition_dict`` argument. You can pass a +dictionary of boolean values or callables. The key should match the steps +name (e.g. '0', '1'). + +If the value of a specific step is callable it will be called with the +:class:`WizardView` instance as the only argument. If the return value is true, +the step's form will be used. + +This example provides a contact form including a condition. The condition is +used to show a message from only if a checkbox in the first step was checked. + +The steps are defined in a ``forms.py``:: + + from django import forms + + class ContactForm1(forms.Form): + subject = forms.CharField(max_length=100) + sender = forms.EmailField() + leave_message = forms.BooleanField(required=False) + + class ContactForm2(forms.Form): + message = forms.CharField(widget=forms.Textarea) + +We define our wizard in a ``views.py``:: + + from django.shortcuts import render_to_response + from django.contrib.formtools.wizard.views import SessionWizardView + + def show_message_form_condition(wizard): + # try to get the cleaned data of step 1 + cleaned_data = wizard.get_cleaned_data_for_step('0') or {} + # check if the field ``leave_message`` was checked. + return cleaned_data.get('leave_message', True) + + class ContactWizard(SessionWizardView): + + def done(self, form_list, **kwargs): + return render_to_response('done.html', { + 'form_data': [form.cleaned_data for form in form_list], + }) + +We need to add the ``ContactWizard`` to our ``urls.py`` file:: + + from django.conf.urls.defaults import pattern + + from myapp.forms import ContactForm1, ContactForm2 + from myapp.views import ContactWizard, show_message_form_condition + + contact_forms = [ContactForm1, ContactForm2] + + urlpatterns = patterns('', + (r'^contact/$', ContactWizard.as_view(contact_forms, + condition_dict={'1': show_message_form_condition} + )), + ) + +As you can see, we defined a ``show_message_form_condition`` next to our +:class:`WizardView` subclass and added a ``condition_dict`` argument to the +:meth:`~WizardView.as_view` method. The key refers to the second wizard step +(because of the zero based step index). + +How to work with ModelForm and ModelFormSet +=========================================== + +The WizardView supports :class:`~django.forms.ModelForm` and +:class:`~django.forms.ModelFormSet`. Additionally to the ``initial_dict``, +the :meth:`~WizardView.as_view` method takes a ``instance_dict`` argument +with a list of instances for the ``ModelForm`` and ``ModelFormSet``. + +Usage of NamedUrlWizardView +=========================== + +.. class:: NamedUrlWizardView + +There is a :class:`WizardView` subclass which adds named-urls support to the wizard. +By doing this, you can have single urls for every step. + +To use the named urls, you have to change the ``urls.py``. + +Below you will see an example of a contact wizard with two steps, step 1 with +"contactdata" as its name and step 2 with "leavemessage" as its name. + +Additionally you have to pass two more arguments to the +:meth:`~WizardView.as_view` method: + + * ``url_name`` -- the name of the url (as provided in the urls.py) + * ``done_step_name`` -- the name in the url for the done step + +Example code for the changed ``urls.py`` file:: + + from django.conf.urls.defaults import url, patterns + + from myapp.forms import ContactForm1, ContactForm2 + from myapp.views import ContactWizard + + named_contact_forms = ( + ('contactdata', ContactForm1), + ('leavemessage', ContactForm2), + ) + + contact_wizard = ContactWizard.as_view(named_contact_forms, + url_name='contact_step', done_step_name='finished') + + urlpatterns = patterns('', + url(r'^contact/(?P<step>.+)/$', contact_wizard, name='contact_step'), + url(r'^contact/$', contact_wizard, name='contact'), + ) diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 7fdf0d7e1c..2a2090a9b1 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -55,6 +55,22 @@ signing in Web applications. See :doc:`cryptographic signing </topics/signing>` docs for more information. +New form wizard +~~~~~~~~~~~~~~~ + +The previously shipped ``FormWizard`` of the formtools contrib app has been +replaced with a new implementation that is based on the class based views +introduced in Django 1.3. It features a pluggable storage API and doesn't +require the wizard to pass around hidden fields for every previous step. + +Django 1.4 ships with a session based storage backend and a cookie based +storage backend. The latter uses the tools for +:doc:`cryptographic signing </topics/signing>` also introduced in +Django 1.4 to store the wizard state in the user's cookies. + +See the :doc:`form wizard </ref/contrib/formtools/form-wizard>` docs for +more information. + Simple clickjacking protection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
