6.1 为什么用 Django 表单
手写 <form> + 手动取 request.POST + 手动校验,既繁琐又易出安全漏洞。Django 表单把「渲染、校验、清洗、错误提示」封装成类,一处声明处处受益。
- Form
- 普通表单类,字段手动声明。适合与模型无关的输入(搜索框、联系表单)。
- ModelForm
- 从模型自动生成表单,字段、校验、保存全打通。
form.save()直接写库。 - Field 与 Widget
- Field 是数据层(校验规则),Widget 是表现层(渲染成什么 HTML 控件)。如
CharField+Textarea。 - is_valid()
- 触发全部校验,通过后把清洗后的数据放进
form.cleaned_data,失败则填充form.errors。 - cleaned_data
- 校验通过后类型转换、去空格后的干净数据字典,视图应只读它,绝不用原始
request.POST。 - CSRF(跨站请求伪造)
- 攻击者诱导用户在已登录站点执行非本意操作。Django 用
{% csrf_token %}令牌防护,POST 缺令牌直接 403。 - clean 方法
- 自定义校验钩子:
clean_<field>()校验单字段,clean()做跨字段校验。
6.2 普通 Form
# blog/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=50, label="姓名")
email = forms.EmailField(label="邮箱")
message = forms.CharField(widget=forms.Textarea, label="内容")
6.3 视图中处理表单
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact(request):
if request.method == "POST":
form = ContactForm(request.POST)
if form.is_valid():
data = form.cleaned_data # 干净数据
# 发邮件 / 存库 ...
return redirect("blog:thanks")
else:
form = ContactForm()
return render(request, "blog/contact.html", {"form": form})
6.4 模板渲染表单与 CSRF
<form method="post">
{% csrf_token %} # 必须!否则 POST 被拒 403
{{ form.as_p }} # 逐字段包 <p> 渲染
<button type="submit">提交</button>
</form>
| 渲染方式 | 输出 |
|---|---|
| {{ form.as_p }} | 每字段一个 <p> |
| {{ form.as_div }} | 每字段一个 <div>(推荐) |
| {{ form.as_table }} | 表格行 |
| {{ form.name }} | 手动逐字段控制样式 |
CSRF 令牌不可省
任何 POST 表单都必须在 <form> 内放 {% csrf_token %}。缺失将触发 403 Forbidden。这是 Django 默认开启的关键安全防护,切勿为图省事关掉 CsrfViewMiddleware。
6.5 ModelForm:从模型生成表单
from django.forms import ModelForm
from .models import Post
class PostForm(ModelForm):
class Meta:
model = Post
fields = ["title", "body", "category", "published"]
widgets = {"body": forms.Textarea(attrs={"rows": 8})}
# 视图里保存
form = PostForm(request.POST)
if form.is_valid():
post = form.save() # 直接写库并返回实例
6.6 自定义校验:clean 方法
class PostForm(ModelForm):
class Meta:
model = Post
fields = ["title", "body"]
def clean_title(self): # 单字段校验
title = self.cleaned_data["title"]
if "广告" in title:
raise forms.ValidationError("标题不能含广告字样")
return title
def clean(self): # 跨字段校验
cleaned = super().clean()
if len(cleaned.get("body", "")) < 10:
raise forms.ValidationError("正文太短")
return cleaned
6.7 文件上传
# 模型加文件字段
class Post(models.Model):
cover = models.ImageField(upload_to="covers/", blank=True)
# 表单必须传 request.FILES
form = PostForm(request.POST, request.FILES)
# 模板 form 加 enctype
<form method="post" enctype="multipart/form-data">
ImageField 依赖 Pillow
使用 ImageField 需先 pip install Pillow。同时在 settings 配置 MEDIA_URL/MEDIA_ROOT 并在开发路由挂载媒体文件服务。
6.8 小结与下一步
- Form 手动声明字段,ModelForm 从模型自动生成并可
save()写库。 is_valid()→cleaned_data是黄金路径;永远不直接信任request.POST。- POST 表单必带
{% csrf_token %};用clean_<field>/clean做自定义与跨字段校验。 - 下一章进入 Django Admin——注册模型即得一套强大的后台管理。