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
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
from django.shortcuts import render
from .forms import ContactForm
def contact(request):
form = ContactForm()
return render(request, 'contact.html', {'form': form})
<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):
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.
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.