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:

  1. Field-level validation: Django runs default field validation (e.g., verifying an EmailField has an "@" symbol) and checks any registered validators.
  2. clean_<fieldname>() execution: Django looks for custom cleaning methods defined on the form matching the field names.
  3. Form-level clean() execution: After all individual fields are processed, the general clean() 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

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

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_data

Key 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