Open-source project
jazzband/django-formtools avatar
jazzband/django-formtools

django-formtools: previews and multi-step forms, extracted from Django's core

A set of high-level abstractions for Django forms

881 stars147 forksPythonBSD-3-Clause

At a glance

What is it?
django-formtools is a Jazzband package of high-level abstractions for Django forms, currently covering form previews and multi-step forms, and it exists because this code used to live in django.contrib.formtools before it was split out in Django 1.8. It is a small, well tested, BSD licensed library with a tox matrix over Python and Django versions.
Who is it for?
Adopt django-formtools if your Django project needs a multi-step wizard or a form preview and you would rather use the abstractions Django itself shipped than write them again. Do not adopt it for simple forms, since a plain Django Form still does that job with nothing to install, and do not expect new features to arrive quickly, because the package is maintained but slow moving.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • jazzband/django-formtools881 ★ · -2
Star history
Repository2026-10-212026-11-07
jazzband/django-formtools883881

-2 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The problem: wizards and previews that the framework core should not carry

Django's formtools is a set of high-level abstractions for Django forms, currently for form previews and multi-step forms. Both of those are the kind of feature that every framework accumulates: a wizard that keeps partial answers across several screens, and a preview that lets a user see exactly what will be submitted before they submit it.

The history explains why the code lives in its own package. This code used to live in Django proper, in django.contrib.formtools, but was separated into a standalone package in Django 1.8 to keep the framework's core clean.

That is the entire argument for the project, and it is a good one. A wizard and a preview are not something the core of a web framework needs to maintain forever, but they are something a great many applications need, and the arrangement lets the implementation evolve on its own schedule rather than on the framework's. The package is a Jazzband project, which is the collective governance model for Django ecosystem packages that have outgrown a single maintainer's availability.

The audience is Django developers with a form that spans several pages or that needs a confirmation step, and the documentation site at django-formtools.readthedocs.io carries the full list of available formtools.

The licence is BSD-3-Clause, the language is Python, the default branch is master, and the last push was on 2026-10-19. The repository has no GitHub releases, so the version history is on the package index rather than in a release feed.

The abstractions: a preview and a multi-step form

Two abstractions, and it is worth being precise about what each one does, because the names overlap in the popular imagination.

A form preview is the simpler of the two. The user fills in a form, is shown what will be sent, and then confirms. The point is to let a person catch a mistake before the write happens rather than after it, which matters most in the places where the submitted value is expensive or awkward to undo.

A multi-step form is the wizard. Instead of one long form, the answers are collected over several screens and the state is carried between them. That solves a different problem, because a long form is both unpleasant to fill in and fragile when it comes to validation: with one page you validate everything at the end, and the user is told about the first error and nothing about the rest. With a wizard you validate a step at a time, and the state has to survive a page load, which is the part that needs an abstraction.

That last point is the one that explains the package's value. A wizard is not hard to sketch and awkward to get right, because the state, the navigation and the validation all have to agree, and the failure shows up as a user who cannot go back a step without losing their answers.

The package is deliberately small, and the documentation says so: for a full list of available formtools, see the documentation site. It is also installable from the Python Package Index, so adopting it is a dependency line rather than a vendored copy of Django's old contrib module, which is the practical difference between this and the code that used to live in the framework.

The packaging: a Jazzband project with a version taken from git

The project metadata is worth reading closely, because it says what the package has committed to.

The name is django-formtools, the description is a set of high-level abstractions for Django forms, and the licence is BSD, with the authors recorded as the Django Software Foundation. The dependency list is a single line, Django>=5.2, and the development status is recorded as 5, Production/Stable, with framework classifiers for Django 5.2, 6.0 and 6.1.

The version is dynamic. The build backend is setuptools with setuptools_scm, and the project metadata marks the version as dynamic, which means it is derived from the git tags at build time rather than written into the file. That is a deliberate choice for a package with no release feed: the version on the package index reflects a tag, and the tag is the source of truth.

There is a small inconsistency worth noticing while you are in there. The requires-python field says 3.11 or newer, while the classifier list includes Python 3.10 as well as 3.11 through 3.14. A project that a reviewer will read, because the whole point of the metadata is to be reviewed, and the mismatch is the kind of thing that catches a stale classifier rather than a broken install.

The build itself is conventional: setuptools with include-package-data, the tests excluded from the package discovery, namespaces turned off, and the isort configuration naming formtools as the first party package, so imports within the library are sorted as local code rather than as third party.

The test matrix: tox across Python and Django versions

The running of the tests is described as simple, and the recipe is the familiar one: install Tox and run it in the root of a git clone.

bash
    $ git clone https://github.com/jazzband/django-formtools
    [..]
    $ cd django-formtools
    $ tox

What tox does is the interesting part. The previous command will run the tests in different combinations of Python, if available, and Django versions, and the full list of combinations is available with the -l option:

bash
    $ tox -l

The listed names encode the environment, for example py312-djangoAB and py312-djangomain, where the letters stand for the Django release channel. A single environment can be run with the -e option:

bash
    $ tox -e py312-djangoAB  # runs the tests only on Python 3.12 and Django A.B.x

That is the right shape for a library whose whole job is to keep working across a range of Django releases, and it is the same shape Django itself uses. For a contributor it answers the question you actually have when you are about to change something: which combinations do I have to run.

