Skip to content

第8章:Django表单(Forms)

8.2 ModelForm

ModelForm创建

ModelForm是Django提供的一个强大的表单类,它可以根据模型自动生成表单字段。这大大减少了重复代码,特别是在创建与模型字段对应的表单时。

基本的ModelForm定义:

python
from django import forms
from .models import Article

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

完整的ModelForm示例:

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': '请输入文章标题'
            }),
            'content': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 10,
                'placeholder': '请输入文章内容'
            }),
            'category': forms.Select(attrs={'class': 'form-control'}),
            'tags': forms.CheckboxSelectMultiple(),
            'is_published': forms.CheckboxInput(attrs={'class': 'form-check-input'})
        }
        labels = {
            'title': '文章标题',
            'content': '文章内容',
            'category': '分类',
            'tags': '标签',
            'is_published': '是否发布'
        }
        help_texts = {
            'title': '请输入一个吸引人的标题',
            'tags': '可选择多个标签'
        }

字段自定义

ModelForm允许你自定义字段,包括字段类型、验证规则和小部件:

python
from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    # 添加额外字段
    summary = forms.CharField(
        max_length=300,
        widget=forms.Textarea(attrs={'rows': 3}),
        required=False,
        label='文章摘要'
    )
    
    class Meta:
        model = Article
        fields = ['title', 'summary', 'content', 'category', 'tags', 'is_published']
        # 排除某些字段
        # exclude = ['author', 'created_at', 'updated_at']
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 动态修改字段属性
        self.fields['title'].widget.attrs.update({
            'class': 'form-control',
            'placeholder': '请输入文章标题'
        })
        
        # 根据条件过滤选择项
        if 'category' in self.fields:
            self.fields['category'].queryset = Category.objects.filter(is_active=True)
    
    # 自定义字段验证
    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) < 5:
            raise forms.ValidationError("标题至少需要5个字符")
        return title
    
    # 重写save方法
    def save(self, commit=True):
        article = super().save(commit=False)
        # 在保存前进行额外处理
        if not article.slug:
            article.slug = slugify(article.title)
        
        if commit:
            article.save()
            self.save_m2m()  # 保存多对多关系
        
        return article

保存数据

ModelForm提供了简单的方式来保存表单数据到数据库:

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():
            # 方法1:直接保存
            article = form.save()
            
            # 方法2:保存前设置额外字段
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            form.save_m2m()  # 保存多对多关系
            
            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})

联系表单示例

以下是一个完整的联系表单示例:

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': '请输入您的姓名'
            }),
            'email': forms.EmailInput(attrs={
                'class': 'form-control',
                'placeholder': '请输入您的邮箱'
            }),
            'subject': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': '请输入主题'
            }),
            'message': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 5,
                'placeholder': '请输入您的消息'
            })
        }
        labels = {
            'name': '姓名',
            'email': '邮箱',
            'subject': '主题',
            '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, '您的消息已发送成功!')
            return redirect('contact')
    else:
        form = ContactForm()
    
    return render(request, 'contact.html', {'form': form})

<!-- contact.html -->
<div class="container">
    <h2>联系我们</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">发送消息</button>
    </form>
</div>

ModelForm大大简化了表单处理过程,特别是在与数据库模型交互时,提供了自动化的字段映射和验证功能。

小结

ModelForm是Django表单系统的重要组成部分:

  1. ✅ 根据模型自动生成表单字段
  2. ✅ 支持字段自定义和验证
  3. ✅ 简化数据保存到数据库的过程
  4. ✅ 提供灵活的字段控制(fields/exclude)
  5. ✅ 支持小部件和标签的自定义

掌握ModelForm能够显著提高开发效率,减少重复代码。

下一篇

我们将学习表单集和高级表单特性。

8.3 表单高级特性 →

目录

返回课程目录

Released under the Apache 2.0 License.