Django 表单是处理用户输入的核心工具,ModelForm能自动关联数据库模型生成表单,配合自定义验证逻辑,可大幅减少重复代码,同时确保数据合法性。以下是完整实战案例:
(1)基础 ModelForm 实现(关联模型)
# 1. 定义模型(models.py)
from django.db import models
class Article(models.Model):
"""文章模型"""
title = models.CharField(max_length=200, verbose_name="标题")
content = models.TextField(verbose_name="内容")
author = models.ForeignKey(
'auth.User', # 关联Django内置User模型
on_delete=models.CASCADE,
verbose_name="作者"
)
publish_date = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
update_date = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = "文章"
verbose_name_plural = "文章列表"
ordering = ["-publish_date"]
# 2. 定义ModelForm(forms.py)
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
"""文章表单:自动关联Article模型"""
# 可选:自定义额外字段(非模型字段)
confirm_title = forms.CharField(
label="确认标题",
max_length=200,
widget=forms.TextInput(attrs={"class": "form-control"}) # 设置HTML属性
)
class Meta:
model = Article # 关联的模型
# 指定需要生成的字段(或使用exclude排除不需要的字段)
fields = ["title", "content", "confirm_title"]
# 自定义字段的widget和属性
widgets = {
"title": forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入文章标题"}),
"content": forms.Textarea(attrs={"class": "form-control", "rows": 6, "placeholder": "请输入文章内容"}),
}
# 自定义字段标签
labels = {
"title": "文章标题",
"content": "文章内容",
}
# 自定义错误信息
error_messages = {
"title": {
"required": "标题不能为空",
"max_length": "标题长度不能超过200个字符"
},
"content": {
"required": "内容不能为空"
}
}
# 3. 自定义字段验证:方法名格式为clean_字段名
def clean_title(self):
"""验证标题:不能包含敏感词"""
title = self.cleaned_data.get("title")
sensitive_words = ["敏感词1", "敏感词2"]
if any(word in title for word in sensitive_words):
raise forms.ValidationError("标题包含敏感词,请修改")
return title
# 4. 自定义跨字段验证:验证标题与确认标题一致性
def clean(self):
cleaned_data = super().clean() # 调用父类clean方法
title = cleaned_data.get("title")
confirm_title = cleaned_data.get("confirm_title")
if title and confirm_title and title != confirm_title:
# 添加错误信息到指定字段(或使用non_field_errors)
self.add_error("confirm_title", "两次输入的标题不一致")
return cleaned_data(2)在视图中使用 ModelForm
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required # 登录装饰器
from .models import Article
from .forms import ArticleForm
# 发布文章(登录后才能访问)
@login_required
def article_create(request):
if request.method == "POST":
# 1. 绑定POST数据到表单(request.FILES用于处理文件上传)
form = ArticleForm(request.POST)
# 2. 验证表单数据
if form.is_valid():
# 3. 获取验证后的干净数据(已排除无效字段)
article = form.save(commit=False) # commit=False:不立即保存到数据库
# 4. 补充模型中未在表单中的字段(如作者)
article.author = request.user
# 5. 保存到数据库
article.save()
# 6. 跳转至文章详情页
return redirect("article_detail", pk=article.pk)
else:
# GET请求:显示空表单
form = ArticleForm()
# 渲染模板(传递表单对象,用于前端显示)
return render(request, "article/create.html", {"form": form})
# 编辑文章(仅作者可编辑)
@login_required
def article_edit(request, pk):
# 获取要编辑的文章(不存在则返回404)
article = get_object_or_404(Article, pk=pk)
# 校验权限:仅文章作者可编辑
if article.author != request.user:
return redirect("article_detail", pk=article.pk) # 无权限则跳转详情页
if request.method == "POST":
# 绑定POST数据和已有文章对象(实现更新逻辑)
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
return redirect("article_detail", pk=article.pk)
else:
# GET请求:显示带有初始数据的表单(instance=article)
form = ArticleForm(instance=article)
return render(request, "article/edit.html", {"form": form, "article": article})(3)前端模板渲染表单
<!-- article/create.html -->
<!DOCTYPE html>
<html>
<head>
<title>发布文章</title>
<!-- 引入Bootstrap样式(美化表单) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>发布新文章</h2>
<!-- 表单提交:POST方法,action为空表示提交到当前URL -->
<form method="post">
{% csrf_token %} <!-- 必须添加CSRF令牌,防御CSRF攻击 -->
<!-- 渲染标题字段 -->
<div class="mb-3">
{{ form.title.label_tag }} <!-- 显示字段标签 -->
{{ form.title }} <!-- 显示输入控件 -->
{% if form.title.errors %} <!-- 显示字段错误信息 -->
<div class="text-danger mt-1">
{% for error in form.title.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<!-- 渲染确认标题字段 -->
<div class="mb-3">
{{ form.confirm_title.label_tag }}
{{ form.confirm_title }}
{% if form.confirm_title.errors %}
<div class="text-danger mt-1">
{% for error in form.confirm_title.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<!-- 渲染内容字段 -->
<div class="mb-3">
{{ form.content.label_tag }}
{{ form.content }}
{% if form.content.errors %}
<div class="text-danger mt-1">
{% for error in form.content.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<!-- 渲染全局错误信息(跨字段验证错误) -->
{% if form.non_field_errors %}
<div class="text-danger mb-3">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">发布</button>
</form>
</div>
</body>
</html>
