skip to content

Form Processing

django.forms declares fields and widgets, cleans bound data into cleaned_data, backs ModelForm and renders via templates. Interviewers ask where validation belongs: form, model or serializer.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

27

In Django forms, what is the difference between a bound and an unbound form, and when does cleaned_data exist?

level: juniorimportance: must knowfreq 72%

answer

  1. what the constructor received
  2. the is_bound attribute
  3. initial is display-only
  4. is_valid() triggers full_clean()
  5. empty POST and the or-None idiom

basics

~20 s

A Django form is bound when it is built with data= or files= (even an empty dict) and unbound otherwise. Only a bound form validates; cleaned_data appears once is_valid() or errors runs full_clean() on it.

solid answer

~40 s

Django sets `form.is_bound` to True when `data` or `files` is not `None`, so `SignupForm(request.POST)` is bound while `SignupForm()` and `SignupForm(initial={...})` are unbound. An unbound form only renders, pre-filled from `initial`; `is_valid()` on it returns False without validating. On a bound form, the first call to `is_valid()` or `form.errors` runs `full_clean()`, which builds `cleaned_data`: each field's input coerced to a Python value such as a `date`, a `bool` or a stripped string. A field that fails validation is left out of `cleaned_data` and reported in `errors`, so you read `cleaned_data` only after `is_valid()` returns True. For ordinary enabled fields `initial` is not a fallback: a field missing from the submission is treated as empty.

code

python · 15 lines
python
from django import forms

class NewsletterSignupForm(forms.Form):
    email = forms.EmailField()
    first_name = forms.CharField(required=False, max_length=50)

unbound = NewsletterSignupForm(initial={'email': '[email protected]'})
unbound.is_bound      # False
unbound.is_valid()    # False, nothing was validated
# unbound.cleaned_data -> AttributeError

bound = NewsletterSignupForm(data={'email': ' [email protected] '})
bound.is_bound        # True
bound.is_valid()      # True
bound.cleaned_data    # {'email': '[email protected]', 'first_name': ''}

go deeper

for a junior

Recall the rule: data= or files= binds the form, initial= does not. Say that cleaned_data exists only after is_valid() or errors has run.

for a middle

Explain full_clean(): the widget pulls raw input, the field coerces it, failing fields drop out of cleaned_data into errors, and unbound forms stop before any of it.

for a senior

Show you know the traps in real views: request.POST or None on an empty POST, re-reading request.POST instead of cleaned_data, and assuming initial fills gaps.

for a principal

Frame binding as the boundary between untrusted input and trusted values, and argue for team conventions that only ever persist cleaned_data.

