Skip to content

Django ORM、表单约束与事务 ​

本文用登录用户的个人任务板解释Task、owner和表单保存;模型、手写迁移、TaskForm及views.py均见完整工程。前置是QuerySet过滤、表单提交与基本数据库约束。

适用:Python 3.12、Django 5.2.18(5.2 LTS)。先掌握函数、模块、虚拟环境和 HTTP 的请求/响应。示例未执行;本轮不安装依赖、不启动服务、不运行框架或迁移。

Django的QuerySet何时真正查询 ​

filter 返回惰性 QuerySet,链式过滤主要是在构造 SQL;迭代、转成 list、判断真假等会触发求值。不要把“定义一个变量”当作已经获得数据库快照。同一 QuerySet 普通完整迭代后可用结果缓存,重新构造的查询不是同一缓存;需要最新数据应重新查询或刷新实例。

列表页面把 owner 过滤后的 QuerySet 交给模板,在循环时获取数据;这里不访问 owner 的属性,因此没有因逐行访问关联对象而产生的 N+1。若以后展示用户名,可用 select_related("owner") 一起取得外键对象;多对多或反向关系再考虑 prefetch_related。先确定实际访问路径,再测 SQL 次数。

Django模型修改后为什么还要迁移 ​

模型代码描述目标模型状态,迁移记录描述状态变化,数据库保存真实表结构。只改 models.py 不会自动改表。本例手写 0001_initial,声明对可替换用户模型的依赖,并创建外键和唯一约束;它不是框架生成或执行成功的证据。

未来自行运行 makemigrations --check --dry-run 检查模型与迁移状态是否漂移,再用 showmigrations 和 migrate --plan 审阅计划;确认备份后再应用迁移。不要删迁移文件来“修复”已上线数据库。数据迁移应使用历史模型 apps.get_model,并考虑反向操作、分批处理和兼容旧程序的发布顺序。

Django表单校验和数据库约束如何配合 ​

ModelForm 负责把输入转换成 Python 值并校验长度与必填;clean_title 再去掉两端空白。数据库唯一约束保证同一 owner 下 title 唯一,即使两个请求同时通过前置校验,数据库仍是最终裁判。不同用户可以有同名任务,大小写比较语义受数据库排序规则影响,本例不承诺跨数据库完全一致。

本例 owner 不在表单字段中,组合唯一约束不会依靠用户可编辑 owner 来校验;保存时捕获 IntegrityError 并返回非字段错误。save(commit=False) 只返回未保存实例,赋值 owner 后仍需 save。直接 ORM save 不会自动完整执行表单校验;批量 update 也不会运行模型 save 方法或其信号。

Django捕获IntegrityError应该放在哪里 ​

将 try/except IntegrityError 放在 with transaction.atomic() 外部,让异常先离开 atomic 并触发回滚,再处理表单错误。若在仍被标记为失败的事务里继续查询,可能再遇到事务管理错误。atomic 保证这段数据库操作全成或全败,不保证跨网络动作一起回滚。

本例只有一次保存也明确展示事务边界,为重复标题提供可解释的恢复路径。需要提交后触发通知时使用 transaction.on_commit,同时另行处理通知失败;不要在事务中等待慢网络调用。SQLite 的并发写能力有限,本课程不把它的行为当作 PostgreSQL 行锁模型,也不声称 atomic 等同防止全部竞态。

查询写法和可观察结果 ​

python
# 片段示意,假定已在Django上下文中取得current_user;本轮未执行。
pending = Task.objects.filter(owner=current_user, done=False)
titles = list(pending.values_list("title", flat=True))
changed = Task.objects.filter(owner=current_user, pk=task_id).update(done=True)

第一行构造筛选,第二行物化标题列表;第三行直接执行更新并返回匹配行数。changed == 0 只说明没有匹配行,不应据此泄露“任务存在但属于其他人”。update 不经过 TaskForm,也不会自动运行 clean_title。对于本例编辑页面,仍走表单与实例 save,才能统一展示错误。

如果只需要确认存在,用 exists 表达意图;如果紧接着要读取全部记录,先 exists 再完整读取会多一次往返。需要数量用 count;需要遍历很多行再讨论 iterator 和分页。模板每展示一个任务就读取额外外键,会把一次列表请求变成多次查询,优化要根据实际访问而非盲目增加预加载。

修改字段时的发布顺序 ​

例如以后增加非空备注字段,现有行没有值,不能只改模型后假定迁移会猜出业务含义。通常先允许空或给出符合业务的默认策略,发布兼容代码,分批补旧数据,再收紧约束。不能为方便随意把所有历史用户数据写成一个无意义常量。

本例已有唯一约束;若给旧数据库新加同类约束,先查重、确定保留规则并备份,再应用结构变化。大表创建索引和修改列的锁行为依后端而异,SQLite教学不涵盖线上无停机迁移。把源文件入库、检查计划和实际执行三个事实分开记录,才能追踪失败时在哪个阶段。

独立练习 ​

解释两个请求同时创建同一标题时,为什么预先 exists 仍不能保证唯一;把发送邮件放进 atomic 是否能够回滚邮件?

先写出判断依据,再核对下节。

参考解答 ​

两次检查可能都为否,唯一约束由数据库最终裁决;邮件是外部副作用,不随数据库回滚,应提交后处理且设计失败补偿。

版本与资料边界 ​

核对日期2026-10-17。官方5.2.18发布页标注2026-10-06,官方支持表列5.2 LTS扩展支持至2028年4月。这里固定教学补丁,不声称以后仍是最新版本;未来自跑应先检查受支持补丁并重新验收。Windows PowerShell、macOS/Linux Bash/Zsh的命令已在完整工程README分开给出,三平台均未运行。

ORM查询;迁移;事务(中文);表单。版本依据:支持表与5.2.18发布页。

继续阅读