Skip to content

How to do a Wizard Form with Django

how-to-do-a-wizard-form-with-django

Many times, we have a form with a lot of fields and we need to use it, but we don’t want users to get bored filling the form or that they stop at middle of the form and they end up leaving the website. To avoid these situations, you can divide your form in as many steps as you want. Now, we are going to check what we need to do with the forms.

First, we need to configure our project. In this case, we are going to use Django and a library called django-formtools.

First of all, we need to install django-formtools:

pip install django-formtools

Later, we need to  add 'formtools' to our INSTALLED_APPS setting:

INSTALLED_APPS = (
    'formtools',
)

Then, we will continue with the creation of the forms. Each step must have a form. If you want 3 steps, you will have to create 3 forms. In this case we only want two steps, so we’re going to create two forms:

from django import forms

class FormStepOne(forms.Form):
    name = forms.CharField(max_length=100)
    last_name = forms.CharField(max_length=100)
    phone = forms. CharField(max_length=100)
    email = forms.EmailField()

class FormStepTwo(forms.Form):
    job = forms.CharField(max_length=100)
    salary = forms.CharField(max_length=100)
    job_description = forms.CharField(widget=forms.Textarea)

Now, we are going to add the view for our forms. We can use SessionWizardView or CookieWizardView classes. Those classes are designed to save the information in each step (as their names indicate, server-side sessions and browser cookies respectively), in this case we’re going to use a SessionWizardView  because we want to store the form’s information  server-side:

from django.shortcuts import render
from .forms import FormStepOne, FormStepTwo
from formtools.wizard.views import SessionWizardView

class FormWizardView(SessionWizardView):
    template_name = "path/to/template"
    form_list = [FormStepOne, FormStepTwo]

    def done(self, form_list, **kwargs):
        return render(self.request, 'done.html', {
            'form_data': [form.cleaned_data for form in form_list],
        })

SessionWizardView needs to have a done() method. This method is called when the user fills all forms and all the fields are valid. In this method, you can process all information and save it. Finally, we need a template for rendering our forms:

{% extends "base.html" %}
{% load i18n %}

{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">
  {% csrf_token %}
  <table>
  {{ wizard.management_form }}
  {% if wizard.form.forms %}
    {{ wizard.form.management_form }}
    {% for form in wizard.form.forms %}
      {{ form }}
    {% endfor %}
  {% else %}
    {{ wizard.form }}
  {% endif %}
  </table>
  {% if wizard.steps.prev %}
  <button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
  <button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
  {% endif %}
  <input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}

Everything looks great, right? but, what happens if our user did leave the website before the form has been finished? Well, there are many fixes for this issue. One of them is that you may process each step after the user submits it. You can do it the method process_step() or you can store the information in sessions. How to do it? Well, by default SessionWizardView stores information in sessions but, how can you restore that information when the user access the form again? It’s possible, but you need to overwrite the get() method.

def get(self, request, *args, **kwargs):
    try:
        return self.render(self.get_form())
    except KeyError:
        return super().get(request, *args, **kwargs)

 

With that method you can restore all the information for your forms when the user access again to continue filling them but you need to define if the user wants to restore his information or if he/she wants fill a new form. You will need to edit that code if you want to support this option. Another thing that you need to define is: how long do you want that information to be stored? Well you can set lifetime with SESSION_COOKIE_AGE. By default SESSION_COOKIE_AGE is set at 1209600 (2 weeks, in seconds), but in the case that you don’t want that information to be stored for 2 weeks, you can change that variable with another value that you prefer.

In conclusion, django-formtools helps us to structure a form that can be long and tedious into a simple form with steps, increasing our user’s engagement. If you want to learn more about it you can go to official documentation, here.