## What binding means A Django `Form` instance is in one of two states, decided entirely by its constructor arguments: - **Bound**: `data` or `files` is not `None`. Django sets `form.is_bound = True` and treats `data` as the user's submission. - **Unbound**: both are `None`. The form can only be rendered, empty or pre-filled from `initial`. The check is a literal `is not None` test. `SignupForm(data={})` is therefore **bound** (an empty submission that will fail every required field), while `SignupForm(initial={...})` is **unbound** however much initial data it carries. | Call | `is_bound` | What `is_valid()` does | |---|---|---| | `SignupForm()` | False | returns False, runs nothing | | `SignupForm(initial={'email': '[email protected]'})` | False | returns False, runs nothing | | `SignupForm(data={})` | True | validates; required fields fail | | `SignupForm(request.POST, request.FILES)` | True | validates the submission | ## When cleaned_data appears `cleaned_data` is not an attribute of a freshly built form. It is created by `full_clean()`, which Django runs lazily the first time you call `is_valid()` or read `form.errors`: 1. `full_clean()` creates an empty error dictionary. For an unbound form it stops there, so an unbound form never gains `cleaned_data` and reading it raises `AttributeError`. 2. For a bound form it creates `cleaned_data = {}` and cleans each field: the widget extracts the raw value from `data` or `files`, the field coerces it into a Python object, and the result is stored under the field's name. 3. Form-wide cleaning and an internal hook that `ModelForm` uses run afterwards (their order is the validation topic's material). What ends up in `cleaned_data` is **typed, normalised data**: a `datetime.date` for a `DateField`, a `bool` for a `BooleanField`, a whitespace-stripped string for a `CharField` (`strip=True` is the default). A field that raised a `ValidationError` is **not** in `cleaned_data`; its message sits in `form.errors`. Hence the working rule: read `cleaned_data` only after `is_valid()` returned True, and never go back to `request.POST` for a value the form has already cleaned. ## data versus initial The two dictionaries look alike and do different jobs: - **`data`** is what the user submitted. It is validated and becomes `cleaned_data`. - **`initial`** is what an unbound form displays. It is never validated. You can pass it to the form or declare it on the field; the form-level dictionary wins, and a callable such as `datetime.date.today` is called each time it is needed. - In a bound form, an ordinary enabled field that is missing from `data` is treated as **empty**, not filled from `initial`. A `CharField(required=False, initial='weekly')` absent from the submission cleans to `''`. - `initial` still matters to a bound form in narrow places: `has_changed()` and `changed_data` compare the submission against it, a `disabled=True` field cleans its initial value instead of submitted data, and a `FileField` with no new upload keeps its initial file. ## The request.POST or None idiom Many function views write `form = SignupForm(request.POST or None)` to share one code path for GET and POST. It works because an empty `QueryDict` is falsy, so a GET builds an unbound form. The edge is a POST whose body carries no fields at all: it also builds an unbound form, and the view silently re-renders without errors instead of reporting the required fields. In a normal template form the CSRF token keeps the POST non-empty, but branching on `request.method == 'POST'` states the intent without relying on truthiness. ## A newsletter sign-up example ```python from django import forms class NewsletterSignupForm(forms.Form): email = forms.EmailField() first_name = forms.CharField(required=False, max_length=50) f = NewsletterSignupForm(data={'email': ' [email protected] '}) f.is_valid() # True f.cleaned_data # {'email': '[email protected]', 'first_name': ''} ``` The address arrives padded with spaces and leaves stripped; the missing optional name cleans to the `CharField`'s empty value, the empty string. ## What interviewers listen for - Binding is decided by `data` and `files`, not by the HTTP method and not by `initial`. - `is_valid()` on an unbound form returns False without raising. - `cleaned_data` holds coerced Python values, only for the fields that passed. - `initial` is display data, not a default for missing input, apart from disabled fields and kept files.

  • In a Django view, why can SignupForm(request.POST or None) misbehave on a POST request?
    An empty `QueryDict` is falsy, so a POST that carries no fields produces an unbound form. `is_valid()` then returns False without errors and the view re-renders silently. The CSRF token usually keeps template POSTs non-empty, but checking `request.method == 'POST'` avoids the ambiguity entirely.
  • In Django, does a bound form that failed validation still have a cleaned_data attribute?
    Yes. After `full_clean()` runs, `cleaned_data` exists on any bound form, but it holds only the fields that passed; each failing field was removed and its message placed in `form.errors`. It is useful for diagnostics, not for saving.
  • When both a Django Form's initial= dict and a field's initial= give a value, which one is displayed?
    The form-level `initial` dictionary wins; the field's `initial` is only used when the form's dictionary has no entry for that name. Either may be a callable, which Django calls when the value is needed, so `datetime.date.today` stays current.

saying these in an interview costs you the question

  • Passing initial= to a Django form makes it bound and validates it.
  • An empty dict passed as data leaves the form unbound.
  • cleaned_data holds the raw strings exactly as they arrived in request.POST.
  • A field missing from submitted data falls back to its initial value.
  • Calling is_valid() on an unbound form raises an exception.
open as a page

In Django, what does a ModelForm build from its model, and why must its Meta declare fields or exclude?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A Django ModelForm generates a form field for each selected editable model field, mapping type, max_length, blank, choices, labels and help text, and its save() writes the instance. Meta must name fields or exclude, or class creation raises ImproperlyConfigured.

open as a page

In a Django template, what does {{ form }} output by default, and how do you render one field's label, errors and help text yourself?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Since Django 5.0, {{ form }} renders django/forms/div.html: top-level errors, then one <div> per visible field holding its label, help text, errors and widget. By hand, use each BoundField's label_tag, help_text, errors and the field itself.

open as a page

In Django forms, how do Field and Widget responsibilities differ, and how would you give a sign-up form's date field a native date picker?

level: middleimportance: must knowfreq 56%

