What Django Forms Do
A Django Form is a Python class, subclassing django.forms.Form, that declares a set of fields (CharField, EmailField, IntegerField, and so on). Each field knows how to render itself as an HTML widget and how to clean and validate the raw string data that arrives in request.POST, turning it into typed Python values like int, datetime, or str before your view logic ever touches it.
Cricket analogy: Just as a third umpire converts raw camera footage into a clean, structured decision (out or not out) before it reaches the scoreboard, a Form converts raw POST strings into typed, validated Python values before the view uses them.
Fields, Widgets, and Validation
Each field type carries its own built-in validation: EmailField checks for a valid email shape, IntegerField enforces min_value/max_value, and CharField enforces max_length. A widget is the separate concept controlling how a field is rendered as HTML — a CharField can render as a plain <input type='text'> or, if you pass widget=forms.Textarea, as a multi-line <textarea>, without changing the underlying validation logic at all.
Cricket analogy: A field's validation is like the umpire's LBW rules (fixed, non-negotiable), while the widget is like the stadium's scoreboard display style — you can show the same decision on an LED board or a manual flip board without changing the rule itself.
Custom Validation with clean_<field>() and clean()
Beyond built-in field validation, you can add custom rules by defining a clean_<fieldname>() method on your Form subclass, which runs after the field's default validation and can call self.add_error() or raise forms.ValidationError to reject the value. For rules that span multiple fields — like confirming password and confirm_password match — override the form-level clean() method instead, since it runs after all individual field cleaning is complete.
Cricket analogy: A clean_<field>() method is like a bowling review that checks one specific delivery (front-foot no-ball) in isolation, while clean() is like the third umpire cross-checking multiple camera angles together to rule on a run-out.
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def clean_email(self):
email = self.cleaned_data['email']
if email.endswith('@spam.com'):
raise forms.ValidationError('This email domain is not allowed.')
return email
# In a view:
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
email = form.cleaned_data['email']
# process the validated data
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})form.cleaned_data is only populated after is_valid() returns True. Accessing cleaned_data before calling is_valid() will raise an AttributeError, since validation hasn't run yet.
Rendering Forms in Templates
In a template, {{ form }} (or the more explicit {{ form.as_p }}, {{ form.as_table }}, {{ form.as_div }}) renders all fields with their labels, widgets, and any validation errors already attached. For finer control you can render fields individually with {{ form.email }} and {{ form.email.errors }}, and Django automatically re-populates the widget with the previously submitted (invalid) value so users don't have to retype everything after a validation failure.
Cricket analogy: Using {{ form }} is like accepting the broadcaster's default scorecard overlay, while rendering {{ form.email }} individually is like a commentator pulling up just the strike-rate graphic for one batter mid-innings.
Never trust request.POST directly in a view without running it through a Form's is_valid() check first — skipping validation opens the door to malformed data, type errors, and injection-style abuse even though Django's ORM parameterizes queries.
- A Form class declares fields; each field validates and converts raw POST strings into typed Python values.
- Widgets control HTML rendering and are independent of a field's validation logic.
- clean_<field>() validates one field; clean() validates relationships across multiple fields.
- form.cleaned_data is only available and reliable after is_valid() returns True.
- {{ form.as_p }} renders the whole form quickly; individual field rendering gives layout control.
- Invalid submissions automatically re-populate widgets with the user's prior input for convenience.
- Always validate request.POST through a Form before using the data in a view.
Practice what you learned
1. When is form.cleaned_data reliably available in a Django Form?
2. What is the correct way to add a custom validation rule for a single field named 'email'?
3. Which method should you override to validate a rule that depends on two fields together, like password confirmation?
4. What is the relationship between a Form field and its widget?
5. Why should raw request.POST data never be used directly in a view without a Form?
Was this page helpful?
You May Also Like
Model Forms
ModelForm automatically builds a Django Form from a model's fields, reducing duplication and providing a direct save() path from validated input to the database.
User Authentication in Django
Django's auth framework provides a User model, session-based login/logout, password hashing, and decorators to protect views — all ready to use out of the box.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics