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:
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'category', 'tags']Complete ModelForm example:
# 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:
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 articleSaving Data
ModelForm provides a simple way to save form data to the database:
# 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:
# 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:
- ✅ Automatically generates form fields based on models
- ✅ Supports field customization and validation
- ✅ Simplifies the process of saving data to the database
- ✅ Provides flexible field control (fields/exclude)
- ✅ 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.