Django Form Validation

Ensuring data quality and security

✅ What is Form Validation?

Form validation checks user input for correctness and security. Django provides built-in validators and custom validation methods to ensure data meets your requirements before processing.


# forms.py - Basic validation
from django import forms

class SignupForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=20)
    email = forms.EmailField()
    age = forms.IntegerField(min_value=18)
                                    

Validation Types

🔧

Built-in Validators

Django's default validation

email = forms.EmailField()
🎯

Field Validation

Validate individual fields

def clean_username(self):
    # validation logic
🔗

Form Validation

Validate multiple fields together

def clean(self):
    # cross-field validation
⚙️

Custom Validators

Reusable validation functions

validators=[custom_validator]

🔹 Built-in Field Validation

Django form fields include automatic validation based on field type and parameters. These validators check data format, length, and value ranges without extra code.

# forms.py
from django import forms

class RegistrationForm(forms.Form):
    # Length validation
    username = forms.CharField(
        min_length=3,
        max_length=20,
        error_messages={
            'min_length': 'Username must be at least 3 characters',
            'max_length': 'Username cannot exceed 20 characters'
        }
    )
    
    # Email format validation
    email = forms.EmailField()
    
    # Number range validation
    age = forms.IntegerField(
        min_value=18,
        max_value=100
    )
    
    # Required field
    terms = forms.BooleanField(
        required=True,
        error_messages={'required': 'You must accept terms'}
    )

🔹 Custom Field Validation

Create clean_fieldname methods to add custom validation logic for specific fields. These methods run after built-in validators and can raise ValidationError for invalid data.

# forms.py
from django import forms
from django.core.exceptions import ValidationError

class UserForm(forms.Form):
    username = forms.CharField(max_length=20)
    password = forms.CharField(widget=forms.PasswordInput)
    
    def clean_username(self):
        username = self.cleaned_data.get('username')
        
        # Check if username contains only letters and numbers
        if not username.isalnum():
            raise ValidationError(
                'Username must contain only letters and numbers'
            )
        
        # Check if username is already taken
        if User.objects.filter(username=username).exists():
            raise ValidationError('Username already exists')
        
        # Always return the cleaned data
        return username
    
    def clean_password(self):
        password = self.cleaned_data.get('password')
        
        if len(password) < 8:
            raise ValidationError('Password must be at least 8 characters')
        
        if not any(char.isdigit() for char in password):
            raise ValidationError('Password must contain at least one number')
        
        return password

🔹 Form-wide Validation

Use the clean method to validate multiple fields together. This is useful for checking relationships between fields or complex business logic requirements.

# forms.py
from django import forms
from django.core.exceptions import ValidationError

class PasswordChangeForm(forms.Form):
    old_password = forms.CharField(widget=forms.PasswordInput)
    new_password = forms.CharField(widget=forms.PasswordInput)
    confirm_password = forms.CharField(widget=forms.PasswordInput)
    
    def clean(self):
        cleaned_data = super().clean()
        new_password = cleaned_data.get('new_password')
        confirm_password = cleaned_data.get('confirm_password')
        old_password = cleaned_data.get('old_password')
        
        # Check if passwords match
        if new_password and confirm_password:
            if new_password != confirm_password:
                raise ValidationError('New passwords do not match')
        
        # Check if new password is different from old
        if old_password and new_password:
            if old_password == new_password:
                raise ValidationError(
                    'New password must be different from old password'
                )
        
        return cleaned_data

🔹 Custom Validators

Create reusable validator functions that can be applied to multiple fields. These functions take a value and raise ValidationError if validation fails.

# validators.py
from django.core.exceptions import ValidationError
import re

def validate_phone_number(value):
    """Validate phone number format"""
    pattern = r'^\+?1?\d{9,15}$'
    if not re.match(pattern, value):
        raise ValidationError(
            'Enter a valid phone number',
            code='invalid_phone'
        )

def validate_no_special_chars(value):
    """Check for special characters"""
    if not value.isalnum():
        raise ValidationError(
            'Only letters and numbers allowed',
            code='special_chars'
        )

# forms.py
from django import forms
from .validators import validate_phone_number, validate_no_special_chars

class ContactForm(forms.Form):
    name = forms.CharField(
        validators=[validate_no_special_chars]
    )
    phone = forms.CharField(
        validators=[validate_phone_number]
    )

🔹 Displaying Validation Errors

Django automatically collects validation errors and makes them available in templates. Display errors next to fields or show all errors together.

<!-- template.html -->
<form method="post">
    {% csrf_token %}
    
    <!-- Show all errors at once -->
    {% if form.errors %}
        <div class="alert alert-danger">
            <ul>
                {% for field, errors in form.errors.items %}
                    {% for error in errors %}
                        <li>{{ field }}: {{ error }}</li>
                    {% endfor %}
                {% endfor %}
            </ul>
        </div>
    {% endif %}
    
    <!-- Show errors per field -->
    <div>
        {{ form.username.label_tag }}
        {{ form.username }}
        {% if form.username.errors %}
            <span class="error">{{ form.username.errors }}</span>
        {% endif %}
    </div>
    
    <button type="submit">Submit</button>
</form>

Output (with errors):

username: Username must be at least 3 characters

Username must be at least 3 characters

🧠 Test Your Knowledge

Which method validates a single field named 'email'?