Django Forms & Validation
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is the difference between Django's Form and ModelForm?
| Aspect | Form | ModelForm |
|---|---|---|
| Fields | Manually defined, no model tie-in | Auto-generated from a model's fields |
| Saving | No built-in save() - manual handling | save() creates/updates a model instance directly |
| Use case | Non-model data (search forms, contact forms) | Forms that directly map to a database model |
from django import forms
from .models import Book
# Form - manually defined, not tied to any model
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# must manually handle the cleaned data - no save()
send_email(form.cleaned_data['email'], form.cleaned_data['message'])
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
# ModelForm - auto-generated from the Book model
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'author', 'price'] # or fields = '__all__'
def create_book(request):
if request.method == 'POST':
form = BookForm(request.POST)
if form.is_valid():
book = form.save() # creates a new Book instance directly
return redirect('book-detail', pk=book.pk)
else:
form = BookForm()
return render(request, 'book_form.html', {'form': form})
# ModelForm for UPDATING an existing instance
def edit_book(request, pk):
book = get_object_or_404(Book, pk=pk)
form = BookForm(request.POST or None, instance=book)
if request.method == 'POST' and form.is_valid():
form.save() # updates the existing book
return redirect('book-detail', pk=book.pk)
return render(request, 'book_form.html', {'form': form})
Q2. How does Django form validation work with clean_() and clean()?
| Method | Validates | When to use |
|---|---|---|
| clean_<fieldname>() | A single specific field | Field-specific rules (format, uniqueness) |
| clean() | The whole form, across multiple fields | Cross-field validation (e.g. password confirmation) |
from django import forms
from django.core.exceptions import ValidationError
class SignupForm(forms.Form):
username = forms.CharField(max_length=30)
password = forms.CharField(widget=forms.PasswordInput)
confirm_password = forms.CharField(widget=forms.PasswordInput)
email = forms.EmailField()
# FIELD-level validation - runs automatically for the 'username' field
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise ValidationError('This username is already taken')
return username # MUST return the (possibly modified) cleaned value
def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith('@company.com'):
raise ValidationError('Must use a company email address')
return email
# FORM-level validation - runs AFTER all field-level clean_X methods
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password and confirm_password and password != confirm_password:
# attach the error to a specific field, or use None for a
# general (non-field) error shown at the top of the form
self.add_error('confirm_password', 'Passwords do not match')
return cleaned_data
# Validation execution order:
# 1. Field's built-in validators (e.g. EmailField's format check)
# 2. clean_<fieldname>() for each field with one defined
# 3. Form's clean() method for cross-field validation
form = SignupForm(request.POST)
if form.is_valid():
# all validation passed, form.cleaned_data has clean values
pass
else:
print(form.errors) # dict of field -> list of error messages
Q3. How do you render Django forms in templates, and what are the different rendering options?
| Rendering method | Control level | Use case |
|---|---|---|
| {{ form }} | Least control | Quick prototyping |
| {{ form.as_p }} / as_table / as_ul | Basic layout choice | Simple, standard layouts |
| Manual field-by-field | Full control | Custom design, specific styling |
<!-- Quick rendering options -->
<form method='post'>
{% csrf_token %}
{{ form.as_p }} <!-- each field wrapped in <p> tags -->
<button type='submit'>Submit</button>
</form>
<!-- Alternatives: {{ form.as_table }}, {{ form.as_ul }} -->
<!-- Manual field-by-field rendering - full control over markup -->
<form method='post'>
{% csrf_token %}
<div class='form-group'>
<label for='{{ form.title.id_for_label }}'>Title</label>
{{ form.title }}
{% if form.title.errors %}
<div class='error'>{{ form.title.errors }}</div>
{% endif %}
{% if form.title.help_text %}
<small>{{ form.title.help_text }}</small>
{% endif %}
</div>
<div class='form-group'>
<label for='{{ form.price.id_for_label }}'>Price</label>
{{ form.price }}
{{ form.price.errors }}
</div>
<button type='submit'>Submit</button>
</form>
<!-- Looping over all fields for consistent custom rendering -->
<form method='post'>
{% csrf_token %}
{% for field in form %}
<div class='form-group'>
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
<button type='submit'>Submit</button>
</form>
<!-- Non-field errors (from the form's clean() method) -->
{{ form.non_field_errors }}
<!-- Customizing widget attributes in Python (e.g. for CSS classes) -->
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'price']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
}
Q4. What is the difference between form.is_valid(), form.errors, and form.cleaned_data?
| Attribute/Method | Type | Contains |
|---|---|---|
| is_valid() | Method, returns bool | Triggers validation, returns True/False |
| errors | Dict-like object | Field name -> list of error messages (populated after validation) |
| cleaned_data | Dict | Field name -> validated, converted Python value (only if valid) |
form = BookForm(request.POST)
# is_valid() triggers the validation process and returns a boolean
if form.is_valid():
print('Form is valid')
# cleaned_data is ONLY reliable AFTER is_valid() returns True
title = form.cleaned_data['title'] # already validated and type-converted
price = form.cleaned_data['price'] # e.g. Decimal, not a raw string
else:
print('Form is invalid')
print(form.errors)
# {'title': ['This field is required.'], 'price': ['Enter a valid number.']}
# Accessing errors for a SPECIFIC field
if form.errors.get('title'):
print('Title errors:', form.errors['title'])
# as_json - errors can be serialized for AJAX responses
import json
errors_json = form.errors.as_json()
# IMPORTANT: cleaned_data only contains keys for fields that PASSED
# validation - if a field fails, it's missing from cleaned_data entirely
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'price']
form = BookForm({'title': '', 'price': 'not-a-number'})
form.is_valid() # False
print(form.cleaned_data) # {} - both fields failed, so nothing is present
# is_bound - distinguishes an unbound (blank) form from a bound (submitted) one
empty_form = BookForm()
print(empty_form.is_bound) # False - no data submitted yet
submitted_form = BookForm(request.POST)
print(submitted_form.is_bound) # True - data was provided
Q5. How do you use Django's built-in validators and create custom field validators?
Validators are callables that raise ValidationError if a value doesn't meet a condition. Django ships several built-in ones and lets you attach custom validators to any form or model field.
from django.core.validators import MinLengthValidator, MaxValueValidator, RegexValidator
from django.core.exceptions import ValidationError
from django import forms
# Built-in validators applied directly to a field
class BookForm(forms.Form):
title = forms.CharField(validators=[MinLengthValidator(3)])
rating = forms.IntegerField(validators=[MaxValueValidator(5)])
isbn = forms.CharField(validators=[
RegexValidator(r'^\d{3}-\d{10}$', 'Enter a valid ISBN (XXX-XXXXXXXXXX)')
])
# CUSTOM validator function - a standalone callable
def validate_even(value):
if value % 2 != 0:
raise ValidationError(f'{value} is not an even number')
class QuantityForm(forms.Form):
quantity = forms.IntegerField(validators=[validate_even])
# Custom validator with a configurable message
def validate_no_profanity(value):
banned_words = ['spam', 'scam']
for word in banned_words:
if word in value.lower():
raise ValidationError(
'%(value)s contains inappropriate content',
params={'value': value},
)
# Applying a custom validator on a MODEL field (reused everywhere the model is used)
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200, validators=[validate_no_profanity])
# Model field validators run when full_clean() is called
# (automatically via ModelForm, or manually):
book = Book(title='spam book')
try:
book.full_clean() # triggers all field validators
except ValidationError as e:
print(e.message_dict)
# Combining multiple validators on one field
class PasswordForm(forms.Form):
password = forms.CharField(validators=[
MinLengthValidator(8),
RegexValidator(r'\d', 'Password must contain at least one digit'),
])
Q6. What are Django formsets and when do you use them?
Formsets manage multiple instances of the same form on one page - useful when a user needs to submit several related items at once (e.g. multiple order line items).
from django import forms
from django.forms import formset_factory, modelformset_factory
class BookForm(forms.Form):
title = forms.CharField(max_length=200)
price = forms.DecimalField()
# Creating a formset class from a regular Form
BookFormSet = formset_factory(BookForm, extra=3) # 3 empty forms by default
def add_books(request):
if request.method == 'POST':
formset = BookFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data: # skip empty extra forms
title = form.cleaned_data['title']
price = form.cleaned_data['price']
Book.objects.create(title=title, price=price)
else:
formset = BookFormSet()
return render(request, 'add_books.html', {'formset': formset})
# ModelFormSet - tied to a model, similar to ModelForm but for MULTIPLE instances
BookModelFormSet = modelformset_factory(Book, fields=['title', 'price'], extra=2)
def edit_books(request):
if request.method == 'POST':
formset = BookModelFormSet(request.POST, queryset=Book.objects.filter(available=True))
if formset.is_valid():
formset.save() # saves ALL forms (creates new, updates existing)
else:
formset = BookModelFormSet(queryset=Book.objects.filter(available=True))
return render(request, 'edit_books.html', {'formset': formset})
<!-- Template rendering a formset -->
<form method='post'>
{% csrf_token %}
{{ formset.management_form }} <!-- REQUIRED - tracks form count for validation -->
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
<button type='submit'>Save All</button>
</form>
# Inline formsets - for editing related objects together (e.g. Author + their Books)
from django.forms import inlineformset_factory
BookInlineFormSet = inlineformset_factory(Author, Book, fields=['title', 'price'], extra=2)
Q7. How do you handle file uploads with Django forms?
File uploads require the form's enctype to be multipart/form-data and the view must access request.FILES separately from request.POST.
from django import forms
from .models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'cover'] # cover is an ImageField/FileField on the model
def upload_book(request):
if request.method == 'POST':
# BOTH request.POST (text fields) and request.FILES (file fields) needed
form = BookForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('book-list')
else:
form = BookForm()
return render(request, 'upload.html', {'form': form})
<!-- Template - enctype is REQUIRED for file uploads -->
<form method='post' enctype='multipart/form-data'>
{% csrf_token %}
{{ form.as_p }}
<button type='submit'>Upload</button>
</form>
<!-- Without enctype='multipart/form-data', file data is silently NOT sent -->
# Model with a file field
class Book(models.Model):
title = models.CharField(max_length=200)
cover = models.ImageField(upload_to='covers/%Y/%m/') # organized by year/month
# Validating uploaded file size/type manually
def validate_file_size(value):
max_size_mb = 5
if value.size > max_size_mb * 1024 * 1024:
raise forms.ValidationError(f'File size cannot exceed {max_size_mb}MB')
class BookForm(forms.ModelForm):
cover = forms.ImageField(validators=[validate_file_size])
class Meta:
model = Book
fields = ['title', 'cover']
# Accessing an uploaded file's properties directly in a view
def check_upload(request):
if request.method == 'POST':
uploaded_file = request.FILES.get('cover')
if uploaded_file:
print(uploaded_file.name, uploaded_file.size, uploaded_file.content_type)
Q8. What is the difference between widgets and fields in Django forms?
| Concept | Responsibility |
|---|---|
| Field | Validation logic and Python data type conversion |
| Widget | HTML rendering - what input element is displayed |
from django import forms
class BookForm(forms.Form):
# A Field can be paired with different Widgets for the SAME data type
title = forms.CharField(max_length=200) # default: <input type='text'>
description = forms.CharField(widget=forms.Textarea) # <textarea> instead
password = forms.CharField(widget=forms.PasswordInput) # <input type='password'>
# Same underlying type (choice), different widgets
category = forms.ChoiceField(
choices=[('fic', 'Fiction'), ('nf', 'Non-Fiction')],
widget=forms.Select # dropdown
)
genres = forms.MultipleChoiceField(
choices=[('sci-fi', 'Sci-Fi'), ('fantasy', 'Fantasy')],
widget=forms.CheckboxSelectMultiple # checkboxes
)
# Customizing widget HTML attributes
price = forms.DecimalField(
widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
)
published_date = forms.DateField(
widget=forms.DateInput(attrs={'type': 'date'}) # HTML5 date picker
)
# Common built-in widgets:
# TextInput, Textarea, PasswordInput, EmailInput, NumberInput,
# Select, SelectMultiple, CheckboxInput, CheckboxSelectMultiple,
# RadioSelect, DateInput, FileInput, HiddenInput
# For ModelForm, customizing widgets via Meta
class BookModelForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'description', 'price']
widgets = {
'description': forms.Textarea(attrs={'rows': 3}),
'price': forms.NumberInput(attrs={'step': '0.01'}),
}
# Custom widget - subclassing an existing widget for reusable behavior
class StarRatingWidget(forms.RadioSelect):
template_name = 'widgets/star_rating.html'
Q9. What is CSRF protection in the context of Django forms, and how does {% csrf_token %} relate to it?
Every form that submits via POST needs {% csrf_token %} to include Django's CSRF protection token, proving the request originated from your own site and not a malicious third party.
<!-- Every POST form MUST include this -->
<form method='post'>
{% csrf_token %}
{{ form.as_p }}
<button type='submit'>Submit</button>
</form>
<!-- Renders as: <input type='hidden' name='csrfmiddlewaretoken' value='...'> -->
<!-- GET forms do NOT need csrf_token - GET requests should be safe/idempotent
and don't modify data, so CSRF protection doesn't apply to them -->
<form method='get'>
<input type='text' name='q'>
<button type='submit'>Search</button>
</form>
<!-- Common error without the token -->
<!-- Forbidden (403) CSRF verification failed. Request aborted. -->
# For AJAX form submissions, include the token in headers instead
# JavaScript
# const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
# fetch('/submit/', {
# method: 'POST',
# headers: { 'X-CSRFToken': csrftoken },
# body: formData
# });
# The CSRF token is tied to the user's session - Django's
# CsrfViewMiddleware validates it automatically for all POST/PUT/
# PATCH/DELETE requests unless the view is explicitly @csrf_exempt
# Testing forms in Django's test client automatically handles CSRF
from django.test import Client
client = Client(enforce_csrf_checks=True) # simulates real browser CSRF behavior
response = client.post('/books/create/', {'title': 'Test'})
# fails with 403 unless the test explicitly fetches and includes a valid token
Django Forms & Validation
What is the difference between Django's Form and ModelForm?
AspectFormModelFormFieldsManually defined, no model tie-inAuto-generated from a model's fieldsSavingNo built-i...
How does Django form validation work with clean_
MethodValidatesWhen to useclean_<fieldname>()A single specific fieldField-specific rules (format, uniqueness)c...
How do you render Django forms in templates, and what are the different rendering options?
Rendering methodControl levelUse case{{ form }}Least controlQuick prototyping{{ form.as_p }} / as_table / as_u...
What is the difference between form.is_valid(), form.errors, and form.cleaned_data?
Attribute/MethodTypeContainsis_valid()Method, returns boolTriggers validation, returns True/FalseerrorsDict-li...
How do you use Django's built-in validators and create custom field validators?
Validators are callables that raise ValidationError if a value doesn't meet a condition. Django ships several...
What are Django formsets and when do you use them?
Formsets manage multiple instances of the same form on one page - useful when a user needs to submit several r...
How do you handle file uploads with Django forms?
File uploads require the form's enctype to be multipart/form-data and the view must access request.FILES separ...
What is the difference between widgets and fields in Django forms?
ConceptResponsibilityFieldValidation logic and Python data type conversionWidgetHTML rendering - what input el...
What is CSRF protection in the context of Django forms, and how does {% csrf_token %} relate to it?
Every form that submits via POST needs {% csrf_token %} to include Django's CSRF protection token, proving the...