Multi-page Forms in Django
Introduction
Most online forms fit on a single page. Think of a "join our forum" or "contact us" form into which the user enters a name, email address, and maybe a few other pieces of information. If you're building this kind of functionality into a Django site, you will probably want to take advantage of Django's built-in form classes. These are especially handy when dealing with model forms, where the form fields correspond to the fields on a model that will be saved in your database.
But what if you need a form that spans more than one page? Like a multipage job application where your personal details are on page 1, your education is on page 2, and so on? We have an app to help you to add such a form to your website. You can download it here:
https://github.com/ImaginaryLandscape/django-multipage-form
The app’s README should explain everything needed to use the app in your own project, but if you’re interested in a walkthrough, we have also developed an example project that demonstrates all the functions of the multipage form app. That project can be downloaded here:
https://github.com/ImaginaryLandscape/multipage-form-demo
Feel free to clone the repo, get the demo up and running, and use or modify it to your own purposes. Or just follow along here in the post. We’ll go step-by-step through the creation of the same multipage job application form used in the example project.
Now let's jump in!
Requirements
- Python 3
- Django 3.2
- A basic knowledge of how Django works
The Model
We'll be working with a model form, so the first thing we need is a model that will represent a submitted job application. The following snippet is from the example project:
job_application/models.py
- from django.db import models
- from multipage_form.models import MultipageModel
- class JobApplication(MultipageModel):
- # stage 1 fields
- first_name = models.CharField(max_length=20, blank=True)
- middle_name = models.CharField(max_length=20, blank=True)
- last_name = models.CharField(max_length=20, blank=True)
- # stage 2 fields
- education_level = models.CharField(max_length=20, blank=True)
- year_graduated = models.CharField(max_length=4, blank=True)
- # stage 3 fields
- prior_company = models.CharField(max_length=20, blank=True)
- prior_experience = models.TextField(blank=True)
- anything_more = models.BooleanField(default=False)
- # stage 3b fields
- personal_statement = models.TextField(blank=True)
- # stage 4 fields
- all_is_accurate = models.BooleanField(default=False)
The attributes defined on this model are simply the fields that correspond to user-submitted values. Note that there is no real difference on the model between a “stage 1” field and a “stage 2” field. These groupings are indicated in comments merely to give an idea of the example form’s structure. (The “stage 3b” designation will be addressed below.)
Note also that an argument of “blank=True” has been passed to all of the text-based fields. With the multipage form, it's important to declare your model fields with “blank=True” for string values or “null=True” / “default=False” for other values. This is because unlike with a typical, single-page form, the values on the model must be saved one chunk at a time, as the user progresses through the different form pages.
This is true even for fields for which the user should be required to provide a response. Therefore, if you want a given field to be required, it should be done by making the field required at the form level instead of at the database level. We’ll talk more about that below.
The Form
This is where the real action is. Let’s start with a simplified version of the “forms.py” module from the example project:
job_application/forms.py
- from multipage_form.forms import MultipageForm, ChildForm
- from .models import JobApplication
- class JobApplicationForm(MultipageForm):
- model = JobApplication
- starting_form = "Stage1Form"
- class Stage1Form(ChildForm):
- next_form_class = "Stage2Form"
- class Meta:
- fields = ["first_name", "middle_name", "last_name"]
- class Stage2Form(ChildForm):
- class Meta:
- fields = ["education_level", "year_graduated"]
- help_text = {
- "education_level": "Indicate the highest level of education you have attained"
- }
Notice that our form inherits from the “MultipageForm” class, and that it contains multiple nested classes that inherit from the “ChildForm” class. The outer class specifies the model that we’ll be populating and the name of one of its own nested child classes as the starting point of the multipage form. The outer class must define these “model” and “starting_form” attributes.
As for the child forms, they are subclasses of Django’s regular “ModelForm” class. As such, they have a nested “class Meta” that is used to define which fields should be rendered in the form; set help text, labels, or widgets; or do anything else the regular Django “class Meta” can do.
But there are some special attributes that can be defined on the child forms directly (not in the “class Meta”). The most important of these for a multipage form is the “next_form_class” attribute, which -- naturally -- contains the name of the next “ChildForm” subclass that will be presented to the user if the current form is submitted with valid inputs. (The last form in the sequence should not define a “next_form_class” attribute.)
We mentioned above that our model fields had to be declared with blank=True for string fields and null=True for other fields. A consequence of this is that with a multipage form, a form field cannot be required at the database level.
Back to Top