Django comes with an optional “form wizard” application that allows you to split forms across multiple web pages in a sequential order. This ability is provided by using the FormWizard class. You would use this when you have, for example, a long registration process that needs to be split up in small digestable chunks, making it easier on your users to complete the process.
You can see the FormWizard documentation at: http://docs.djangoproject.com/en/dev/ref/contrib/formtools/form-wizard/
In a word, yes. A few things the FormWizard does that may not work for your projects, as it did not for ours. First, the FormWizard using an HTTP POST to process a form. This makes it tough when you are trying to use the browser’s back button to change some data on a previous step. The FormWizard checks for any GET requests and moves you to the first step in the wizard process, YUCK! Secondly, the FormWizard docs recommends using your wizard subclass as the callable in a urlconf in your urls.py. This is a really nice feature except that it will only create one copy of your FormWizard for all requests. This works well until you start messing with the hooks it provides to inserting or removing steps based on data from a form submission. Once you insert or remove a form, the steps are now changed for any subsequent users.
- The SessionWizard is given a list of Step objects instead of a list of Django Form classes.
- SessionWizard stores all of its state in the Django Session object. This allows you to use the SessionWizard in the urlconf and keep state seperate by user (or session). When the SessionWizard starts it makes a copy of the Step list for the session so it can be manipulated independantly of any other session.
- The SessionWizard processes all GET requests as a form view and only moves to the next step in the sequence on a succesful POST request. This allows for the browser’s Back button to function correctly.
- Each Step in the sequence has a unique slug for that step. This slug is used in the urlconf to be able to go to any part of the wizard. This allows you to provide proper “Back” and “Next” buttons on your forms.
Here is the basic workflow needed to use the SessionWizard object:
- Make sure you have enabled the Django session middleware.
- Create a subclass the SessionWizard class and override the done() method. The done() method allows you to collect all of the validated form data, process that data and move on to the next web page after successful processing of the wizard. You are able to redirect out of done if there are some post processing errors you need the user to be notified of. If you have processed everything correctly then you can call the clear() method to clean up the data stored in the session. If clear() is not called then the next time the same session goes through the wizard the existing form data from the original run will be put into the forms.
- Override the get_template() method to return the path to the template the forms should use. The default is to return “forms/wizard.html”, which you provide. Based on the step passed in you could return different templates for different forms.
- Create a url that will be the entry point of your wizard. This url should provide a (?P<slug>[A-Za-z0-9_-]+) option in the url pattern.
- Point this url to the subclass of SessionWizard, providing a list of Step objects that the wizard should process in the order it should process them.
- Sit back and enjoy form wizard goodness!
- The user makes a GET request to your wizard url with the first slug of the sequence.
- The wizard returns the form using the template you specify.
- The user submits the form using a POST request.
- The wizard validates the form data. If the data is invalid it returns the user to the current form and you can display to the user any errors that have occured. If the data is valid then the wizard stores the clean data in its state object.
- If there is another step in the process the wizard sends a redirect to the user to the next step in the sequence. If not next step is found the wizard then calls the done() method, which expects to return some HttpResponse to the user letting them know they are finished with the process.
You’ll need to create a template that renders the step’s form. By default, every form uses a template called forms/wizard.html. (You can change this template name by overriding get_template())
The template recieves the following context:
- current_step – The current Step being processed
- form– The current form for the current step (with any data already available)
- previous_step – The previous Step or None
- next_step – The next Step or None
- url_base – The base URL that can be used in creating links to the next for previous steps
- extra_context – Any extra context you have provided using overriding the process_show_form() method
There are couple of hooks in the SessionWizard that allow you to modify the execution of the wizard in interesting ways. For more in depth information make sure to check out the API docs for SessionWizard.
- process_show_form() – allows you to provide any extra context data that needs to be provided to the template for processing
- process_step() – allows for changing the internal state of the wizard. For example, you could use this hook to add or remove steps in the process based off some user submitted information. You can use the methods remove_step(), insert_before() and insert_after() to accomplish this.
- get_template() – allows you to return a template path to use for processing the currently executing step.
- render_form() – allows you the ability to render the form however you see fit. The default is to use the render_to_response Django shortcut; but, you could use this hook to provide a PageAssembly render method from the excellent django-crunchyfrog project found at : http://github.com/localbase/django-crunchyfrog
- initialize() – allows you the ability to initialize the wizard at each request. This can be used to put data into the wizard state object that can then be used in the done() method.
When you have a long form process and the user decides they don’t want to finish the wizard you would to provide a Cancel button or link they can click that will reset the wizard and redirect the user to a different screen. It would be great if the SessionWizard provided a way to handle this and also clean up the data it has been tracking as well. Well pine no more because the SessionWizard has got your back!
When you want to cancel a wizard you can just pass “cancel” as the step slug in the url. By just doing this the wizard will, by default, clear the session data it was tracking and send an HttpResponseRedirect to the / url. You can provide the query string parameter ?rd=yoururl to redirect to a different url. If you have a Step with the slug of “cancel” then the wizard will proceed to this step and you will have to handle the cancel action yourself.
For example, let’s say we have a wizard and url /mywizard and we have steps “form1” and “form2”.
- The user sends a GET request to /mywizard/form1.
- The user fills out the form information and clicks the Next button.
- The browser sends a POST request with the form data and the wizard does its tricks and redirects the user to /mywizard/form2.
- The user is sleepy and decides to come back tomorrow and finish the wizard. The user then clicks the cancel link you have provided in the template.
- The cancel link in your template points to /mywizard/cancel?rd=/thanks.
- The browser sends a GET request to /mywizard/cancel?rd=/thanks and the SessionWizard sees it has no step called “cancel”.
- The SessionWizard calls its internal cancel method, which cleans up any session and form data the wizard was tracking, and redirects the user to /thanks!
- No harm, no foul.
- cancel() – cleans up the session data that has been tracked by the wizard. You can override this method and provide other features you would like when cancelling, for example; You could track the cancel actions from wizards.
We are always looking for updates to make SessionWizard even better and provide even more form wizards to this tool chest. If you have any questions, comments or suggestions please email us at development@localbase.com. You can always particapte by using the projects GitHub account as well: http://github.com/localbase/django-merlin