django-formtools
django-formtools is a collection of high-level abstractions designed to simplify complex form use cases in Django applications. It primarily offers utilities for form previews and multi-step form wizards. Originally a part of Django's core (`django.contrib.formtools`), it was extracted into a standalone package in Django 1.8 to facilitate independent maintenance and trim the framework's core. The library is actively maintained by the Jazzband community, with releases typically following new Django versions to ensure compatibility.
Warnings
- breaking The `django-formtools` package was moved from `django.contrib.formtools` to its own standalone package `formtools` in Django 1.8. All import paths changed accordingly.
- gotcha You must add `'formtools'` to your `INSTALLED_APPS` setting in `settings.py` for its templates (like `wizard_form.html`) and internationalization (translations) to be discovered and used correctly.
- gotcha `FormPreview` does not support file uploads. If your forms contain `FileField`s, you should implement the preview logic manually or use `WizardView` which supports file handling.
- breaking In `django-formtools` 2.4.1, support for Python 3.6 and Django versions older than 3.2 was dropped.
- gotcha By default, `SessionWizardView` and `CookieWizardView` manage state across steps, but they do not inherently provide mechanisms to restore a user's progress if they leave the site and return later. Additionally, `CookieWizardView` now restarts the wizard from the first step if an invalid cookie is detected, instead of raising a `SuspiciousOperation` error.
Install
-
pip install django-formtools
Imports
- SessionWizardView
from formtools.wizard.views import SessionWizardView
- CookieWizardView
from formtools.wizard.views import CookieWizardView
- NamedUrlWizardView
from formtools.wizard.views import NamedUrlWizardView
- FormPreview
from formtools.preview import FormPreview
Quickstart
import os
from django import forms
from django.shortcuts import render
from django.urls import path
from formtools.wizard.views import SessionWizardView
# forms.py (example)
class ContactForm1(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
class ContactForm2(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
# views.py (example)
FORMS = [("step1", ContactForm1), ("step2", ContactForm2)]
TEMPLATES = {"step1": "wizard_form.html", "step2": "wizard_form.html"}
class ContactWizard(SessionWizardView):
def get_template_names(self):
# Dynamically selects template based on current step
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
# Process the cleaned data from all forms after the wizard is complete
cleaned_data = [form.cleaned_data for form in form_list]
return render(self.request, 'done.html', {'form_data': cleaned_data})
# urls.py (example)
# urlpatterns = [
# path('contact/', ContactWizard.as_view(FORMS)),
# ]
# To make this runnable as a standalone quickstart for illustration (requires a Django project setup)
# In a real Django project, you'd add 'formtools' to INSTALLED_APPS and set up templates.
# For testing, you can manually run the view logic (not a full server setup).
# Example of what your templates/wizard_form.html might look like:
# <h1>Step {{ wizard.steps.current }} of {{ wizard.steps.total }}</h1>
# <form action="" method="post">{% csrf_token %}
# {{ wizard.management_form }}
# {{ wizard.form.as_p }}
# <input type="submit" value="{% if wizard.steps.next %}Next{% else %}Submit{% endif %}">
# </form>
# Example of what your templates/done.html might look like:
# <h1>Wizard Complete!</h1>
# <p>Form Data:</p>
# <ul>
# {% for form_data in form_data %}
# <li>{{ form_data }}</li>
# {% endfor %}
# </ul>