Form Validation

Back

Loading concept...

Django Form Validation 🛡️

The Security Guard at Your Data’s Front Door


The Big Picture: What is Form Validation?

Imagine you’re hosting a fancy birthday party. You have a guest list. At the door stands a security guard (that’s Django’s form validation!).

The guard checks:

  • Is this person’s name on the list? âś…
  • Are they wearing proper clothes? âś…
  • Did they bring a gift (not a water balloon)? âś…

Form validation is Django’s security guard - it checks every piece of data before letting it into your application.

graph TD A["User Submits Form"] --> B["Django Receives Data"] B --> C{Validation Process} C -->|Pass| D["✅ Data is Clean & Safe"] C -->|Fail| E["❌ Show Errors to User"] D --> F["Save to Database"] E --> G["User Fixes & Resubmits"]

1. The Form Validation Process 🔄

How It Works - Step by Step

Think of validation like washing vegetables before cooking:

  1. Receive - You get dirty vegetables (raw user data)
  2. Check - Look for bugs, dirt, bad spots (validation)
  3. Clean - Wash and prepare (data cleaning)
  4. Use - Now safe to cook! (save to database)

The Magic Method: is_valid()

# In your view
def signup(request):
    if request.method == 'POST':
        form = SignupForm(request.POST)

        # This ONE line triggers ALL validation!
        if form.is_valid():
            # Data is clean and safe
            form.save()
        else:
            # Errors exist - show them
            print(form.errors)

What happens inside is_valid()?

  1. Runs to_python() - converts data to Python types
  2. Runs validate() - checks basic rules
  3. Runs run_validators() - runs custom validators
  4. Runs clean_<fieldname>() - field-level cleaning
  5. Runs clean() - form-level cleaning

2. cleaned_data and Form Errors 📦

cleaned_data - Your Gift Box of Clean Data

After validation passes, Django gives you a special dictionary called cleaned_data. It’s like getting your clothes back from the dry cleaner - clean, pressed, and ready!

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    age = forms.IntegerField()

# In your view
if form.is_valid():
    # Access the CLEAN data
    name = form.cleaned_data['name']
    email = form.cleaned_data['email']
    age = form.cleaned_data['age']  # This is now an INTEGER, not a string!

Why use cleaned_data?

  • Data types are correct (strings become integers, dates become date objects)
  • Extra whitespace is trimmed
  • Data is validated and safe

Form Errors - The Problem Report

When validation fails, Django collects all problems in form.errors:

if not form.is_valid():
    # Dictionary of field -> error messages
    print(form.errors)
    # Output: {'email': ['Enter a valid email address.']}

    # Get errors for specific field
    print(form.errors.get('email'))

    # Get ALL errors as simple list
    print(form.errors.as_text())

In your template:

<form method="post">
    {% csrf_token %}

    <!-- Show all errors at top -->
    {% if form.errors %}
        <div class="alert alert-danger">
            Please fix the errors below.
        </div>
    {% endif %}

    <!-- Show field with its error -->
    <div>
        {{ form.email }}
        {% if form.email.errors %}
            <span class="error">
                {{ form.email.errors.0 }}
            </span>
        {% endif %}
    </div>

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

3. Field-Level Validation 🎯

Check ONE Field at a Time

Field-level validation is like checking one ingredient before adding it to your recipe. Is this tomato fresh? Is this egg not cracked?

Use clean_<fieldname>() method:

class SignupForm(forms.Form):
    username = forms.CharField(max_length=50)
    email = forms.EmailField()
    age = forms.IntegerField()

    def clean_username(self):
        """Check username is not taken"""
        username = self.cleaned_data['username']

        # Check if username exists
        if User.objects.filter(
            username=username
        ).exists():
            raise forms.ValidationError(
                "This username is already taken!"
            )

        # Always return the cleaned value
        return username.lower()  # Also make it lowercase

    def clean_age(self):
        """Check age is at least 13"""
        age = self.cleaned_data['age']

        if age < 13:
            raise forms.ValidationError(
                "You must be at least 13 years old."
            )

        return age

Key Rules:

  1. Method name must be clean_ + field name
  2. Access the value from self.cleaned_data
  3. Raise ValidationError if something’s wrong
  4. Always return the value (even if unchanged)

4. Form-Level Validation đź”—

Check Multiple Fields Together

Sometimes you need to check how fields relate to each other. Like making sure your password and confirm password match!

Use the clean() method:

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):
        """Check passwords match"""
        # Get ALL cleaned data
        cleaned_data = super().clean()

        new_pw = cleaned_data.get('new_password')
        confirm_pw = cleaned_data.get('confirm_password')

        # Compare two fields
        if new_pw and confirm_pw:
            if new_pw != confirm_pw:
                raise forms.ValidationError(
                    "Passwords don't match!"
                )

        return cleaned_data

Another Example - Date Range:

class BookingForm(forms.Form):
    check_in = forms.DateField()
    check_out = forms.DateField()

    def clean(self):
        cleaned_data = super().clean()

        check_in = cleaned_data.get('check_in')
        check_out = cleaned_data.get('check_out')

        if check_in and check_out:
            if check_out <= check_in:
                raise forms.ValidationError(
                    "Check-out must be after check-in!"
                )

        return cleaned_data

5. Custom Validators ⚡

Reusable Validation Functions

