SyntaxStudy
Sign Up
Django Django Forms and Form Validation
Django Beginner 1 min read

Django Forms and Form Validation

Django's form system provides a high-level API for handling HTML forms. A Form class defines the fields and their validation rules. When a form is instantiated with POST data (bound form) and is_valid() is called, Django runs field-level cleaning (type coercion and format validation), then custom field-level clean_() methods, and finally the form-level clean() method. Validated data is available in form.cleaned_data. Each form field type (CharField, EmailField, IntegerField, DateField, ChoiceField, etc.) provides both HTML widget rendering and Python-side validation. The widget argument lets you customise the rendered HTML element — for example using a Textarea widget for a CharField or a PasswordInput widget for a sensitive field. Field arguments like required, min_length, max_length, and validators let you configure validation behaviour. Custom validators are functions that raise ValidationError if a value is invalid. You can attach validators to individual fields using the validators argument or write clean_() methods directly on the form class for context-aware validation. The form-level clean() method is the right place for cross-field validation — for example checking that a password confirmation field matches the password field.
Example
# blog/forms.py — form definition and validation
from django import forms
from django.core.exceptions import ValidationError
from .models import Post


def no_spam_words(value):
    """Custom validator: reject values containing spam keywords."""
    spam_words = ['buy now', 'click here', 'free money']
    lower = value.lower()
    for word in spam_words:
        if word in lower:
            raise ValidationError(f'Content contains a disallowed phrase: "{word}".')


class PostForm(forms.Form):
    title = forms.CharField(
        max_length=200,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Post title'}),
    )
    body = forms.CharField(
        widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 8}),
        validators=[no_spam_words],
    )
    tags = forms.CharField(
        max_length=200,
        required=False,
        help_text='Comma-separated tag names.',
    )

    def clean_title(self):
        title = self.cleaned_data['title']
        if Post.objects.filter(title__iexact=title).exists():
            raise ValidationError('A post with this title already exists.')
        return title.strip().title()

    def clean(self):
        cleaned = super().clean()
        title   = cleaned.get('title', '')
        body    = cleaned.get('body', '')
        # Cross-field: body must not just repeat the title
        if body.strip().lower() == title.strip().lower():
            raise ValidationError('The post body must contain more than just the title.')
        return cleaned