Django forms: rendering, validation, and cleaned data
Comments have only ever been added through the admin so far. This part builds a real Form for it — deliberately a plain forms.Form rather than a ModelForm (that's part 13), so the validation mechanics are visible before the shortcut that hides most of them.
Defining the form
# blog/forms.py
from django import forms
class CommentForm(forms.Form):
name = forms.CharField(max_length=80)
body = forms.CharField(widget=forms.Textarea)Each field on a Form class does two jobs at once: it defines what HTML input renders (CharField → a text input, unless a different widget is set, like Textarea here), and it defines how the submitted value gets validated and cleaned.
Rendering it in a template
# blog/views.py
from .forms import CommentForm
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
slug_field = "slug"
slug_url_kwarg = "slug"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = CommentForm()
return context{# inside post_detail.html's {% block content %} #}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Post comment</button>
</form>{{ form.as_p }} renders every field wrapped in a <p> tag, with labels generated from each field's name — the fastest way to get a form on the page, though real projects often render fields individually for more layout control. {% csrf_token %} is not optional; part 14 covers exactly why.
Handling the submission
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
Comment.objects.create(
post=post,
name=form.cleaned_data["name"],
body=form.cleaned_data["body"],
)
return redirect("post_detail", slug=post.slug)
else:
form = CommentForm()
return render(request, "blog/post_detail.html", {"post": post, "form": form})This switches back to a function-based view — handling two HTTP methods differently is exactly the branching case from part 9's callout where a function view stays more readable than a class-based one. form.is_valid() runs every field's validation and returns False if anything fails; only on success is form.cleaned_data safe to read — it holds the validated, correctly-typed values (not the raw, unvalidated request.POST strings). Redirecting after a successful POST, rather than rendering a response directly, is deliberate: it stops a page refresh from resubmitting the same form.
Reading request.POST["name"] directly instead of form.cleaned_data["name"] after validation. It skips everything the form was just asked to check — length limits, required fields, type coercion — making the validation step you just wrote pointless.
Next: ModelForm — the shortcut that generates a form like this one directly from a model, and CreateView/UpdateView to go with it.