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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
INSTALLED_APPS = (
'formtools',
)
INSTALLED_APPS = ( 'formtools', )
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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)
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)
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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],
})
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], })
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{% 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 %}
{% 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 %}
{% 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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def get(self, request, *args, **kwargs):
try:
return self.render(self.get_form())
except KeyError:
return super().get(request, *args, **kwargs)
def get(self, request, *args, **kwargs): try: return self.render(self.get_form()) except KeyError: return super().get(request, *args, **kwargs)
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.