Skip to content

Chapter 8: Django Forms

8.2 ModelForm

ModelForm Creation

ModelForm is a powerful form class provided by Django that can automatically generate form fields based on models. This greatly reduces repetitive code, especially when creating forms that correspond to model fields.

Basic ModelForm definition:

python
from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'category', 'tags']

Complete ModelForm example:

python
# models.py
from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)
    
    def __str__(self):
        return self.name

class Tag(models.Model):
    name = models.CharField(max_length=50)
    
    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField(Tag, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_published = models.BooleanField(default=False)
    
    def __str__(self):
        return self.title

# forms.py
from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'category', 'tags', 'is_published']
        widgets = {
            'title': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': 'Please enter article title'
            }),
            'content': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 10,
                'placeholder': 'Please enter article content'
            }),
            'category': forms.Select(attrs={'class': 'form-control'}),
            'tags': forms.CheckboxSelectMultiple(),
            'is_published': forms.CheckboxInput(attrs={'class': 'form-check-input'})
        }
        labels = {
            'title': 'Article Title',
            'content': 'Article Content',
            'category': 'Category',
            'tags': 'Tags',
            'is_published': 'Publish'
        }
        help_texts = {
            'title': 'Please enter an attractive title',
            'tags': 'Multiple tags can be selected'
        }

Field Customization

ModelForm allows you to customize fields, including field types, validation rules, and widgets:

python
from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    # Add extra field
    summary = forms.CharField(
        max_length=300,
        widget=forms.Textarea(attrs={'rows': 3}),
        required=False,
        label='Article Summary'
    )
    
    class Meta:
        model = Article
        fields = ['title', 'summary', 'content', 'category', 'tags', 'is_published']
        # Exclude certain fields
        # exclude = ['author', 'created_at', 'updated_at']
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Dynamically modify field attributes
        self.fields['title'].widget.attrs.update({
            'class': 'form-control',
            'placeholder': 'Please enter article title'
        })
        
        # Filter selection options based on conditions
        if 'category' in self.fields:
            self.fields['category'].queryset = Category.objects.filter(is_active=True)
    
    # Custom field validation
    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) < 5:
            raise forms.ValidationError("Title must be at least 5 characters")
        return title
    
    # Override save method
    def save(self, commit=True):
        article = super().save(commit=False)
        # Perform additional processing before saving
        if not article.slug:
            article.slug = slugify(article.title)
        
        if commit:
            article.save()
            self.save_m2m()  # Save many-to-many relationships
        
        return article

Saving Data

ModelForm provides a simple way to save form data to the database:

python
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from .forms import ArticleForm
from .models import Article

def create_article(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            # Method 1: Save directly
            article = form.save()
            
            # Method 2: Set extra fields before saving
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            form.save_m2m()  # Save many-to-many relationships
            
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm()
    
    return render(request, 'articles/create.html', {'form': form})

def edit_article(request, pk):
    article = get_object_or_404(Article, pk=pk)
    
    if request.method == 'POST':
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            article = form.save()
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm(instance=article)
    
    return render(request, 'articles/edit.html', {'form': form, 'article': article})

Contact Form Example

Here's a complete contact form example:

python
# models.py
from django.db import models

class ContactMessage(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()
    subject = models.CharField(max_length=200)
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    is_read = models.BooleanField(default=False)
    
    def __str__(self):
        return f"{self.name} - {self.subject}"

# forms.py
from django import forms
from .models import ContactMessage

class ContactForm(forms.ModelForm):
    class Meta:
        model = ContactMessage
        fields = ['name', 'email', 'subject', 'message']
        widgets = {
            'name': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': 'Please enter your name'
            }),
            'email': forms.EmailInput(attrs={
                'class': 'form-control',
                'placeholder': 'Please enter your email'
            }),
            'subject': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': 'Please enter subject'
            }),
            'message': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 5,
                'placeholder': 'Please enter your message'
            })
        }
        labels = {
            'name': 'Name',
            'email': 'Email',
            'subject': 'Subject',
            'message': 'Message'
        }

# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Your message has been sent successfully!')
            return redirect('contact')
    else:
        form = ContactForm()
    
    return render(request, 'contact.html', {'form': form})

<!-- contact.html -->
<div class="container">
    <h2>Contact Us</h2>
    
    {% if messages %}
        {% for message in messages %}
            <div class="alert alert-success">{{ message }}</div>
        {% endfor %}
    {% endif %}
    
    <form method="post">
        {% csrf_token %}
        
        <div class="mb-3">
            {{ form.name.label_tag }}
            {{ form.name }}
            {% if form.name.errors %}
                <div class="text-danger">{{ form.name.errors }}</div>
            {% endif %}
        </div>
        
        <div class="mb-3">
            {{ form.email.label_tag }}
            {{ form.email }}
            {% if form.email.errors %}
                <div class="text-danger">{{ form.email.errors }}</div>
            {% endif %}
        </div>
        
        <div class="mb-3">
            {{ form.subject.label_tag }}
            {{ form.subject }}
            {% if form.subject.errors %}
                <div class="text-danger">{{ form.subject.errors }}</div>
            {% endif %}
        </div>
        
        <div class="mb-3">
            {{ form.message.label_tag }}
            {{ form.message }}
            {% if form.message.errors %}
                <div class="text-danger">{{ form.message.errors }}</div>
            {% endif %}
        </div>
        
        <button type="submit" class="btn btn-primary">Send Message</button>
    </form>
</div>

ModelForm greatly simplifies the form processing workflow, especially when interacting with database models, providing automated field mapping and validation functionality.

Summary

ModelForm is an important component of the Django form system:

  1. ✅ Automatically generates form fields based on models
  2. ✅ Supports field customization and validation
  3. ✅ Simplifies the process of saving data to the database
  4. ✅ Provides flexible field control (fields/exclude)
  5. ✅ Supports customization of widgets and labels

Mastering ModelForm can significantly improve development efficiency and reduce repetitive code.

Next

We will learn about formsets and advanced form features.

8.3 Advanced Form Features →

Contents

Back to Course Outline

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