Forms

A Django Form class describes a form's fields once, in Python, and Django uses that description to render the HTML, validate whatever gets submitted, and hand back clean, typed data — all from a single definition.

Defining a form

Python 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)

Each field type already knows how to validate itself — EmailField rejects anything that isn't a plausible email address, and a plain CharField with no required=False rejects an empty submission, all without you writing any validation logic by hand.

Rendering the form

Python blog/views.py
from django.shortcuts import render
from .forms import ContactForm

def contact(request):
    form = ContactForm()
    return render(request, 'contact.html', {'form': form})
HTML blog/templates/contact.html
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Send</button>
</form>

{{ form.as_p }} renders every field as a labeled <p> automatically — you get real <input>, <textarea>, and label elements without writing them by hand. {% csrf_token %} inserts a hidden token Django checks on submission, protecting the form against cross-site request forgery.

Processing a submission

The same view handles both showing an empty form (GET) and processing a submission (POST):

Python blog/views.py
def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            # e.g. send an email, save to the database, etc.
            return render(request, 'thanks.html', {'name': name})
    else:
        form = ContactForm()
    return render(request, 'contact.html', {'form': form})

form.is_valid() runs every field's validation and returns True only if all of it passes. form.cleaned_data is only populated after a successful is_valid() call, and its values are already the correct Python types — the string from an EmailField has already been confirmed to look like an email, for instance.

Note: if is_valid() returns False, re-rendering contact.html with that same form object (not a fresh ContactForm()) automatically shows the user's previously entered values plus per-field error messages next to whatever failed — you don't need to write any of that error-display logic yourself, it comes from the template calling {{ form.as_p }} on a form that already has errors attached.