Templates and Forms
Build consistent pages with template inheritance, validate user input with forms, and handle POST requests without losing errors.
By the end, you’ll be able to…
- Use template inheritance to keep layouts consistent.
- Create forms that validate and render field errors.
- Handle POST with CSRF protection and a redirect after success.
- Keep templates free of business logic.
01Template inheritance keeps layout consistent
A base template defines the shared shell and named blocks. Child templates extend it and override only the blocks they need. This avoids duplicating navigation, metadata, and footer markup on every page. A change to the shell happens in one file.
Django templates escape variables by default, which prevents accidental HTML injection. Use the safe filter only for content you have deliberately sanitized. Keep loops and conditionals simple; if a template needs complex calculations, move that work into the view or a model method.
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>{% block title %}My Blog{% endblock %}</title></head><body> <header><a href="{% url 'post_list' %}">Home</a></header> <main>{% block content %}{% endblock %}</main></body></html>02Forms validate at the boundary
A Django Form declares fields and validation rules. On a POST request, bind the form to request.POST and call is_valid(). If it passes, cleaned_data contains converted, validated values. If it fails, the same form renders with field-level errors so the user can correct the input.
A ModelForm derives fields from a model, reducing duplication. It still needs an explicit fields list; using __all__ can expose fields you did not intend to accept. Save only after validation, and perform authorization checks separately from validation.
from django import formsfrom .models import Postclass PostForm(forms.ModelForm): class Meta: model = Post fields = ["title", "slug", "body", "published"] def clean_title(self): title = self.cleaned_data["title"].strip() if len(title) < 3: raise forms.ValidationError("title must be at least 3 characters") return title03Handle POST safely
A state-changing form must use method='post' and include {% csrf_token %}. Django validates the token and rejects requests without it in the default middleware configuration. Do not disable CSRF to make a form work; fix the template or the request instead.
After a successful POST, redirect to a GET view. This is the post/redirect/get pattern: refreshing the confirmation page does not resubmit the form. On failure, re-render the form with errors and the submitted values so the user does not lose their work.
from django.shortcuts import redirect, renderfrom .forms import PostFormdef post_create(request): if request.method == "POST": form = PostForm(request.POST) if form.is_valid(): post = form.save() return redirect("post_detail", slug=post.slug) else: form = PostForm() return render(request, "blog/post_form.html", {"form": form})04Keep templates dumb
Templates are for presentation. Put authorization, calculations, and multi-step rules in views, forms, or model methods. A template tag is appropriate for a genuinely presentational concern that recurs, not for hiding a query or a permission check.
Test the form directly with valid and invalid data, then test the view with Django's test client. The form tests document the validation contract; the view tests document status codes, redirects, and which template rendered. Together they catch the boundary mistakes that template inspection cannot. If a template needs an expensive value, compute it once in the view and pass it in explicitly.
from django.test import TestCasefrom .forms import PostFormclass PostFormTests(TestCase): def test_rejects_short_title(self): form = PostForm(data={"title": "Hi", "slug": "hi", "body": "text"}) self.assertFalse(form.is_valid()) self.assertIn("title", form.errors) def test_accepts_valid_post(self): form = PostForm(data={"title": "Hello", "slug": "hello", "body": "text"}) self.assertTrue(form.is_valid())The ideas worth keeping.
- Template inheritance keeps shared markup in one place.
- Forms validate at the boundary and render actionable errors.
- POST, validate, redirect on success; re-render with errors on failure.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Create a contact form
Define a ContactForm with name, email, and message fields. Make email use EmailField and message use a textarea. Render it with CSRF protection.
Explore a worked solution
from django import formsclass ContactForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() message = forms.CharField(widget=forms.Textarea)EmailField validates the address shape, and the textarea widget makes the message field usable. In the template, render {% csrf_token %} inside a POST form and call {{ form.as_p }} so field errors are visible.
Validate a post title
Add a clean_title method that trims whitespace and rejects titles shorter than three characters. Show the error path in a short explanation.
Explore a worked solution
from django import formsfrom .models import Postclass PostForm(forms.ModelForm): class Meta: model = Post fields = ["title", "slug", "body"] def clean_title(self): title = self.cleaned_data["title"].strip() if len(title) < 3: raise forms.ValidationError("title must be at least 3 characters") return titleclean_<field> runs after the field's built-in validation and before the form is considered valid. Returning the cleaned value ensures the trimmed version is what gets saved.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Create a contact form
Define a ContactForm with name, email, and message fields. Make email use EmailField and message use a textarea. Render it with CSRF protection.
[Process exited with code 0 - No output]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.