Django 表单处理

Django 表单简介

Django 表单是处理 HTML 表单创建和验证的强大工具。它们提供了一种简单的方法来定义表单字段、验证用户输入、显示错误消息以及在模板中渲染表单。

1
定义表单
创建 Form 类
2
视图处理
处理 GET/POST 请求
3
模板渲染
在模板中显示表单
4
验证数据
清理和验证输入
表单的优势
  • 自动生成 HTML 表单元素
  • 内置数据验证和清理
  • CSRF 保护
  • 错误消息处理
  • 与模型紧密集成
  • 可重用的表单逻辑
表单类型
类型 描述 使用场景
Form 普通表单 通用表单需求
ModelForm 模型表单 与数据库模型交互
FormSet 表单集 处理多个表单
ModelFormSet 模型表单集 处理多个模型实例

创建基本表单

在 Django 中创建表单需要定义一个继承自 forms.Form 的类。

定义表单类
forms.py
from django import forms

class ContactForm(forms.Form):
    # 文本字段
    name = forms.CharField(
        max_length=100,
        label='您的姓名',
        widget=forms.TextInput(attrs={
            'class': 'form-control',
            'placeholder': '请输入姓名'
        })
    )

    # 邮箱字段
    email = forms.EmailField(
        label='邮箱地址',
        widget=forms.EmailInput(attrs={
            'class': 'form-control',
            'placeholder': 'example@email.com'
        })
    )

    # 选择字段
    subject = forms.ChoiceField(
        choices=[
            ('', '请选择主题'),
            ('general', '一般咨询'),
            ('support', '技术支持'),
            ('billing', '账单问题'),
        ],
        label='主题',
        widget=forms.Select(attrs={'class': 'form-control'})
    )

    # 文本区域
    message = forms.CharField(
        label='留言内容',
        widget=forms.Textarea(attrs={
            'class': 'form-control',
            'rows': 4,
            'placeholder': '请输入您的留言...'
        })
    )

    # 布尔字段
    newsletter = forms.BooleanField(
        required=False,
        label='订阅新闻简报',
        widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
    )
渲染的表单效果
联系表单
常用表单字段
字段类型 描述 HTML 控件
CharField 文本字段 <input type="text">
EmailField 邮箱字段 <input type="email">
IntegerField 整数字段 <input type="number">
BooleanField 布尔字段 <input type="checkbox">
DateField 日期字段 <input type="date">
ChoiceField 选择字段 <select>

在视图中处理表单

表单处理通常在视图函数中进行,需要处理 GET(显示空表单)和 POST(处理提交数据)请求。

基于函数的视图
views.py
from django.shortcuts import render, redirect
from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        # 使用提交的数据创建表单实例
        form = ContactForm(request.POST)

        if form.is_valid():
            # 表单数据已验证和清理
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            subject = form.cleaned_data['subject']
            message = form.cleaned_data['message']
            newsletter = form.cleaned_data['newsletter']

            # 处理数据(保存到数据库、发送邮件等)
            print(f"收到来自 {name} 的留言: {message}")

            # 重定向到成功页面
            return redirect('contact_success')
    else:
        # GET 请求,显示空表单
        form = ContactForm()

    # 渲染模板(包含表单实例)
    return render(request, 'contact.html', {'form': form})

def contact_success(request):
    return render(request, 'contact_success.html')
基于类的视图
views.py
from django.views.generic import FormView
from .forms import ContactForm
from django.urls import reverse_lazy

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = reverse_lazy('contact_success')

    def form_valid(self, form):
        # 表单验证通过时的处理
        name = form.cleaned_data['name']
        email = form.cleaned_data['email']
        message = form.cleaned_data['message']

        # 处理表单数据
        print(f"收到来自 {name} 的留言")

        # 调用父类方法完成重定向
        return super().form_valid(form)
表单处理流程
表单验证成功:
  • 数据通过所有验证规则
  • 数据被清理并转换为 Python 类型
  • 可通过 form.cleaned_data 访问
  • 执行 form_valid 逻辑
表单验证失败:
  • 数据未通过验证规则
  • 错误信息存储在 form.errors
  • 重新显示表单和错误消息
  • 执行 form_invalid 逻辑

在模板中渲染表单

Django 提供了多种在模板中渲染表单的方式,从自动渲染到手动控制每个字段。

