一、为什么需要 Form 组件
在没有 Form 组件时,我们需要手写 HTML 表单、读取 request.POST,并为每个字段编写长度、格式和必填校验。字段一多,模板和视图就会重复很多逻辑。
Django 的 Form 组件把"字段定义、HTML 生成、数据清洗、错误提示"集中到一个 Python 类中。它既可以生成表单控件,也可以在服务端统一校验用户提交的数据。需要注意:浏览器端的校验只能改善交互体验,真正可靠的校验必须在服务端执行。
下面是手写 HTML 表单的例子。它能展示页面,但不会自动完成 Django 的数据校验。
html
<form action="" method="post">
<div class="form-group">
<label for="inputUsername">用户名</label>
<input type="text" name="username" id="inputUsername">
</div>
<div class="form-group">
<label for="inputPassword">密码</label>
<input type="password" name="password" id="inputPassword">
</div>
<button type="submit">提交</button>
</form>
二、创建并使用一个 Form
通常将表单类放在应用的 forms.py 中。forms.Form 不会自动创建数据库表,它只描述当前页面需要的字段及校验规则。
1. 定义表单类
python
# users/forms.py
from django import forms
class RegisterForm(forms.Form):
username = forms.CharField(max_length=8, min_length=3, label="用户名")
password = forms.CharField(
max_length=6,
min_length=2,
label="密码",
widget=forms.PasswordInput,
)
email = forms.EmailField(label="邮箱")
代码中的每个字段都会对应一个 HTML 控件。max_length 和 min_length 限制的是用户提交到表单中的字符长度;它们与模型字段的数据库长度约束不是同一个概念。
2. 在视图中绑定数据
python
# users/views.py
from django.shortcuts import render
from django.views import View
from .forms import RegisterForm
class RegisterView(View):
def get(self, request, *args, **kwargs):
# 未提交数据时创建未绑定表单,用于展示空表单。
form = RegisterForm()
return render(request, "register.html", {"form": form})
def post(self, request, *args, **kwargs):
# 传入 request.POST 后,表单成为"已绑定"状态,可以执行校验。
form = RegisterForm(request.POST)
if form.is_valid():
# cleaned_data 是经过类型转换和校验后的可信数据。
data = form.cleaned_data
# 在这里执行创建用户等业务操作。
return render(request, "register_success.html", {"data": data})
# 校验失败时把同一个 form 返回模板,用户输入和错误信息都会保留。
return render(request, "register.html", {"form": form})
is_valid() 会触发所有字段的内置校验和自定义校验。只有返回 True 时才应该使用 cleaned_data;失败时应将表单重新渲染,让用户看到具体错误。
3. 在模板中输出表单
html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">提交</button>
</form>
POST 表单通常需要 {% csrf_token %},否则 Django 的 CSRF 中间件会拒绝请求。{``{ form.as_p }} 只是 Django 提供的一种快速布局方式,实际项目也可以逐字段编写模板,以获得更精细的样式和错误展示。
三、常用字段参数与控件
字段参数决定了表单的显示方式和校验行为,常用参数如下:
python
from django import forms
class ProfileForm(forms.Form):
username = forms.CharField(
max_length=20,
min_length=3,
required=True, # 是否必填,默认 True
initial="dream", # 未绑定表单的初始值
label="用户名", # 标签文字
label_suffix=":", # 标签后缀
help_text="3~20 个字符",
error_messages={
"required": "用户名不能为空",
"max_length": "用户名不能超过 20 个字符",
},
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": "请输入用户名",
}),
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(attrs={"class": "form-control"}),
)
gender = forms.ChoiceField(
label="性别",
choices=(("0", "女"), ("1", "男")),
widget=forms.RadioSelect,
)
city = forms.ChoiceField(
label="城市",
choices=(("sh", "上海"), ("bj", "北京")),
)
hobbies = forms.MultipleChoiceField(
label="爱好",
choices=(("read", "阅读"), ("sport", "运动")),
widget=forms.CheckboxSelectMultiple,
required=False,
)
widget 负责生成 HTML 控件,attrs 可以为控件添加 class、placeholder 等属性。ChoiceField 适合单选或下拉框,MultipleChoiceField 适合多选。提交的数据通常是字符串,业务代码需要根据实际需求进行类型转换。
常见字段速查:
text
CharField 文本,支持 max_length、min_length
IntegerField 整数,支持 min_value、max_value
FloatField / DecimalField 小数;DecimalField 可限制总位数和小数位
DateField / TimeField 日期或时间
EmailField 邮箱格式
URLField URL 格式
BooleanField 布尔值
ChoiceField 单选或下拉选项
MultipleChoiceField 多选选项
FileField 文件上传
ImageField 图片上传,需要 Pillow
ModelChoiceField 从 QuerySet 中选择一个模型对象
ModelMultipleChoiceField 从 QuerySet 中选择多个模型对象
文件上传必须同时满足两个条件:HTML 表单设置 enctype="multipart/form-data",视图绑定 request.FILES。
python
class UploadForm(forms.Form):
file = forms.FileField()
form = UploadForm(request.POST, request.FILES)
四、Form 的几种渲染方式
Django 可以快速把整个表单渲染成不同的 HTML 结构:
html
{{ form }} <!-- 默认结构,通常是 div 或段落结构 -->
{{ form.as_p }} <!-- 每个字段放入 p 标签 -->
{{ form.as_ul }} <!-- 每个字段放入 li,需要 ul/ol 包裹 -->
{{ form.as_table }} <!-- 每个字段放入 tr,需要 table 包裹 -->
需要完全控制布局时,可以遍历字段。这样既能输出控件,也能分别处理标签、帮助文本和错误信息。
html
{% for field in form %}
<div class="field">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}<small>{{ field.help_text }}</small>{% endif %}
{% for error in field.errors %}<p class="error">{{ error }}</p>{% endfor %}
</div>
{% endfor %}
如果需要暂时关闭浏览器原生校验,可以在 <form> 上添加 novalidate。这不会关闭 Django 的服务端校验:
html
<form method="post" novalidate>
五、钩子函数:补充业务校验
字段的 max_length、EmailField 等规则只能解决通用问题。对于"用户名必须以 nb_ 开头""两次密码必须一致"等业务规则,可以使用清洗钩子:
clean_字段名():局部钩子,只校验一个字段。clean():全局钩子,适合比较多个字段。
不要把 onBlur、onSubmit 等前端事件称为 Django Form 钩子;它们属于 JavaScript 交互,与服务端的 clean_*、clean 不同。
python
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(max_length=20, label="用户名")
password = forms.CharField(widget=forms.PasswordInput, label="密码")
confirm_password = forms.CharField(
widget=forms.PasswordInput,
label="确认密码",
)
def clean_username(self):
username = self.cleaned_data["username"]
if not username.startswith("nb_"):
raise forms.ValidationError("用户名必须以 nb_ 开头")
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:
self.add_error("confirm_password", "两次密码不一致")
return cleaned_data
局部钩子返回字段值,才能继续把它放入 cleaned_data。全局钩子应先调用 super().clean(),这样才能拿到已经通过字段校验的数据。字段不存在或字段校验失败时,使用 .get() 可以避免额外的 KeyError。
六、ModelForm:让表单复用模型定义
ModelForm 是 Form 的扩展。它可以根据模型字段自动生成表单字段,并在校验通过后直接保存模型,适合新增和编辑数据的场景。模型字段的数据库约束仍然需要正确设置,表单校验不能替代数据库约束。
1. 模型与 ModelForm
python
# users/models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=128)
python
# users/forms.py
from django import forms
from .models import User
class UserModelForm(forms.ModelForm):
# 手动声明的字段会覆盖模型自动生成的同名字段。
username = forms.CharField(
max_length=20,
min_length=3,
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"}),
)
class Meta:
model = User
# fields 与 exclude 二选一,不能同时配置。
fields = ["username", "password"]
fields = "__all__" 会包含模型的全部字段,但不建议在用户注册等敏感场景中滥用,否则模型新增字段后可能被意外暴露。更稳妥的做法是显式列出允许提交的字段。
2. 校验、保存与编辑
python
class UserCreateView(View):
def post(self, request, *args, **kwargs):
form = UserModelForm(request.POST)
if form.is_valid():
user = form.save() # 校验后的数据写入数据库
return render(request, "success.html", {"user": user})
return render(request, "user_form.html", {"form": form})
# 编辑已有对象时传入 instance;提交后 form.save() 会执行更新而不是新增。
form = UserModelForm(request.POST or None, instance=user)
如果需要先补充字段或执行额外业务逻辑,可以使用 commit=False,完成修改后再保存:
python
from django.contrib.auth.hashers import make_password
user = form.save(commit=False)
user.password = make_password(user.password)
user.save()
七、总结
Form适合独立于数据库的表单;ModelForm适合直接对应模型的数据录入和编辑。- 未绑定表单用于展示,绑定
request.POST后才能校验;文件上传还要绑定request.FILES。 is_valid()成功后再读取cleaned_data,失败时把原表单返回模板以显示错误。clean_字段名()用于单字段规则,clean()用于跨字段规则。- 快速渲染适合简单页面,复杂页面应逐字段渲染并明确展示错误信息。