Collect and validate user input safely. Build forms, generate them from models with ModelForm, add custom validation rules, and protect every POST with CSRF.
Why: a Form class declares fields once and gives you HTML rendering, type conversion, and validation for free. Where: create blog/forms.py. Each field maps to an input and knows how to validate itself (required, max length, type).
# blog/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)Why {% csrf_token %}: Django rejects any POST without it, blocking cross-site request forgery — a forged submission from another site. Note: it is mandatory on every POST form. {{ form.as_p }} renders the fields, labels, and error messages for you.
<form method="post">
{% csrf_token %} <!-- required on every POST -->
{{ form.as_p }} <!-- fields + labels + errors -->
<button type="submit">Send</button>
</form>Why: a ModelForm reads your model and builds the matching fields automatically, and its save() creates or updates the row — no field repetition. When: any create/edit form backed by a model (most of them). Note: list columns in Meta.fields.
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug", "body", "status"]
widgets = {"body": forms.Textarea(attrs={"rows": 8})}Note: bind the POST data to the form, then is_valid() runs every validation rule and fills form.errors. Only save when valid; otherwise re-render so the user sees the errors with their input preserved.
from django.shortcuts import render, redirect
from .forms import PostForm
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save()
return redirect(post.get_absolute_url())
else:
form = PostForm()
return render(request, "blog/post_form.html", {"form": form})When clean_<field>(): validate one field — return the cleaned value or raise ValidationError. When clean(): validate across fields (e.g. two passwords match). Note: these run automatically inside is_valid().
from django import forms
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug"]
def clean_title(self):
title = self.cleaned_data["title"]
if "spam" in title.lower():
raise forms.ValidationError("No spam in titles.")
return title
def clean(self):
cleaned = super().clean()
if cleaned.get("title") == cleaned.get("slug"):
raise forms.ValidationError("Title and slug must differ.")
return cleaned