14.2 A form field: the base class
The base class of every form field, Field (Field), turns one submitted value into a Python value in Field.clean, through Field.to_python, Field.validate and Field. It also holds an input’s widget, label, initial value, messages and validators, and tells a form what to draw and whether a value changed.
A field that belongs to a form class is made once, with the class, and each form of that class works on a deep copy of it (Declaring a form: DeclarativeFieldsMetaclass and BaseForm). A field holds nothing of any one submission: no data, no errors, not even its name, which is the form’s key for it. It holds what was decided where it was declared: which widget draws it, how a string becomes a value, what the value must satisfy, and what to say when it does not. The rest is on the bound field (BoundField: a field in a form).
What a field class declares
Field keeps its defaults in class attributes, for subclasses to override. Field.widget is the widget class a field is drawn with by default, TextInput, and Field the one for a hidden input, HiddenInput. Field is empty, and Field has a message for the one code the base class raises, "required". Field are the values that count as empty, a list made from EMPTY_VALUES: None, the empty string, and the empty list, tuple and dictionary, but not 0 or False. Field is None: Field makes the bound field of the class the field names, by argument or attribute, else of the form’s, else a BoundField.
Making a field
Field.__init__ takes only keyword arguments, and stores most of them for others to use: the label, help text, label suffix and template for the bound field, the initial value for the form, which prefers one it was given itself. A comment adds that initial is no fallback: a bound form whose data lacks the field cleans what the widget reads from that data, None for most widgets.
The widget is settled next. The widget argument, or the class’s, is made an instance if it is a class and deep-copied if it is one already, so a widget given to two fields becomes two. localize=True sets Widget, which Widget reads to write the value in the locale’s format. Widget is set from required; the HTML attribute "required" is the bound field’s work, and the flag is for widgets that draw an optional field differently, as ClearableFileInput offers its box for clearing the file only then. Last, Field is given the widget, and what it returns is merged into Widget.attrs: the hook by which a field turns its arguments into HTML attributes.
Then the messages: one dictionary is updated with the default_error_messages of the field’s class and its bases, the most general first, and then with the error_messages argument. A subclass declares only the codes it adds or rewords, and the caller’s message for a code beats every class’s. The validators become a new list, the class’s default_validators and then the validators argument, so a field can append to its own without touching the list its class shares.
Cleaning a value
Field.clean makes three calls: Field.to_python with the value, then Field.validate and Field with what that returned, which clean returns. Each step is a method of its own, so a field class overrides the one it needs.
to_python converts what the widget read, usually a string, into the field’s type, and raises an error, usually with the code "invalid", for what will not convert; in Field it returns the value as it came. validate holds the checks built into the field, and in Field there is one: a value among empty_values is refused with the code "required" if the field is required. It is given the converted value, so to a field that strips spaces a string of spaces is empty.
run_validators returns at once for an empty value, so no validator of the field sees None or an empty string, and an empty value is judged by required alone. Otherwise it calls every validator and does not stop at the first failure: it gathers all their errors and raises them as one ValidationError made from the list. Where a validator raises a single error, not a list, and the field’s error_messages has a message for its code, that message replaces the validator’s, and the validator’s parameters stay with it.
>>> tonnage = forms.IntegerField(
... min_value=1, error_messages={"min_value": "Ships of %(limit_value)s ton and more."}
... )
>>> list(tonnage.error_messages)
['required', 'invalid', 'min_value']
>>> tonnage.clean("0")
Traceback (most recent call last):
...
django.core.exceptions.ValidationError: ['Ships of 1 ton and more.']
The codes came from Field, from IntegerField and from the argument, in that order. The MinValueValidator that min_value=1 gave the field raised its own message with the code "min_value"; the field’s replaced it, and the validator’s limit filled it in (Validators: django/). A ValidationError holds one message with a code and parameters, a list of errors, or a dictionary of lists by name; Validating an instance describes all three, and a model field’s clean, which takes the same steps.
The form’s call, and a disabled field
A form does not call the field’s clean itself. BaseForm calls Field with the bound field, and the method cleans the bound field’s data, what the widget read from the submission, or, if the field is disabled, its initial value. The rest is Validation: full_clean, cleaned_data and the errors. FileField overrides the method to pass the initial value as well, so a form sent with no new file keeps the old one (The built-in form fields).
disabled=True acts in four places that agree. _clean_bound_field cleans the initial value, Field.bound_data gives it to the widget, Field answers that nothing changed, and the bound field writes "disabled" on the input. Together they set aside what a client sends for the field in cleaning, drawing and the test for change, but for the hidden initial input of show_hidden_initial=True, which the bound field still reads back.
>>> class BerthChangeForm(BerthForm):
... ship = forms.CharField(max_length=60, disabled=True)
...
>>> form = BerthChangeForm(
... {"ship": "beagle", "tonnage": "366", "arrives": "2026-10-20"},
... initial={"ship": "endeavour", "tonnage": 366},
... )
>>> form.is_valid(), form.cleaned_data["ship"], form.changed_data
(True, 'Endeavour', ['arrives'])
The submitted name was passed over, and the initial one cleaned and given its capital by the form’s method; the tonnage converts to the 366 it started with, so it has not changed.
What a bound field shows, and what changed
BoundField.value, the value an input is drawn with, is the initial value in an unbound form and, in a bound one, what Field.bound_data makes of the data and the initial value; either way Field has it last. In Field, bound_data returns the data, or for a disabled field the initial value, and prepare_value returns what it is given. FileField always returns the initial value, the file already stored.
Field tells whether one field’s data differs from its initial value, and BaseForm asks it of every field. A disabled field has not changed. The data is converted with to_python, and data that will not convert has changed. A field with a method _coerce, as TypedChoiceField has, compares the two coerced; any other takes None as the empty string on either side, so an empty input matches an initial None. The initial value is not converted: to an integer field whose initial value is the string "366", a submitted "366" is a change. With show_hidden_initial=True the bound field also draws the initial value in a hidden input, and compares the data with what that input sends back. Formsets, model formsets and empty_permitted=True act on the answers (Validation: full_clean, cleaned_data and the errors).
Writing a field class
CharField shows how a field class is written. Its __init__ stores its own arguments, max_length, min_length, strip and empty_value, before it calls the base’s __init__ with the rest: the base calls widget_attrs, and CharField reads the lengths to add "maxlength" and "minlength" unless the widget is hidden. After the base’s __init__ it appends to the field’s validators a MinLengthValidator and a MaxLengthValidator for the lengths given, and always a ProhibitNullCharactersValidator, after the class’s and the caller’s.
CharField makes any value that is not empty a string and strips white space from its ends, unless the field was made with strip=False. A value that is empty, or has become so, is returned as empty_value, the empty string unless the field is given another. A model’s CharField with null=True gives it None, except on a backend that stores the empty string as NULL.