The practical caveat is the phrase if available. The matrix runs on the Python versions installed on your machine, so a laptop with one interpreter will exercise one column of the matrix, and a full sweep is a CI job. The repository has a GitHub Actions workflow and a codecov badge, so the full matrix does run somewhere, but running the local subset is what you do day to day.

The Makefile: linting, deprecation warnings treated as errors, and translations

The Makefile is where the project's quality rules are written down, and one line in it is worth more attention than the rest.

The test target runs ruff check, then isort in check-only mode against formtools and tests, and then the Django test suite under coverage with the deprecation warnings promoted to errors:

bash
	@ python -W error::DeprecationWarning -W error::PendingDeprecationWarning -m coverage run `which django-admin` test tests

Turning DeprecationWarning and PendingDeprecationWarning into errors is what keeps a library that supports several Django versions honest. A deprecated Django API used in the library produces a warning in your application, and it usually appears months later in someone else's traceback. Failing your own test run on that warning is the cheapest possible fix.

The rest of the Makefile is the translation workflow, and it is the same shape as Django's own: makemessages to extract strings, compilemessages to compile them, and a combined target that pulls the translations first. The pull step uses Transifex with a minimum percentage of 1, and the extraction runs from inside the formtools package directory. The docs target builds the documentation with Sphinx in the docs directory, and the clean target uses git clean -Xfd, which removes the ignored files the translations and the build leave behind.

The environment setup at the top of the file is the other detail: the Django settings module is set to tests.settings and PYTHONPATH is set to the current directory, so the test suite runs against the package in the checkout rather than against an installed copy.

Licence, maintenance and where django-formtools is the wrong tool

The licence is BSD, recorded in the project metadata with the licence file at the root of the repository, and the package is a Jazzband project with a code of conduct and a contributing guide in the repository.

The maintenance picture is steady rather than busy. The repository is not archived and the last push was on 2026-10-19, the CI workflow and the coverage badge are both wired up, and the project metadata declares a production status. What it does not have is GitHub releases, so the version history is on the package index, and a reader looking for a changelog in the repository will not find one. The CHANGELOG lives with the package, and the repository carries a Makefile, a tox.ini, a pre-commit configuration, a codespell configuration and a Transifex configuration in the .tx/ directory.

Where django-formtools is the wrong tool is mostly about scope. A single-page form does not need a wizard, and adding the package to a project for one form is a dependency for nothing. A form with dynamic server-side behaviour, where the fields present depend on what the user chose earlier, is a different story, and the project description does not claim to cover that case.

There is also the question of whether the abstractions are still the shape you want. The package dates from the era when Django forms were the only way to build a form, and the previews and wizards were designed around a server-rendered, session-based flow. If your application is a single page application with client-side validation and a JSON API, the multi-step form in this package is solving a problem your frontend already solved, and you would be bending it to fit.

The judgement is that for a server-rendered Django project with a wizard or a confirmation step, this package is the maintained continuation of a piece of Django that many projects still depend on. For anything else, the standard form and your own view are likely to be less work.

Editorial conclusion

Adopt django-formtools if your Django project needs a multi-step wizard or a form preview and you would rather use the abstractions Django itself shipped than write them again. Do not adopt it for simple forms, since a plain Django Form still does that job with nothing to install, and do not expect new features to arrive quickly, because the package is maintained but slow moving. Before you add it, check the compatibility line in the project metadata against your Django version, since the package depends on Django 5.2 or newer, and run the test matrix locally with tox -l to see which combinations are covered.

Frequently asked questions

What does django-formtools provide?

It is a set of high-level abstractions for Django forms, currently for form previews and multi-step forms. The code used to live in Django proper in django.contrib.formtools and was separated into this standalone package in Django 1.8 to keep the framework's core clean.

How do I run the django-formtools tests?

Clone the repository, change into the directory and run tox. It runs the tests in different combinations of Python, if available, and Django versions, and tox -l lists the full set of combinations while tox -e runs a single environment such as py312-djangoAB.

Which Django and Python versions does django-formtools support?

The package depends on Django>=5.2, and the classifiers list Django 5.2, 6.0 and 6.1 with Python 3.10 through 3.14, while requires-python is set to 3.11 or newer. It also carries a Development Status of 5, Production/Stable.

How are translations handled in django-formtools?

The Makefile has the same three step workflow Django uses: maketranslations extracts strings with django-admin makemessages, pulltranslations pulls them from Transifex with a minimum percentage of 1, and compiletranslations compiles them, with a combined translations target that runs all three.

What licence is django-formtools released under, and how is it maintained?

BSD, recorded in the project metadata with the licence file in the repository root, and it is a Jazzband project with a code of conduct and contributing guide. The repository is not archived and the last push was on 2026-10-19, with CI and coverage badges, and the version is derived from git tags by setuptools_scm rather than written into the metadata.

Official sources

  1. Issues
  2. jazzband/django-formtools on GitHub
  3. License: BSD-3-Clause
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jazzband-django-formtools.svg)](https://hysenlabs.com/projects/jazzband-django-formtools)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/jazzband-django-formtools.svg)](https://hysenlabs.com/projects/jazzband-django-formtools)

Was this useful?