What if you want to use the same check in many forms? Create a custom validator - a reusable function!

Simple Function Validator:

from django.core.exceptions import ValidationError

def validate_no_bad_words(value):
    """Check for inappropriate content"""
    bad_words = ['spam', 'scam', 'fake']

    for word in bad_words:
        if word in value.lower():
            raise ValidationError(
                f"'{word}' is not allowed!"
            )

# Use it in your form
class CommentForm(forms.Form):
    comment = forms.CharField(
        validators=[validate_no_bad_words]
    )

Class-Based Validator (with parameters):

from django.core.exceptions import ValidationError

class MinWordsValidator:
    """Ensure text has minimum word count"""

    def __init__(self, min_words=10):
        self.min_words = min_words

    def __call__(self, value):
        word_count = len(value.split())
        if word_count < self.min_words:
            raise ValidationError(
                f"Please write at least "
                f"{self.min_words} words. "
                f"You wrote {word_count}."
            )

# Use it
class EssayForm(forms.Form):
    essay = forms.CharField(
        widget=forms.Textarea,
        validators=[MinWordsValidator(50)]
    )

    summary = forms.CharField(
        validators=[MinWordsValidator(10)]
    )

6. Built-in Validators đź§°

Django’s Ready-Made Validators

Django comes with many validators already built! No need to reinvent the wheel.

from django.core.validators import (
    MinValueValidator,
    MaxValueValidator,
    MinLengthValidator,
    MaxLengthValidator,
    RegexValidator,
    EmailValidator,
    URLValidator,
)

class ProductForm(forms.Form):
    # Number range
    price = forms.DecimalField(
        validators=[
            MinValueValidator(0.01),
            MaxValueValidator(99999.99)
        ]
    )

    # Text length
    description = forms.CharField(
        validators=[
            MinLengthValidator(10),
            MaxLengthValidator(500)
        ]
    )

    # Pattern matching
    product_code = forms.CharField(
        validators=[
            RegexValidator(
                regex=r'^[A-Z]{2}\d{4}#x27;,
                message="Code format: AB1234"
            )
        ]
    )

    # URL validation
    website = forms.CharField(
        validators=[URLValidator()]
    )

Common Built-in Validators

Validator What It Checks
MinValueValidator(n) Number >= n
MaxValueValidator(n) Number <= n
MinLengthValidator(n) Text length >= n
MaxLengthValidator(n) Text length <= n
RegexValidator(pattern) Matches pattern
EmailValidator() Valid email format
URLValidator() Valid URL format
validate_email Email check (function)
validate_slug URL-safe string

Complete Example: Registration Form

Let’s put it ALL together!

from django import forms
from django.core.validators import (
    MinLengthValidator,
    RegexValidator
)
from django.core.exceptions import ValidationError

# Custom validator
def validate_not_common_password(value):
    common = ['password', '123456', 'qwerty']
    if value.lower() in common:
        raise ValidationError(
            "Please choose a stronger password!"
        )

class RegistrationForm(forms.Form):
    username = forms.CharField(
        max_length=30,
        validators=[
            MinLengthValidator(3),
            RegexValidator(
                r'^[a-zA-Z0-9_]+#x27;,
                "Letters, numbers, underscore only"
            )
        ]
    )

    email = forms.EmailField()

    password = forms.CharField(
        widget=forms.PasswordInput,
        validators=[
            MinLengthValidator(8),
            validate_not_common_password
        ]
    )

    confirm_password = forms.CharField(
        widget=forms.PasswordInput
    )

    age = forms.IntegerField()

    # Field-level validation
    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(
            username__iexact=username
        ).exists():
            raise ValidationError(
                "Username already taken!"
            )
        return username.lower()

    def clean_age(self):
        age = self.cleaned_data['age']
        if age < 13:
            raise ValidationError(
                "Must be 13 or older."
            )
        return age

    # Form-level validation
    def clean(self):
        cleaned_data = super().clean()
        pw = cleaned_data.get('password')
        confirm = cleaned_data.get('confirm_password')

        if pw and confirm and pw != confirm:
            raise ValidationError(
                "Passwords don't match!"
            )

        return cleaned_data

Summary: Validation Flow

graph TD A["Form Submitted"] --> B["is_valid Called"] B --> C["Field Validators Run"] C --> D["clean_fieldname Methods"] D --> E["clean Method"] E --> F{All Pass?} F -->|Yes| G["cleaned_data Ready!"] F -->|No| H["form.errors Populated"] G --> I["Safe to Save"] H --> J["Show Errors to User"]

Remember:

  • đź”’ is_valid() starts everything
  • 📦 cleaned_data = your clean, safe data
  • ⚠️ form.errors = problems found
  • 🎯 clean_<field>() = check one field
  • đź”— clean() = check multiple fields together
  • ⚡ Custom validators = reusable checks
  • đź§° Built-in validators = ready to use!

Quick Reference

# Check if form is valid
if form.is_valid():
    data = form.cleaned_data

# Get errors
form.errors  # All errors
form.errors['field']  # Field errors

# Field validation
def clean_username(self):
    value = self.cleaned_data['username']
    # ... validate ...
    return value

# Form validation
def clean(self):
    data = super().clean()
    # ... cross-field checks ...
    return data

# Raise error
raise forms.ValidationError("Message")

# Built-in validators
from django.core.validators import *

You’ve got this! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.