basics

~20 s

A Django Field coerces and validates input into a Python value; its Widget renders the HTML and extracts the raw value. For a native picker use DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), because browsers accept only ISO dates.

open as a page

With Django's inlineformset_factory, how do you save an order and its line items from one POST so every line references the order?

level: middleimportance: must knowfreq 52%

basics

~20 s

Bind a ModelForm for the order and an inline formset with instance=order to request.POST, validate both, save the order, set formset.instance to it and call formset.save(), which stamps each new line with the order's key.

open as a page

In Django, why does a submitted formset report 'ManagementForm data is missing or has been tampered with', and how do you fix it?

level: middleimportance: must knowfreq 55%

basics

~20 s

A bound Django formset reads its form count from hidden <prefix>-TOTAL_FORMS and <prefix>-INITIAL_FORMS fields. The error means they were not posted under the expected prefix: render {{ formset.management_form }} and use the same prefix on GET and POST.

open as a page

In a Django ModelForm, what does save(commit=False) return, and why must you call save_m2m() afterwards?

level: middleimportance: must knowfreq 66%

basics

~10 s

Django's ModelForm.save(commit=False) returns the model instance populated from cleaned_data but not saved. After setting extra fields and calling instance.save(), you call form.save_m2m(), because many-to-many rows need the saved instance's primary key.

open as a page

In Django forms, in what order does is_valid() run to_python(), validate(), validators, clean_<field>() and clean()?

level: middleimportance: must knowfreq 72%

basics

~10 s

Django's is_valid() runs full_clean(): for each field in declared order, Field.clean() calls to_python(), validate() and run_validators(), then clean_<field>() if that passed. After every field, the form's clean() runs, even if fields failed.

open as a page

In Django forms, what does a field's required flag actually check, and why does a BooleanField reject an unticked checkbox?

level: juniorimportance: should knowfreq 46%

basics

~20 s

A Django field with required=True (the default) rejects a cleaned value that is empty: None, '', or an empty list, tuple or dict. An unticked checkbox cleans to False, and a required BooleanField treats False as missing.

open as a page

In Django, what is a formset, and how do formset_factory's extra, min_num and max_num decide how many forms it renders?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A Django formset is a class, built by formset_factory, that repeats one form on a page and validates the copies together. Unbound, it renders max(initial, min_num) + extra forms, trimmed to max_num unless the initial rows alone exceed it.

open as a page

In Django forms, where do validation errors end up, and how do form.errors, non_field_errors() and errors.as_json() differ?

level: juniorimportance: should knowfreq 48%

basics

~10 s

Django stores form errors in form.errors, a dict keyed by field name plus 'all' for form-wide errors. non_field_errors() returns that 'all' list, and errors.as_json() serialises every error as message and code.

open as a page

Why does a Django form's FileField report 'This field is required.' even though the user chose a file to upload?

level: middleimportance: should knowfreq 50%

basics

~20 s

A Django FileField's widget reads only from the form's files dict, so the form must be built with files=request.FILES, and the HTML form must use enctype="multipart/form-data"; otherwise request.FILES is empty and the field sees no file.

open as a page

In a Django formset, where do you put validation that spans several forms, and how do its errors reach the template?

level: middleimportance: should knowfreq 38%

basics

~20 s

Cross-form rules go in clean() on a BaseFormSet subclass passed to the factory with formset=. It runs after every form is cleaned; a ValidationError raised there lands in formset.non_form_errors(), which the template must render explicitly.

open as a page

When editing an adoption application with a Django ModelForm, why must both the GET and the POST branches pass instance=?

level: middleimportance: should knowfreq 50%

basics

~20 s

In a Django ModelForm, instance= supplies the initial values on GET and tells save() which row to update on POST. A POST form built without instance= starts from a new object, so save() inserts a duplicate row instead of updating.

open as a page

In a Django ModelForm, what validation runs after the form's clean(), and why must an overridden clean() call super().clean()?

level: middleimportance: should knowfreq 44%

basics

~20 s

After a Django ModelForm's clean(), _post_clean() copies cleaned data onto the instance, runs the model's field validation and clean(), then validate_unique() and validate_constraints(). Those last two only run if ModelForm.clean() ran, so overrides must call super().clean().

open as a page

