Django表单类


Django表单类

Django 的表单类(FormModelForm)是处理用户输入数据的核心组件,它们提供了数据验证、清洗、HTML 渲染和错误提示等功能。

一、基础概念

1. 表单类的两种类型

  • Form:普通表单类,手动定义字段和验证逻辑。
  • ModelForm:模型表单类,自动映射模型字段,简化数据库操作。

2. 核心功能

  • 数据验证:检查输入是否符合规则(如必填项、格式、唯一性)。
  • 数据清洗:转换数据类型(如字符串转整数)、去除无效字符。
  • 错误处理:自动生成友好的错误信息。
  • HTML 渲染:快速生成表单 HTML(如 {{ form.as_p }})。

二、普通表单类(Form

1. 定义表单类

# forms.py
from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(label='姓名', max_length=100)
    email = forms.EmailField(label='邮箱')
    message = forms.CharField(label='留言', widget=forms.Textarea)

2. 字段类型

常用字段类型:

  • CharField:文本字段(默认单行)
  • EmailField:邮箱字段(自动验证格式)
  • IntegerField:整数字段
  • BooleanField:布尔字段(复选框)
  • DateField:日期字段(格式:YYYY-MM-DD)
  • ChoiceField:下拉选择框
  • FileField:文件上传字段

3. 字段参数

常用参数:

  • label:显示标签
  • required:是否必填(默认 True
  • max_length/min_length:最大 / 最小长度
  • widget:自定义 HTML 控件(如 TextareaPasswordInput
  • initial:初始值
  • help_text:帮助文本
  • error_messages:自定义错误信息

4. 在视图中使用

# views.py
from django.shortcuts import render
from .forms import ContactForm

def contact_view(request):
    # 处理表单提交的视图函数
    if request.method == 'POST':
        # 当用户提交表单时,创建绑定了POST数据的表单实例
        form = ContactForm(request.POST)
        # 验证表单数据
        if form.is_valid():
            # 从验证后的cleaned_data中获取字段值
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']

            # TODO: 处理表单数据的逻辑
            # 这里可以添加发送邮件、保存到数据库等操作
            # 例如: send_email(name, email, message)

            # 成功处理后重定向到成功页面
            return render(request, 'success.html')
    else:
        # GET请求(首次访问页面)时创建未绑定数据的空表单
        form = ContactForm()  # GET请求返回空表单

    # 渲染包含表单的模板,无论表单是否有效都会执行到这里
    return render(request, 'contact.html', {'form': form})

5. 在模板中渲染

<!-- contact.html -->
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}  <!-- 快速渲染为 <p> 标签包裹的表单 -->
    <button type="submit">提交</button>
</form>

更精细的控制:

<form method="post">
    {% csrf_token %}
    <div>
        <label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
        {{ form.name }}
        {% if form.name.errors %}
            <div class="error">{{ form.name.errors.as_text }}</div>
        {% endif %}
    </div>
    <!-- 其他字段类似 -->
    <button type="submit">提交</button>
</form>

三、模型表单类(ModelForm

1. 定义模型表单

# models.py
from django.db import models

class Comment(models.Model):
    content = models.TextField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

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

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['content']  # 只让用户填写 content 字段
        # 或排除某些字段: exclude = ['user', 'created_at']

2. 自动映射字段

ModelForm 会自动根据模型字段类型创建表单字段:

  • CharFieldmodels.CharField
  • IntegerFieldmodels.IntegerField
  • BooleanFieldmodels.BooleanField
  • ForeignKeyModelChoiceField(下拉选择框)

3. 在视图中使用

# views.py
from django.shortcuts import render, redirect
from .forms import CommentForm

def add_comment(request):
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)  # 创建对象但暂不保存
            comment.user = request.user  # 设置关联用户
            comment.save()  # 保存到数据库
            return redirect('success')
    else:
        form = CommentForm()
    return render(request, 'add_comment.html', {'form': form})

四、表单验证

1. 字段级验证

在表单类中定义 clean_<field_name>() 方法:

class ContactForm(forms.Form):
    email = forms.EmailField()

    def clean_email(self):
        email = self.cleaned_data['email']
        if "spam" in email:
            raise forms.ValidationError("邮箱地址包含禁用关键词")
        return email

2. 表单级验证

定义 clean() 方法验证多个字段:

class DateRangeForm(forms.Form):
    start_date = forms.DateField()
    end_date = forms.DateField()

    def clean(self):
        cleaned_data = super().clean()
        start_date = cleaned_data.get("start_date")
        end_date = cleaned_data.get("end_date")

        if start_date and end_date and start_date > end_date:
            raise forms.ValidationError("开始日期不能晚于结束日期")
        return cleaned_data

3. 自定义验证器

可重用的验证逻辑:

# validators.py
from django.core.exceptions import ValidationError

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(f"{value} 不是偶数")

# forms.py
from .validators import validate_even

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

五、高级用法

1. 动态修改表单字段

class MyForm(forms.Form):
    choice = forms.ChoiceField(choices=[])

    def __init__(self, *args, **kwargs):
        extra_choices = kwargs.pop('extra_choices', [])
        super().__init__(*args, **kwargs)
        self.fields['choice'].choices = [('default', '默认选项')] + list(extra_choices)

2. 文件上传

# forms.py
class UploadForm(forms.Form):
    file = forms.FileField()

# views.py
def upload_view(request):
    if request.method == 'POST':
        form = UploadForm(request.POST, request.FILES)  # 注意 request.FILES
        if form.is_valid():
            file = form.cleaned_data['file']
            # 处理文件...
    else:
        form = UploadForm()
    return render(request, 'upload.html', {'form': form})

3. 表单集(Formsets)

处理多个相同表单:

# forms.py
from django.forms import formset_factory

class ArticleForm(forms.Form):
    title = forms.CharField()
    pub_date = forms.DateField()

ArticleFormSet = formset_factory(ArticleForm, extra=2)  # 默认创建2个空表单

# views.py
def manage_articles(request):
    formset = ArticleFormSet(request.POST or None)
    if formset.is_valid():
        for form in formset:
            if form.cleaned_data:
                # 处理每个表单数据
                pass
    return render(request, 'manage_articles.html', {'formset': formset})

六、模板渲染技巧

1. 快速渲染

{{ form.as_p }}  <!-- <p> 标签包裹 -->
{{ form.as_table }}  <!-- <tr> 标签包裹 -->
{{ form.as_ul }}  <!-- <li> 标签包裹 -->

2. 自定义渲染

<form method="post">
    {% csrf_token %}
    {% for field in form %}
        <div class="form-group">
            {{ field.label_tag }}
            {{ field }}
            {% if field.help_text %}
                <small class="form-text text-muted">{{ field.help_text }}</small>
            {% endif %}
            {% if field.errors %}
                <div class="alert alert-danger">{{ field.errors.as_text }}</div>
            {% endif %}
        </div>
    {% endfor %}
    <button type="submit">提交</button>
</form>

七、与 ModelForm 相关的坑点

  1. commit=False 的局限性

  2. 不能用于多对多字段(需先 save(),再添加关联)。

python post = form.save(commit=False) post.save() # 必须先保存,才能添加多对多关系 post.tags.set(tags) # 添加多对多关系

  1. 字段权限控制

  2. 不要在 fields 中包含敏感字段(如用户密码),而是在视图中手动设置。

  3. 自动填充字段

  4. initial 参数预填数据:

python form = CommentForm(initial={'content': '默认内容'})

八、最佳实践

  1. 表单类放在 forms.py:保持代码组织清晰。

  2. 重用验证逻辑:用自定义验证器减少重复代码。

  3. 使用 ModelForm:能自动映射模型字段,减少样板代码。

0 条评论

发表评论

暂无评论,欢迎发表您的观点!