In a Django ModelForm, what validation runs after the form's clean(), and why must an overridden clean() call super().clean()?
answer
- form first, then the model
- values copied onto the instance
- model clean() before uniqueness
- flags set by the parent clean
- errors merged into the form
basics
~20 sAfter 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().
solid answer
~50 sA `ModelForm` validates in two stages. First the ordinary form stage: field cleaning, `clean_<field>()` and the form's `clean()`. Then the internal `_post_clean()` hook: `construct_instance()` copies `cleaned_data` onto `self.instance`, the instance's `full_clean()` runs model field validation and the model's `clean()` for the fields on the form, and finally `validate_unique()` and `validate_constraints()` run. Those two are gated by flags that `ModelForm.clean()` sets, so an override that forgets `super().clean()` silently turns off uniqueness and constraint checks while everything else still works. Model errors are merged into the form: errors keyed by a form field land on that field, unkeyed ones become non-field errors, a key naming a field that is not on the form raises `ValueError`, and `Meta.error_messages` can replace messages by code. The instance is modified in memory even if validation fails, so it should not be reused.
code
python · 16 linesfrom django import forms
from django.core.exceptions import ValidationError
from .models import AdoptionApplication
class AdoptionApplicationForm(forms.ModelForm):
class Meta:
model = AdoptionApplication
fields = ['pet', 'home_type', 'has_other_pets', 'message']
def clean(self):
cleaned = super().clean() # sets the flags for validate_unique/validate_constraints
if cleaned.get('home_type') == 'flat' and not cleaned.get('message'):
self.add_error('message', ValidationError(
'Tell us how a flat suits this pet.', code='flat_needs_message'))
return cleanedgo deeper
Recall that a ModelForm also runs the model's validation, and that an overridden clean() must call super().clean().
Explain _post_clean's order: construct_instance, full_clean with the model's clean(), then uniqueness and constraints, and how model errors map to fields.
Show you catch clean() overrides without super(), avoid reusing a mutated instance, and supply server-owned values before model rules need them.
Decide which rules live on the model so every form and API shares them, and which belong to a particular form's workflow.
## Two validation stages Calling `is_valid()` on a `ModelForm` runs the normal form pipeline and then validates the model instance: 1. **Form stage**: each field's own cleaning, any `clean_<fieldname>()` methods, then the form's `clean()`. 2. **Model stage**, inside the internal `_post_clean()` hook: 1. `construct_instance()` copies the cleaned values of the form's fields onto `self.instance`. 2. `self.instance.full_clean(exclude=..., validate_unique=False, validate_constraints=False)` runs the model's field validation and the model's `clean()`. 3. `self.validate_unique()` runs the model's uniqueness checks, if enabled. 4. `self.validate_constraints()` runs `Meta.constraints` checks, if enabled. The `exclude` set keeps model validation away from fields that are not on the form or already failed. ## Why super().clean() matters Steps 3 and 4 are gated. `BaseModelForm.__init__` sets two private flags to False, and `ModelForm.clean()`, the parent implementation, sets them to True. Django's documentation warns about exactly this: override `clean()` without calling the parent, and uniqueness and constraint validation are skipped. | Your `clean()` | Field and model `clean()` validation | Unique and constraint checks | |---|---|---| | not overridden | run | run | | overridden, calls `super().clean()` | run | run | | overridden, no `super().clean()` | run | **skipped** | The failure is silent: the form validates, and a duplicate adoption application only surfaces as an `IntegrityError` at save time, or not at all if no database constraint backs it. ## How model errors reach the form The model stage raises `ValidationError`s that the form absorbs: - An error keyed by a field that is **on the form** is attached to that field. - An error from the model's `clean()` that is not tied to a field becomes a **non-field error**. - An error the model's `clean()` ties to a field that is **not on the form** cannot be attached anywhere, and Django raises `ValueError`. The model documentation suggests overriding `Model.clean_fields()`, which receives the excluded field names, for such rules. - If the form's `Meta.error_messages` or the field's `error_messages` has an entry for the error's `code`, such as `unique`, that text replaces the model's message. This lets one model-level rule, for example "applicants with other pets must describe them", show up on the right form field without duplicating the rule in every form. ## The instance is modified during validation Because `construct_instance()` runs before model validation, the instance passed with `instance=` carries the new values **in memory** after `is_valid()`, even when validation fails. Date strings have become `date` objects, choices have been applied, and so on. The documentation warns that a failed validation may leave the instance in an inconsistent state, so do not reuse it for display or further logic; re-fetch it if needed. Nothing is written to the database until `save()`. ## Ordering consequences worth knowing - The model's `clean()` runs **after** the form's `clean()`, so the form cannot see the model's errors while it runs. - The model's `clean()` runs **before** uniqueness and constraint checks. - Server-owned values set after `save(commit=False)` arrive **too late** for the model's `clean()`; pass them in through `instance=` or the form's `__init__` if model rules depend on them. ## Choosing between the form's clean() and the model's clean() - A rule that must hold **however** the object is written, from any form, the admin or a script calling `full_clean()`, belongs on the model. - A rule that belongs to **this workflow**, such as "the public form requires a message for flats", belongs in the form's `clean()`. - Remember that `Model.save()` does not call `full_clean()` by itself, so a model rule protects only code paths that validate; database constraints protect every path. ## What interviewers listen for The two stages, the `construct_instance()` step, the `super().clean()` warning, how model errors map onto form fields, and that the instance is mutated even on failure.
- In a Django ModelForm, can the model's clean() see values the view sets after save(commit=False)?No. The model's `clean()` runs inside `is_valid()`, before the view gets the instance back from `save(commit=False)`. Values it depends on must be on the instance earlier, for example via `instance=Model(applicant=user)` or assigned in the form's `__init__`.
- How can a Django ModelForm change the message of a model uniqueness error?Add an entry keyed by the error's code to `Meta.error_messages` for that field, for example `{'pet': {'unique': '...'}}`, or set it in the declared field's `error_messages`. When model errors are merged, Django replaces messages whose code matches, and `NON_FIELD_ERRORS` can be used as a key for form-wide ones.
saying these in an interview costs you the question
- The model's clean() runs before the form's clean().
- Skipping super().clean() in a ModelForm also skips the model's clean().
- A failed is_valid() leaves the passed instance untouched.
- Model validation errors are raised as exceptions from is_valid().
- Model validation only happens when save() is called.
- A model clean() error keyed to a field missing from the form shows as a non-field error.