Forms and CSRF
Introduction
Django forms validate and render HTML input, bridging browser POST data to models and views. ModelForm generates fields from your Post model; Form handles arbitrary input. Every POST form must include CSRF protection—Django’s built-in defense against cross-site request forgery. This chapter builds create/edit post flows with FBV and templates, explains validation, and notes safe CSRF patterns for AJAX.
Prerequisites
- Models and ORM Basics —
Postmodel - Templates and Static Files —
render(),base.html - Views: FBV and CBV
forms.Form vs forms.ModelForm
| Type | Use |
|---|---|
forms.Form | Search boxes, contact forms, non-model data |
forms.ModelForm | Create/update model instances |
Manual Form example
blog/forms.py:
from django import forms
class SearchForm(forms.Form):
q = forms.CharField(label="Search", max_length=100, required=False)
sort = forms.ChoiceField(
choices=[("newest", "Newest"), ("oldest", "Oldest")],
required=False,
)View:
def search(request):
form = SearchForm(request.GET or None)
results = Post.objects.all()
if form.is_valid():
q = form.cleaned_data.get("q")
if q:
results = results.filter(title__icontains=q)
return render(request, "blog/search.html", {"form": form, "results": results})Use request.GET for search (idempotent); request.POST for mutations.
ModelForm for Post
blog/forms.py:
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug", "body", "published", "tags"]
widgets = {
"body": forms.Textarea(attrs={"rows": 12}),
}Exclude sensitive fields:
fields = ["title", "body", "published"] # slug auto-generated in viewOr:
exclude = ["author", "created_at", "updated_at"]Create View with FBV
blog/views.py:
Code explanation:
commit=False— setauthorbefore first savesave_m2m()— persiststagsafter instance has PK@login_required— redirect anonymous users to login (chapter 14)
Update View
Object-level checks prevent editing others’ posts.
Template with CSRF
blog/templates/blog/post_form.html:
{% csrf_token %} renders a hidden input Django validates on POST.
Manual field rendering
<p>
<label for="{{ form.title.id_for_label }}">Title</label>
{{ form.title }}
{{ form.title.errors }}
</p>Better control over HTML/CSS than {{ form.as_p }}.
CSRF How It Works
GET form page → Django sets csrftoken cookie + hidden field in form
POST submit → Middleware compares cookie token vs POST csrfmiddlewaretoken
Mismatch/missing → 403 ForbiddenSettings (defaults usually fine):
CSRF_COOKIE_SECURE = True # production HTTPS
CSRF_TRUSTED_ORIGINS = ["https://example.com"]AJAX POST (Concept)
Read token from cookie and send header:
DRF and SPA patterns — chapter 20.
@csrf_exempt
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook(request):
...Use only for trusted webhooks with alternate auth (signature verification)—never on user-facing forms.
Validation Layers
| Layer | Where |
|---|---|
| HTML5 | required, maxlength on widgets |
| Field validators | forms.CharField(max_length=200) |
Form clean_* | Cross-field rules |
| Model | validators=, clean() on model |
| DB | unique=True, constraints |
clean_* methods
Errors appear in form.errors and {{ field.errors }}.
ModelForm with CreateView (CBV)
Template still needs {% csrf_token %}—CBV includes it via FormView.
URL Wiring
blog/urls.py:
path("posts/new/", views.post_create, name="post-create"),
path("posts/<int:pk>/edit/", views.post_edit, name="post-edit"),Or CBV:
path("posts/new/", views.PostCreateView.as_view(), name="post-create"),Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Missing {% csrf_token %} | 403 CSRF verification failed | Add token to POST forms |
Forgot save_m2m() | Tags not saved | After commit=False |
POST without form.is_valid() | Invalid data saved | Always validate before save |
@csrf_exempt on user forms | CSRF vulnerability | Remove exempt |
Editing request.POST immutable | Cannot modify | Copy to mutable QueryDict if needed |
Post-Chapter Checklist
-
PostFormModelFormwith validation - Create/edit views handle GET and POST
- Templates include
{% csrf_token %} - Field and form errors display to users
- You know when
csrf_exemptis acceptable
FAQ## FAQ
File upload forms?
Set enctype="multipart/form-data" and request.FILES in view—media in chapter 16.
Disable CSRF for API?
JSON APIs often use token/JWT auth instead—DRF handles this; do not mix exempt FBV with session auth carelessly.
form.as_table vs as_p?
as_p wraps fields in <p>; as_table for tables—manual rendering preferred for production CSS.
Repeat POST on refresh?
Redirect after successful POST (PRG pattern)—redirect() after save.
Custom error CSS?
Add error_css_class = "is-invalid" in Meta or widget attrs.