Chapter 8: Django Forms
8.3 Advanced Form Features
Formsets
Formsets are collections of forms of the same type, allowing users to create or edit multiple related objects on a single page.
Basic formset usage:
# forms.py
from django import forms
class ArticleForm(forms.Form):
title = forms.CharField(max_length=100)
content = forms.CharField(widget=forms.Textarea)
# Using formsets
from django.forms import formset_factory
# Create formset class
ArticleFormSet = formset_factory(ArticleForm, extra=2)
# Use in views
def create_articles(request):
if request.method == 'POST':
formset = ArticleFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data:
# Process each form's data
title = form.cleaned_data['title']
content = form.cleaned_data['content']
# Save to database
return redirect('success')
else:
formset = ArticleFormSet()
return render(request, 'create_articles.html', {'formset': formset})
<!-- create_articles.html -->
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div class="article-form">
<h3>Article {{ forloop.counter }}</h3>
{{ form.as_p }}
</div>
{% endfor %}
<button type="submit">Submit</button>
</form>ModelForm formsets:
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publication_date = models.DateField()
# forms.py
from django import forms
from .models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'publication_date']
# Create ModelForm formset
from django.forms import modelformset_factory
BookFormSet = modelformset_factory(
Book,
form=BookForm,
extra=1,
can_delete=True # Allow deletion
)
# Use in views
def edit_books(request, author_id):
author = get_object_or_404(Author, id=author_id)
BookFormSet = modelformset_factory(
Book,
form=BookForm,
extra=1,
can_delete=True
)
if request.method == 'POST':
formset = BookFormSet(request.POST, queryset=author.book_set.all())
if formset.is_valid():
instances = formset.save(commit=False)
for instance in instances:
instance.author = author
instance.save()
formset.save_m2m()
return redirect('author_detail', author_id=author.id)
else:
formset = BookFormSet(queryset=author.book_set.all())
return render(request, 'edit_books.html', {
'formset': formset,
'author': author
})Inline Formsets
Inline formsets are used to handle relationships between main models and related models, especially for one-to-many relationships.
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publication_date = models.DateField()
# forms.py
from django.forms import inlineformset_factory
# Create inline formset
BookInlineFormSet = inlineformset_factory(
Author,
Book,
fields=['title', 'publication_date'],
extra=1,
can_delete=True
)
# views.py
def edit_author_books(request, author_id):
author = get_object_or_404(Author, id=author_id)
if request.method == 'POST':
formset = BookInlineFormSet(request.POST, instance=author)
if formset.is_valid():
formset.save()
return redirect('author_detail', author_id=author.id)
else:
formset = BookInlineFormSet(instance=author)
return render(request, 'edit_author_books.html', {
'formset': formset,
'author': author
})
<!-- edit_author_books.html -->
<form method="post">
{% csrf_token %}
<h2>Edit Author: {{ author.name }}</h2>
{{ formset.management_form }}
{% for form in formset %}
<div class="book-form">
{% if form.non_field_errors %}
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
{% endif %}
{% for field in form %}
<div class="mb-3">
{{ field.label_tag }}
{{ field }}
{% if field.errors %}
<div class="text-danger">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Save</button>
<a href="{% url 'author_detail' author.id %}" class="btn btn-secondary">Cancel</a>
</form>File Upload Handling
Django provides powerful file upload handling capabilities:
# models.py
from django.db import models
class Document(models.Model):
title = models.CharField(max_length=200)
file = models.FileField(upload_to='documents/')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# forms.py
from django import forms
from .models import Document
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ['title', 'file']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'file': forms.FileInput(attrs={'class': 'form-control'})
}
# Handle different types of file uploads
class ImageUploadForm(forms.Form):
title = forms.CharField(max_length=100)
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data['image']
if image:
# Validate file size
if image.size > 5 * 1024 * 1024: # 5MB
raise forms.ValidationError("Image size cannot exceed 5MB")
# Validate file type
if not image.content_type.startswith('image/'):
raise forms.ValidationError("Please upload a valid image file")
return image
# views.py
from django.shortcuts import render, redirect
from django.conf import settings
import os
def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
document = form.save()
return redirect('document_list')
else:
form = DocumentForm()
return render(request, 'upload_document.html', {'form': form})
# Handle multiple file uploads
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", forms.ClearableFileInput(attrs={"multiple": True}))
super().__init__(*args, **kwargs)
class MultipleFileForm(forms.Form):
files = MultipleFileField()
def clean_files(self):
files = self.files.getlist('files')
for file in files:
if file.size > 10 * 1024 * 1024: # 10MB
raise forms.ValidationError("Individual file size cannot exceed 10MB")
return files
def upload_multiple_files(request):
if request.method == 'POST':
form = MultipleFileForm(request.POST, request.FILES)
if form.is_valid():
files = request.FILES.getlist('files')
for file in files:
# Save each file
Document.objects.create(
title=file.name,
file=file
)
return redirect('document_list')
else:
form = MultipleFileForm()
return render(request, 'upload_multiple.html', {'form': form})File upload template example:
<!-- upload_document.html -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">{{ form.title.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.file.label_tag }}
{{ form.file }}
{% if form.file.errors %}
<div class="text-danger">{{ form.file.errors }}</div>
{% endif %}
<div class="form-text">Supported file types: PDF, DOC, DOCX, TXT</div>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
<!-- Display uploaded files -->
{% for document in documents %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">{{ document.title }}</h5>
<p class="card-text">
Upload time: {{ document.uploaded_at|date:"Y-m-d H:i" }}
</p>
<a href="{{ document.file.url }}" class="btn btn-primary" target="_blank">
Download File
</a>
</div>
</div>
{% endfor %}CSRF Protection
Django has built-in CSRF (Cross-Site Request Forgery) protection mechanisms:
<!-- Use CSRF token in forms -->
<form method="post">
{% csrf_token %}
<!-- Form fields -->
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Login</button>
</form>Using CSRF tokens in AJAX requests:
// Method 1: Using jQuery
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send CSRF token for local requests
xhr.setRequestHeader("X-CSRFToken", $('[name=csrfmiddlewaretoken]').val());
}
}
});
// Method 2: Get CSRF token from cookie
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
$.ajax({
url: '/api/endpoint/',
type: 'POST',
headers: {
'X-CSRFToken': csrftoken
},
data: {
// Form data
},
success: function(data) {
// Handle successful response
}
});Configuring CSRF in Django settings:
# settings.py
MIDDLEWARE = [
# ... other middleware
'django.middleware.csrf.CsrfViewMiddleware',
# ... other middleware
]
# CSRF settings
CSRF_COOKIE_HTTPONLY = True # Prevent JavaScript access to CSRF cookie
CSRF_COOKIE_SECURE = True # Only send CSRF cookie over HTTPS
CSRF_TRUSTED_ORIGINS = [
'https://yourdomain.com',
'https://www.yourdomain.com',
]Custom CSRF failure handling:
# views.py
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
# Exempt CSRF protection (use with caution)
@csrf_exempt
def api_view(request):
if request.method == 'POST':
# Handle API request
return JsonResponse({'status': 'success'})
return JsonResponse({'status': 'error'})
# Custom CSRF failure handling
from django.views.decorators.csrf import requires_csrf_token
@requires_csrf_token
def custom_csrf_failure(request, reason=""):
return render(request, 'csrf_failure.html', {
'reason': reason
})These advanced form features enable Django to handle complex form scenarios, including batch operations, file uploads, and security protection.
Summary
Django's advanced form features provide powerful functionality:
- ✅ Formsets support batch creation and editing
- ✅ Inline formsets handle model relationships
- ✅ File upload handling and validation
- ✅ CSRF protection mechanisms prevent attacks
- ✅ Flexible custom validation and error handling
Mastering these advanced features can build more complex and secure form applications.
Next
We will learn about the Django user authentication system.