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).