自动渲染整个表单
contact.html
<form method="post">
    {% csrf_token %}

    {# 自动渲染所有表单字段 #}
    {{ form.as_p }}

    <button type="submit" class="btn btn-primary">提交</button>
</form>
手动渲染每个字段
contact.html
<form method="post">
    {% csrf_token %}

    {# 显示非字段错误 #}
    {% if form.non_field_errors %}
        <div class="alert alert-danger">
            {% for error in form.non_field_errors %}
                {{ error }}
            {% endfor %}
        </div>
    {% endif %}

    {# 手动渲染每个字段 #}
    <div class="form-group">
        {{ form.name.label_tag }}
                                                {{ form.name }}
        {% if form.name.errors %}
            <div class="invalid-feedback">
                {% for error in form.name.errors %}
                    {{ error }}
                {% endfor %}
            </div>
        {% endif %}
    </div>

    <!-- 其他字段类似 -->

    <button type="submit" class="btn btn-primary">提交</button>
</form>
使用循环渲染字段
contact.html
<form method="post" class="needs-validation" novalidate>
    {% csrf_token %}

    {% for field in form %}
        <div class="form-group">
            {{ field.label_tag }}

            {# 添加 Bootstrap 类到表单控件 #}
            {{ field|add_class:"form-control" }}

            {# 显示字段帮助文本 #}
            {% if field.help_text %}
                <small class="form-text text-muted">
                    {{ field.help_text }}
                </small>
            {% endif %}

            {# 显示字段错误 #}
            {% if field.errors %}
                <div class="invalid-feedback d-block">
                    {% for error in field.errors %}
                        {{ error }}
                    {% endfor %}
                </div>
            {% endif %}
        </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">提交</button>
</form>
渲染方法比较
  • {{ form.as_p }} - 包裹在 <p> 标签中
  • {{ form.as_table }} - 表格行(需要在 <table> 中)
  • {{ form.as_ul }} - 列表项(需要在 <ul> 中)
  • 手动渲染 - 完全控制 HTML 结构
自定义模板过滤器
templatetags/form_filters.py
from django import template

register = template.Library()

@register.filter
def add_class(field, css_class):
    """为表单字段添加 CSS 类"""
    return field.as_widget(attrs={"class": css_class})

@register.filter
def add_placeholder(field, placeholder):
    """为表单字段添加占位符"""
    return field.as_widget(attrs={"placeholder": placeholder})

表单验证

Django 表单提供了强大的数据验证功能,包括字段级验证和表单级验证。

字段验证器

forms.py - 内置验证
from django import forms
from django.core.validators import validate_email, MinLengthValidator

class UserForm(forms.Form):
    username = forms.CharField(
        max_length=30,
        min_length=3,
        validators=[MinLengthValidator(3)],
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名至少3个字符',
            'max_length': '用户名不能超过30个字符'
        }
    )

    email = forms.EmailField(
        validators=[validate_email],
        error_messages={
            'required': '邮箱不能为空',
            'invalid': '请输入有效的邮箱地址'
        }
    )

    age = forms.IntegerField(
        min_value=0,
        max_value=150,
        error_messages={
            'min_value': '年龄不能小于0',
            'max_value': '年龄不能大于150'
        }
    )
forms.py - 自定义验证
from django import forms
from django.core.exceptions import ValidationError

def validate_even(value):
    """自定义验证器:检查是否为偶数"""
    if value % 2 != 0:
        raise ValidationError('%(value)s 不是偶数',
                            params={'value': value})

class CustomForm(forms.Form):
    number = forms.IntegerField(
        validators=[validate_even]
    )

    def clean_username(self):
        """字段级清理和验证"""
        username = self.cleaned_data['username']

        # 检查用户名是否已存在
        if User.objects.filter(username=username).exists():
            raise ValidationError('用户名已存在')

        return username

    def clean(self):
        """表单级验证"""
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')

        # 检查密码是否匹配
        if password and confirm_password and password != confirm_password:
            raise ValidationError('两次输入的密码不匹配')

        return cleaned_data

验证错误处理

views.py - 错误处理
def register_view(request):
    if request.method == 'POST':
        form = UserForm(request.POST)
        if form.is_valid():
            # 处理有效数据
            return redirect('success')
        else:
            # 处理验证错误
            print("表单错误:", form.errors)
            print("特定字段错误:", form['username'].errors)
    else:
        form = UserForm()

    return render(request, 'register.html', {'form': form})
模板中的错误显示
<form method="post">
    {% csrf_token %}

    {# 显示表单级错误 #}
    {% if form.non_field_errors %}
        <div class="alert alert-danger">
            {{ form.non_field_errors }}
        </div>
    {% endif %}

    <div class="form-group">
        <label for="{{ form.username.id_for_label }}">用户名</label>
        {{ form.username }}

        {# 显示字段级错误 #}
        {% if form.username.errors %}
            <div class="alert alert-danger mt-2">
                {{ form.username.errors }}
            </div>
        {% endif %}
    </div>

    <button type="submit">注册</button>
</form>

ModelForm - 模型表单

ModelForm 可以根据 Django 模型自动创建表单,简化了模型实例的创建和更新操作。

创建 ModelForm
models.py
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.CharField(max_length=100)
    published_date = models.DateTimeField(auto_now_add=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', 'author', 'is_published']
        # 或者使用 exclude 排除字段
        # exclude = ['published_date']

        labels = {
            'title': '文章标题',
            'content': '内容',
            'author': '作者',
            'is_published': '是否发布'
        }

        widgets = {
            'title': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': '请输入文章标题'
            }),
            'content': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 5,
                'placeholder': '请输入文章内容...'
            }),
            'author': forms.TextInput(attrs={
                'class': 'form-control'
            }),
            'is_published': forms.CheckboxInput(attrs={
                'class': 'form-check-input'
            })
        }
使用 ModelForm 的视图
views.py - 创建对象
from django.shortcuts import render, redirect
from .forms import ArticleForm
from .models import Article

def create_article(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            # 自动创建 Article 对象并保存
            article = form.save()
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm()

    return render(request, 'article_form.html', {'form': form})

def update_article(request, pk):
    article = Article.objects.get(pk=pk)

    if request.method == 'POST':
        # 使用 instance 参数更新现有对象
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm(instance=article)

    return render(request, 'article_form.html', {'form': form})
ModelForm 的优势
  • 自动字段生成 - 根据模型字段自动创建表单字段
  • 数据验证 - 继承模型的验证规则
  • 简化保存 - form.save() 自动创建/更新对象
  • 字段控制 - 使用 fieldsexclude 控制显示字段
  • 自定义 - 可以覆盖默认行为和添加额外字段
提示: 使用 fields = '__all__' 可以包含所有模型字段,但在生产环境中建议明确列出需要的字段。

高级表单功能

表单集 (FormSet)

使用表单集
from django.forms import formset_factory
from .forms import ContactForm

# 创建表单集
ContactFormSet = formset_factory(
    ContactForm,
    extra=2,  # 初始显示的表单数量
    max_num=5  # 最大表单数量
)

def manage_contacts(request):
    if request.method == 'POST':
        formset = ContactFormSet(request.POST)
        if formset.is_valid():
            for form in formset:
                if form.cleaned_data:  # 跳过空表单
                    # 处理每个表单的数据
                    name = form.cleaned_data['name']
                    email = form.cleaned_data['email']
                    # ... 保存数据
            return redirect('success')
    else:
        formset = ContactFormSet()

    return render(request, 'contact_formset.html', {'formset': formset})
模板中的表单集
<form method="post">
    {% csrf_token %}
    {{ formset.management_form }}

    {% for form in formset %}
        <div class="form-row">
            <div class="col">
                {{ form.name.label_tag }}
                                                {{ form.name }}
            </div>
            <div class="col">
                {{ form.email.label_tag }}
                                                {{ form.email }}
            </div>
        </div>
        <hr>
    {% endfor %}

    <button type="submit" class="btn btn-primary">保存所有联系人</button>
</form>
注意: 不要忘记在模板中包含 {{ formset.management_form }},它包含了表单集所需的元数据。

文件上传

文件上传表单
from django import forms

class UploadForm(forms.Form):
    title = forms.CharField(max_length=100)
    file = forms.FileField(
        label='选择文件',
        widget=forms.FileInput(attrs={
            'class': 'form-control-file'
        })
    )

    # 多文件上传
    files = forms.FileField(
        widget=forms.FileInput(attrs={
            'multiple': True,
            'class': 'form-control-file'
        })
    )
处理文件上传
def upload_file(request):
    if request.method == 'POST':
        form = UploadForm(request.POST, request.FILES)
        if form.is_valid():
            # 处理上传的文件
            uploaded_file = request.FILES['file']

            # 保存文件
            with open(f'uploads/{uploaded_file.name}', 'wb+') as destination:
                for chunk in uploaded_file.chunks():
                    destination.write(chunk)

            return redirect('success')
    else:
        form = UploadForm()

    return render(request, 'upload.html', {'form': form})
重要: 处理文件上传时,确保表单的 enctype="multipart/form-data" 并且在视图中同时传递 request.POSTrequest.FILES

表单最佳实践

安全考虑
  • 始终使用 {% csrf_token %}
  • 验证所有用户输入
  • 对敏感数据使用 HTTPS
  • 限制文件上传类型和大小
  • 使用 Django 的内置验证器
# 限制文件类型
from django.core.validators import FileExtensionValidator

file = forms.FileField(
    validators=[FileExtensionValidator(
        ['pdf', 'doc', 'docx']
    )]
)
用户体验优化
  • 提供清晰的标签和占位符
  • 使用适当的输入类型(email, tel, url等)
  • 实现客户端验证
  • 显示有意义的错误消息
  • 保持表单简洁明了
  • 使用 Bootstrap 或其他 CSS 框架美化
<input type="email"
    class="form-control"
    placeholder="name@example.com"
    required>
代码组织
  • 将表单定义放在单独的 forms.py 文件中
  • 为复杂表单创建自定义验证器
  • 使用类视图简化表单处理逻辑
  • 创建可重用的表单混合类 (mixins)
  • 使用模板标签和过滤器自定义表单渲染
性能优化
  • 合理使用表单集,避免过多表单
  • 对大型数据集使用分页
  • 缓存不经常变化的表单数据
  • 优化数据库查询(使用 select_relatedprefetch_related
  • 考虑使用 AJAX 提交表单