(十二)ModelForm 与当前用户的字段规则
# 项目文件:reading_project/src/library/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import Book
class SignupForm(UserCreationForm):
# 使用 Django 已提供的用户名、两次密码和密码规则检查。
pass
class BookForm(forms.ModelForm):
tags_text = forms.CharField(
label="标签", required=False, help_text="用英文逗号分隔,最多 10 个标签。"
)
class Meta:
model = Book
fields = ["title", "finished", "notes", "tags_text"] # owner 不交给客户端赋值。
widgets = {"notes": forms.Textarea(attrs={"rows": 4, "maxlength": 2000})}
def __init__(self, *args, user, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
if self.instance.pk and not self.is_bound:
self.initial["tags_text"] = ", ".join(
tag.name for tag in self.instance.tags.all()
)
def clean_title(self):
title = self.cleaned_data["title"]
same_title = Book.objects.filter(owner=self.user, title=title).exclude(
pk=self.instance.pk
)
if same_title.exists():
raise forms.ValidationError("书架里已经有同名的书")
return titleModelForm.Meta.model 连接模型,fields 列出页面允许编辑的字段。owner 没在列表里,所以客户端多带一个同名字段也不会给书籍指定所有者;服务函数从当前用户赋值。
user 是表单额外接收的关键字参数,用于检查当前书架中的重名。clean_title 在基本字段转换之后读取合格书名,再排除正在编辑的自己。未绑定的编辑表单用现有标签初始化;绑定失败时保留本次输入,而不是被旧标签覆盖。
# 续写同一文件:reading_project/src/library/forms.py
def clean_tags_text(self):
# 先拆分和去空白,再按第一次出现的次序去重。
tags = list(
dict.fromkeys(
piece.strip()
for piece in self.cleaned_data["tags_text"].split(",")
if piece.strip()
)
)
if len(tags) > 10 or any(len(tag) > 30 for tag in tags):
raise forms.ValidationError("最多 10 个标签,每项不超过 30 个字符")
return tagstags_text 是自己声明的普通表单字段,转换结果为去重标签列表;实际多对多关系由服务保存。备注的长度规则来自模型 max_length,表单和后台也会执行它。
form.save(commit=False) 构造或更新 Python 模型对象,暂不写入。这样可以先设置归属,再在同一事务中保存模型与标签。ModelForm 的保存过程可对照 Django ModelForm。