Part IV · Templates and forms
Chapter 14: Forms
Django’s forms turn the strings a browser submits into checked Python values, and values back into HTML inputs. A form class declares its fields; each instance copies them and, bound to submitted data, cleans each value through the field’s widget, the field and its validators before the form’s own checks run. Formsets repeat one form over many rows, and a model form makes its fields from a model’s and writes what it cleaned back onto an instance.
When a browser submits a form, the request’s body is a flat set of strings keyed by the names of the inputs: "ship=++heron+&tonnage=0&arrives=2026-10-20". The view that receives it wants a ship’s name as a string with its stray spaces gone, a tonnage as a whole number above nothing, and an arrival as a date; or else, for each value that will not do, a message to show beside its input, with what was typed still in it. The first time the page is shown, the same inputs are wanted empty, or filled with values the view already has. The forms library is the one declaration that serves all of this: what each input is, how its string becomes a value, what the value must satisfy, and how the input is drawn again.
A form never reads a request. A view hands it POST and FILES from the request (The body: body, POST and FILES) and gets back values, errors and HTML. A form has the template engines draw its HTML, and a model form has the model validate and save its instance.
The objects
A form is made of fields, and each field of a widget; around them stand the objects that join a field to one form, check its value and draw it.
django.forms. Field here is Field; a model’s field is a different class, which makes one of these.| object | what it is | made |
|---|---|---|
Form |
what it is a class of fields, declared as class attributes; an instance holds its own copy of them, the submitted data, the initial values and the errors | made the class once; an instance for each request |
Field |
what it is one value: how its string becomes a Python value, and what that value must satisfy; it holds a widget, a label, an initial value, messages and validators | made with its form class, and copied for each instance |
Widget |
what it is the HTML of a field’s input, or inputs, and how its value is read back out of the submitted data | made with its field, and copied with it |
BoundField |
what it is a field joined to one form: its name in the data, the value to show, its errors and its label | made when the form is first asked for it by name |
| a validator | what it is a callable that raises ValidationError when a value will not do |
made with its module or its field class, and shared; or with the field, from the field’s arguments |
ErrorDict, ErrorList |
what it is a form’s errors, by field name, which draw themselves as HTML | made when the form is validated |
| a renderer | what it is draws forms, fields, widgets and errors from templates | made at its first use, and kept; or given to a form |
BaseFormSet |
what it is many forms of one class, counted by a management form | made a class from formset_factory; an instance for each request |
ModelForm |
what it is a form whose fields are made from a model’s, and which fills, validates and saves an instance | made the class once, from its Meta |
A class of fields, and an instance bound to data
A form is declared as a class whose attributes are fields. The berth office of a port might take requests on this one:
# harbour/fleet/forms.py
from django import forms
class BerthForm(forms.Form):
ship = forms.CharField(max_length=60)
tonnage = forms.IntegerField(min_value=1)
arrives = forms.DateField()
def clean_ship(self):
return self.cleaned_data["ship"].title()
The class’s metaclass, DeclarativeFieldsMetaclass, takes every attribute that is a field out of the class body before the class is made, so that BerthForm has no attribute named ship, and collects them, after those of its bases, in a dictionary, BaseForm (Declaring a form: DeclarativeFieldsMetaclass and BaseForm). Each instance then deep-copies that dictionary into a fields of its own, each field with its widget, so that one form can change its fields without changing them for every other; a comment at the copy says so.
A form is bound when it is made with data or files, even an empty dictionary, and unbound otherwise. An unbound form is only drawn, with its initial values; it is never valid, and validating it finds no errors.
A form is asked for one of its fields by name, form["tonnage"], and answers with a bound field, a BoundField that joins the field to this form; it is made the first time it is asked for and kept. A field holds nothing about any form. The bound field knows the input’s name in the data ("tonnage", or "berth-tonnage" for a form made with prefix="berth"), the value to show in it, its errors and its label (BoundField: a field in a form).
Validation: from strings to values
Validation runs the first time something needs its result. BaseForm.errors calls BaseForm if the errors are not yet known, and BaseForm asks a bound form for them; once made, they are kept.
cleaned_data for the form’s own method for that field; an error at any step is kept under the field’s name and the field is left out of cleaned_data. Then come the checks of the whole form.BaseForm takes the fields in their order. For each that is not disabled, the bound field asks the field’s widget for the value. Most widgets look their name up in the data; a checkbox that was not ticked is not in the data at all and reads as False, and a widget made of several inputs reads several names (Widgets: Widget and its subclasses). Then Field.clean runs three steps. Field.to_python, as a field class overrides it, turns the string into a value of the field’s type, or raises an error, usually with the code "invalid". Field.validate makes the checks built into the field, which in the base class is only that a required field is not empty. Field calls every validator of the field on a value that is not empty, and gathers all their errors. If the form has a method whose name is "clean_" and the field’s name, it runs next, and what it returns replaces the cleaned value. A value that passes goes into cleaned_data; a ValidationError from any step goes into the form’s errors under the field’s name, by way of BaseForm, and the field is left out of cleaned_data.
Then the form’s own BaseForm.clean runs, for checks that need more than one field. An error it raises is kept under "__all__", the constant NON_FIELD_ERRORS, as belonging to no one field, unless it was made from a dictionary of field names; a dictionary it returns replaces cleaned_data. Last comes BaseForm, which does nothing on a plain form and is where a model form fills its instance and has it validated (Validation: full_clean, cleaned_data and the errors).
Given the submission above, the form answers so:
>>> form = BerthForm({"ship": " heron ", "tonnage": "0", "arrives": "2026-10-20"})
>>> form.is_valid()
False
>>> form.errors.as_data()
{'tonnage': [ValidationError(['Ensure this value is greater than or equal to 1.'])]}
>>> form.cleaned_data
{'ship': 'Heron', 'arrives': datetime.date(2026, 10, 20)}
The ship’s name was stripped of its spaces by CharField and given its capital by the form’s method for that field. The tonnage was turned into the number 0 by IntegerField and refused by the MinValueValidator that min_value=1 gave the field. The date was parsed by DateField. The message is the validator’s own, because the field’s error_messages has none for its code, "min_value"; a field’s message for a code replaces a validator’s (Validators: django/).
Drawing: from values to HTML
A form, a bound field, a widget and a list of errors each draw themselves from a template. str(form) calls RenderableMixin, which has the form’s renderer render a template with a context that the form makes, and marks the result safe. Unless a form is given a renderer, or its class names one in BaseForm, it uses the one that the setting FORM_RENDERER names, made at its first use and kept. By default that is django, which keeps a template engine of its own, apart from those of TEMPLATES, that finds templates in django/ and in the installed applications.
The templates nest (Rendering a form: renderers and templates). The form’s template, "django/ by default, writes the errors that belong to no field, with those of the hidden fields among them, and then a <div> for each visible field, with the hidden fields inside the last. Each field is drawn, by default, by "django/, which writes its label, its help text, its errors and its widget. The widget is drawn by a template of its own, "django/ for a number input, which includes "django/, the template that writes an <input> element.
What a widget shows is where the two halves of the system meet. An unbound form shows each field’s initial value; a bound one ordinarily shows the data that was submitted, as it was typed, and not the cleaned value, though a disabled field and a file field show their initial value still. After the submission above, the ship’s input still holds " heron ", spaces and all, and the tonnage’s is drawn as:
<input type="number" name="tonnage" value="0" min="1" required aria-invalid="true" aria-describedby="id_tonnage_error" id="id_tonnage">
The "min" came from the field, which put it on its widget when the field was made. The bound field added the rest as it drew: "required" because the field is required, and "aria-invalid" and "aria-describedby" because it has errors; then the "id", from the form’s auto_id, "id_%s" unless the form is given another.
Formsets and model forms
A formset is a set of forms of one class on one page, as when the berths of several ships are asked for at once (Formsets: BaseFormSet and the management form). formset_factory(BerthForm, extra=2) makes a class, named BerthFormSet, whose instances make their forms with the prefixes "form-0", "form-1" and so on, so that every input on the page has a name of its own: "form-0-ship". How many forms were submitted is submitted too, in the hidden inputs of a management form, "form-TOTAL_FORMS" and "form-INITIAL_FORMS" among them. A bound formset makes as many forms as that count says, but never more than a limit, BaseFormSet, which a comment calls protection against a client that would have the server make any number of forms. A formset is valid when each of its forms is valid, those marked for deletion aside, and it has no errors of its own: a management form missing or invalid, too many or too few forms, or what its clean raised.
A model form is a form whose fields are made from a model’s (Model forms: ModelForm and fields_for_model):
# harbour/fleet/forms.py
from harbour.fleet.models import Ship
class ShipForm(forms.ModelForm):
class Meta:
model = Ship
fields = ["name", "tonnage", "home"]
Its metaclass, ModelFormMetaclass, reads Meta, refuses one that names a model but neither fields nor exclude, and asks each model field it is to use for a form field, ordinarily by calling the model field’s formfield, which makes the field’s blank into required, its verbose_name into the label and its default into the initial value. For ShipForm the name becomes a CharField, the tonnage an IntegerField, and the home port, a foreign key, a ModelChoiceField drawn as a Select whose options are the ports in the database (Choices: ChoiceField, ModelChoiceField and the choice widgets).
A model form made with an instance takes its initial values from the instance’s fields. Its BaseModelForm copies the cleaned values onto the instance and has the instance validate itself, leaving out what the form has already found wrong, as Validating an instance tells. BaseModelForm saves the instance and then its many-to-many values; with commit=False it returns the instance unsaved and gives the form a method, BaseModelForm, to call when the instance has been saved. Model formsets and inline formsets put the two ideas together: a model form for each object of a queryset, or for each object whose foreign key points at one parent (Model formsets and inline formsets).
Where forms meet the rest of Django
A form gets the request’s POST and FILES, parsed on first use (Multipart parsing and upload handlers). The generic views FormView, CreateView and UpdateView run the cycle of showing a form, checking it and redirecting, and ModelFormMixin makes its form class with modelform_factory when it is given none (The form views). A form’s HTML is drawn by the template engines (Template engines: TEMPLATES and the backends) and comes out as a safe string, which a template prints without escaping again (Autoescaping and safe strings). A model field makes its own form field (A field: the base class), and a file field takes what a form’s file field cleaned (File fields). A ModelChoiceField draws its options from a queryset, evaluated anew each time they are drawn (QuerySets). The messages of fields and validators are translated, and numbers and dates can be read and written in the reader’s locale (Internationalization and time zones). The admin edits an object with a model form and inline formsets (The admin), the authentication forms are forms like any other (The authentication forms: AuthenticationForm and the password forms), django adds fields and widgets of its own (PostgreSQL fields: ArrayField, the range fields and hstore), and the test framework checks a form’s errors with SimpleTestCase (Assertions on a response: assertContains, assertRedirects and assertTemplateUsed). The token against cross-site request forgery is not a form field: a template tag writes it and a middleware checks it (Security).