Install & Compatibility
Where this runs
tested against v2.7 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.866s · 67.7MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 3.6s · import 0.754s · 68MB
67MB installed
● package 67MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
SessionWizardView
✓ from formtools.wizard.views import SessionWizardView
✗ from django.contrib.formtools.wizard.views import WizardView
The `formtools` package was moved out of `django.contrib` in Django 1.8. Old import paths are no longer valid.
CookieWizardView
✓ from formtools.wizard.views import CookieWizardView
✗ from django.contrib.formtools.wizard.views import CookieWizardView
The `formtools` package was moved out of `django.contrib` in Django 1.8. Old import paths are no longer valid.
NamedUrlWizardView
✓ from formtools.wizard.views import NamedUrlWizardView
✗ from django.contrib.formtools.wizard.views import NamedUrlWizardView
The `formtools` package was moved out of `django.contrib` in Django 1.8. Old import paths are no longer valid.
FormPreview
✓ from formtools.preview import FormPreview
✗ from django.contrib.formtools.preview import FormPreview
The `formtools` package was moved out of `django.contrib` in Django 1.8. Old import paths are no longer valid.
To use `django-formtools`, define your form classes, then create a `WizardView` subclass (e.g., `SessionWizardView`) that specifies the forms and implements a `done` method to handle the final submission. The view needs to be hooked into your `urls.py`. Remember to add `'formtools'` to your `INSTALLED_APPS` in `settings.py` for templates and translations to work correctly.
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>
Debug
Known issues
breakingThe `django-formtools` package was moved from `django.contrib.formtools` to its own standalone package `formtools` in Django 1.8. All import paths changed accordingly.fixUpdate import statements from `from django.contrib.formtools...` to `from formtools...`.
affects: Django 1.8 and higher, django-formtools 1.0 and higher
gotchaYou 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.fixAdd `'formtools'` to your `INSTALLED_APPS` list.
affects: All versions
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.fixAvoid using `FormPreview` with forms that include `FileField`. For wizards, refer to the 'Handling files' section in the `WizardView` documentation.
affects: All versions
breakingIn `django-formtools` 2.4.1, support for Python 3.6 and Django versions older than 3.2 was dropped.fixEnsure your project uses Python >= 3.7 (preferably >= 3.8) and Django >= 3.2.
affects: django-formtools 2.4.1 and higher
gotchaBy 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.fixFor persistent session data across user visits, override the `get()` method in your `SessionWizardView` subclass to load saved data. Be aware of the `CookieWizardView`'s new behavior regarding invalid cookies.
affects: `SessionWizardView` all versions; `CookieWizardView` 2.4 and higher for new cookie behavior.
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'formtools'
The `formtools` application was moved from `django.contrib.formtools` to a standalone package `django-formtools` in Django 1.8. This error occurs if you are using an older import path or have not installed the standalone `django-formtools` package.
fixFirst, ensure `django-formtools` is installed: `pip install django-formtools`. Then, update all import statements from `from django.contrib.formtools...` to `from formtools...` in your code.
SuspiciousOperation: ManagementForm data is missing or has been tampered with.
This error typically occurs in a `SessionWizardView` if the hidden `{{ wizard.management_form }}` field is missing from one of your wizard's templates, or if the form's management data is somehow altered or corrupted between steps.
fixEnsure that `{{ wizard.management_form }}` is present within the `<form>` tags of all your wizard step templates. Also, avoid manually manipulating hidden fields that `django-formtools` uses to manage state. Django formtools 'done' method is not getting called on form submission
The `done()` method in a `SessionWizardView` subclass is only executed after the *final* step of the multi-step form wizard has been successfully submitted and all forms have passed validation. If an intermediate step's form validation fails, or if the wizard's state is somehow reset, the `done()` method will not be reached.
fixVerify that all forms in the wizard (from the first to the last step) are validating correctly. If forms are failing validation, the wizard will re-render the current step with errors instead of proceeding to `done()`. Use the `process_step()` method if you need to perform actions after each successful step.
TemplateDoesNotExist at /my-wizard-url/ formtools/wizard/wizard_form.html
Django cannot locate the default templates provided by `django-formtools` for rendering the wizard or preview pages. This often happens if `'formtools'` is not added to your `INSTALLED_APPS` setting in `settings.py`, or if your template loaders are not configured to discover app templates.
fixAdd `'formtools'` to your `INSTALLED_APPS` list in your `settings.py` file. Ensure your `TEMPLATES` setting includes the `django.template.loaders.app_directories.Loader` or explicitly lists the `formtools/templates` directory in the `DIRS` option.
Upgrade
Version history
2.7latest on PyPI · released Jul 9, 2026
Audit
Dependencies
DjangorequiredCore framework dependency, requires Django>=4.2 for recent versions.