Skip to content

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:

python
# 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:

python
# 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.

python
# 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:

python
# 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:

  1. ✅ Formsets support batch creation and editing
  2. ✅ Inline formsets handle model relationships
  3. ✅ File upload handling and validation
  4. ✅ CSRF protection mechanisms prevent attacks
  5. ✅ 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.

9.1 Authentication System →

Contents

Back to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).