Django Form Validation Using clean and clean_fieldname
Django processes form data through a robust two-step validation
pipeline designed to ensure data integrity before saving or using input.
When a form's is_valid() method is invoked, Django executes
field-specific validation via individual
clean_<fieldname>() methods, followed by form-wide
validation through the clean() method. This article breaks
down the execution order, individual roles, data handling, and
implementation patterns for both validation steps.
The Validation Lifecycle
When form.is_valid() is called, it triggers
form.full_clean(). The process follows a strict sequential
order:
- Field-level validation: Django runs default field
validation (e.g., verifying an
EmailFieldhas an "@" symbol) and checks any registeredvalidators. clean_<fieldname>()execution: Django looks for custom cleaning methods defined on the form matching the field names.- Form-level
clean()execution: After all individual fields are processed, the generalclean()method is called to perform multi-field or interdependent validation.
Data that passes field-level checks is stored in the
self.cleaned_data dictionary, which becomes the single
source of truth for subsequent validation steps.
Step 1:
Field-Specific Validation with
clean_<fieldname>()
The clean_<fieldname>() hook is designed to
validate, sanitize, or transform a single, specific field independently
of any other fields on the form.
How It Works
- Accessing Input: The partially validated data is
retrieved from
self.cleaned_data.get('<fieldname>'). - Transforming Data: You can normalize the input (such as converting an email address to lowercase or stripping whitespace).
- Raising Errors: If the value violates business
logic, you raise a
forms.ValidationError. This error attaches directly to the field, making it easy to render alongside the corresponding input element in templates. - Return Value: You must return the
cleaned value at the end of the method. If you omit the return
statement, the field's value in
cleaned_datawill be set toNone.
Example
from django import forms
class RegistrationForm(forms.Form):
username = forms.CharField(max_length=50)
def clean_username(self):
username = self.cleaned_data.get('username')
# Validation
if "admin" in username.lower():
raise forms.ValidationError("Usernames containing 'admin' are reserved.")
# Normalization
return username.lower()Step 2: Cross-Field
Validation with clean()
The clean() method executes after every individual
clean_<fieldname>() method has completed. It is
primarily used for cross-field validation where the validity of one
input depends on another.
How It Works
- Accessing Input: Access all field values directly
from the
self.cleaned_datadictionary. - Handling Missing Keys: If an individual field
failed its earlier validation step, it will not exist in
cleaned_data(or will beNone). Always use.get()to avoidKeyErrorexceptions. - Raising Errors:
- Raising a standard
forms.ValidationErrorinsideclean()creates a non-field error, which typically displays at the top of the form. - Alternatively, you can use
self.add_error('fieldname', 'Error message')to assign an error generated during theclean()phase to a specific input field.
- Raising a standard
- Return Value: You must return the
entire
cleaned_datadictionary.
Example
from django import forms
class RegistrationForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput)
confirm_password = forms.CharField(widget=forms.PasswordInput)
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
# Assign error directly to the confirm_password field
self.add_error('confirm_password', "Passwords do not match.")
return cleaned_dataKey Differences Summary
| Feature | clean_<fieldname>() |
clean() |
|---|---|---|
| Scope | Single field | Multiple fields / Whole form |
| Execution Order | First | Second (after all field methods) |
| Return Value | The cleaned field value | The entire cleaned_data
dictionary |
| Error Attachment | Associated automatically with that field | Non-field error (unless using
add_error()) |
| Best Used For | Normalizing data, single-field checks | Password confirmation, date range validation |