Django Form Validation: Custom Validators And Error Handling

User input is unpredictable and often invalid. Django forms provide a robust framework for validating data before it enters your application. Built-in validators handle common cases, but custom validators give you flexibility for domain-specific rules. Understanding form validation deeply ensures your application accepts only valid data and provides helpful feedback when users make mistakes.

Using Built-In Validators

Django includes a library of validators for common patterns: email addresses, URLs, integers, and more. These validators are composable, meaning you can combine multiple validators on a single field to enforce multiple constraints simultaneously.


from django import forms
from django.core.validators import MinLengthValidator, URLValidator
from django.contrib.auth.models import User

class UserRegistrationForm(forms.ModelForm):
    password = forms.CharField(
        min_length=8,
        widget=forms.PasswordInput,
        validators=[MinLengthValidator(8, "Password must be at least 8 characters")]
    )
    website = forms.URLField(
        required=False,
        validators=[URLValidator()]
    )

    class Meta:
        model = User
        fields = ['username', 'email', 'password']
    

The MinLengthValidator ensures passwords meet a minimum length. The URLValidator validates URLs. Multiple validators on a field all execute, and Django reports errors for any that fail. Built-in validators cover most common validation needs, but when they do not suffice, custom validators fill the gap.

Creating Custom Validators

Custom validators are simple functions that receive a value and raise a ValidationError if the value is invalid. You can use custom validators for business logic specific to your application, like checking if a username is already taken or ensuring an age is within acceptable bounds.


from django.core.exceptions import ValidationError

def validate_even_number(value):
    if value % 2 != 0:
        raise ValidationError("This field must be an even number.")

def validate_future_date(value):
    from datetime import date
    if value <= date.today():
        raise ValidationError("This date must be in the future.")

class EventForm(forms.Form):
    num_attendees = forms.IntegerField(validators=[validate_even_number])
    event_date = forms.DateField(validators=[validate_future_date])
    

When the form is submitted, Django calls each validator on the field value. If a ValidationError is raised, Django adds the error message to the form's error list. The form is then invalid and the view typically re-renders the form with error messages displayed to the user. Custom validators are often housed in a validators module for reuse across multiple forms.

See also  How to squash migrations in Django

Using Clean Methods For Multi-Field Validation

Sometimes validation depends on multiple fields. For example, a password confirmation field must match the password field. The clean() method of a form receives all form data and can perform cross-field validation.


class ChangePasswordForm(forms.Form):
    password = forms.CharField(widget=forms.PasswordInput)
    password_confirm = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        password_confirm = cleaned_data.get('password_confirm')

        if password and password_confirm and password != password_confirm:
            raise ValidationError("Passwords do not match.")

        return cleaned_data
    

The clean() method is called after all field validators pass. It receives a dictionary of all field values in cleaned_data. Use this method to validate relationships between fields. Raising a ValidationError in clean() adds a non-field error to the form, displayed separately from individual field errors.

See also  Understanding Django Apps: How Many Apps Should Your Project Have?

Field-Level Clean Methods

For validation specific to a single field that is too complex for a validator function, implement clean_() methods on the form. These run after field validators and before the form's clean() method.


class ProductForm(forms.Form):
    price = forms.DecimalField()

    def clean_price(self):
        price = self.cleaned_data.get('price')
        if price is not None and price <= 0:
            raise ValidationError("Price must be greater than zero.")
        return price

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'slug', 'content']

    def clean_slug(self):
        slug = self.cleaned_data.get('slug')
        if Post.objects.filter(slug=slug).exists():
            raise ValidationError("This slug is already in use.")
        return slug
    

Field-level clean methods are called independently for each field. The clean_price() method validates price-specific logic, while clean_slug() checks for uniqueness in the database. These methods must return the cleaned value after validation. This approach keeps field-specific logic separate from form-level logic, improving code organization.

Displaying Validation Errors In Templates

After validation fails, the template must display error messages to guide users toward correction. Django form templates can access form errors for individual fields or form-level errors.[web:33]

See also  How does Django connect to external database?


{% if form.non_field_errors %}
  <div class="alert alert-danger">
    {{ form.non_field_errors }}
  </div>
{% endif %}

{% for field in form %}
  <div class="form-group">
    {{ field.label_tag }}
    {{ field }}
    {% if field.errors %}
      <div class="invalid-feedback d-block">
        {{ field.errors }}
      </div>
    {% endif %}
  </div>
{% endfor %}

<button type="submit">Submit</button>
    

The template iterates through form fields and displays errors for each. Non-field errors (from the clean() method) are displayed at the top. Field errors are displayed next to the problematic field. This approach provides clear feedback that helps users understand what went wrong and how to fix it.

Best Practices For Validation

Validate early and often: validate input in forms, validate before saving to the database, and validate on the server even if you have client-side validation. Never trust user input. Keep validation logic in one place: use model clean methods or form validators, not scattered throughout views. Write clear error messages that guide users toward correction rather than just stating the problem. These practices ensure your application is secure, user-friendly, and maintainable.