In Django 5.0+, what does BoundField.as_field_group() render, and how do you restyle every form field on a site with one template?

level: middleimportance: should knowfreq 35%

basics

~10 s

as_field_group() renders one field's label, help text, errors and widget through a field template, django/forms/field.html by default. To restyle every field, point a custom FORM_RENDERER's field_template_name at your own template.

open as a page

In Django, what does a form or widget's inner Media class declare, and how does {{ form.media }} combine and order those assets?

level: middleimportance: should knowfreq 30%

basics

~20 s

An inner Media class lists the CSS, by medium, and JavaScript a widget or form needs. form.media merges all widgets' assets plus the form's own, dropping duplicates but keeping order; {{ form.media }} renders the tags.

open as a page

Why does a Django project's override of django/forms/field.html in its template DIRS get ignored, and what does the FORM_RENDERER setting change?

level: middleimportance: should knowfreq 30%

basics

~20 s

Django's default form renderer, DjangoTemplates, uses its own engine that loads the built-in form templates and apps' templates folders, not the project's TEMPLATES DIRS. Set FORM_RENDERER to TemplatesSetting, or a subclass, to make overrides work.

open as a page

In a Django booking form, how do you enforce that check-out follows check-in, and why use cleaned_data.get() in clean()?

level: middleimportance: should knowfreq 62%

basics

~10 s

Put a Django cross-field rule in the form's clean(): read both dates with cleaned_data.get(), compare only when both exist, and call add_error('check_out', ...). A field that failed validation is absent, so indexing raises KeyError.

open as a page

In Django form validation, why should a ValidationError carry a code and params rather than a pre-formatted message?

level: middleimportance: should knowfreq 40%

basics

~10 s

A Django ValidationError's code lets tests, views and error_messages overrides identify the error without parsing text, and params let the message be translated and reworded with placeholders filled in at render time.

open as a page

In a Django form, why is a field with disabled=True safer than a readonly widget attribute for a value subscribers must not change?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A Django field with disabled=True ignores whatever is submitted and cleans the form's initial value, so a tampered POST cannot change it. A readonly attribute is only a browser hint; the submitted value is validated and used.

open as a page

With Django's modelformset_factory, how do you stop a bulk-edit page from changing rows outside the user's scope or creating rows it never offered?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Pass a user-scoped queryset to the model formset on both GET and POST, because without one it covers every row of the model; and use edit_only=True, since extra=0 or max_num does not stop a client posting new rows.

open as a page

Why is fields = '__all__' on a Django ModelForm for a public pet-adoption application a mass-assignment risk?

level: seniorimportance: should knowfreq 55%

basics

~20 s

With fields = 'all' a Django ModelForm accepts and saves every editable field, including staff-only ones like status, and any column added later. Hiding inputs in the template does not help; a crafted POST sets them.

open as a page

Why can a Django ModelForm pass is_valid() yet saving raise IntegrityError when the view sets applicant after save(commit=False)?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A Django ModelForm excludes fields that are not on the form from model validation, and skips any unique check or UniqueConstraint that involves one. A constraint on (applicant, pet) is never checked, so the duplicate reaches the database.

open as a page

In a Django form, how do you decide whether a rule belongs in a validator, a custom Field, clean_<field>() or clean()?

level: seniorimportance: should knowfreq 36%

basics

~10 s

In Django, put reusable single-value checks in validators, type conversion in a custom Field's to_python(), rules needing this form's context in clean_<field>(), and rules spanning fields in the form's clean().

open as a page

In Django forms, how do you set a ChoiceField's choices per request, and why does cleaned_data return strings rather than integers?

level: middleimportance: nice to knowfreq 32%

basics

~10 s

Set Django ChoiceField choices per request by passing a callable or assigning self.fields['name'].choices in the form's init, never on the class. ChoiceField cleans to strings; use TypedChoiceField with coerce=int for integers.

open as a page

When a team writes a custom Django field template, what must it keep so each input stays linked to its label, help text and errors?

level: seniorimportance: nice to knowfreq 16%

basics

~10 s

Keep rendering the widget with {{ field }}, labels with label_tag or legend_tag, errors with {{ field.errors }}, and give help text the id <auto_id>_helptext: Django's aria-describedby on the input points at those ids.

open as a page