bigywater.tech
Django · 104

pre_save、post_save 执行时机

pre_save 和 post_save 到底在 save() 的哪个位置触发?对象有没有 pk?事务提交了没有?这篇把信号和 SQL 放进同一条时间线里,一行行对照。

pk=None → 1
pre_save 时新对象没有 pk,post_save 时有
update_fields
save(update_fields=[...]) 的字段列表会传给信号
COMMIT 之前
atomic 里 post_save 触发时事务尚未提交

新建对象

实测 · 本机执行输出
   pre_save(pk=None, created=None, update_fields=None)
   SQL: INSERT INTO "lab_item" ("name", "n") VALUES (%s, %s)
   post_save(pk=1, created=True, update_fields=None)

用 connection.execute_wrapper 记下每条 SQL,和信号回调排进同一个列表,才能看出真实的先后。

更新对象

实测 · 本机执行输出
save():
   pre_save(pk=2, created=None, update_fields=None)
   SQL: UPDATE "lab_item" SET "name" = %s, "n" = %s WHERE "l
   post_save(pk=2, created=False, update_fields=None)
save(update_fields=["n"]):
   pre_save(pk=2, created=None, update_fields=['n'])
   SQL: UPDATE "lab_item" SET "n" = %s WHERE "lab_item"."id"
   post_save(pk=2, created=False, update_fields=['n'])

UPDATE 时 created 是 False;update_fields 为 None 表示保存了全部字段。

在事务里保存

实测 · 本机执行输出
   SQL: BEGIN
   pre_save(pk=None, created=None, update_fields=None)
   SQL: INSERT INTO "lab_item" ("name", "n") VALUES (%s, %s)
   post_save(pk=3, created=True, update_fields=None)
   (atomic 块内最后一行)

post_save 在 COMMIT 之前触发,这是下一篇要展开的问题:receiver 里做的事看到的是「未提交」的数据。

删除与不发信号的操作

实测 · 本机执行输出
instance.delete():
   SQL: BEGIN
   pre_delete(pk=4, created=None, update_fields=None)
   SQL: DELETE FROM "lab_item" WHERE "lab_item"."id" IN (%s)
   post_delete(pk=4, created=None, update_fields=None)
queryset.update(n=1): ['SQL: UPDATE "lab_item" SET "n" = %s WHERE "lab_item"."nam']
get_or_create(不存在):
   SQL: SELECT "lab_item"."id", "lab_item"."name", "lab_item
   SQL: BEGIN
   pre_save(pk=None, created=None, update_fields=None)
   SQL: INSERT INTO "lab_item" ("name", "n") VALUES (%s, %s)
   post_save(pk=6, created=True, update_fields=None)

批量的 queryset.update() 不走 save(),不发信号;bulk_create 等见第 110 篇。

在信号里改对象

实测 · 本机执行输出
pre_save 里把 name 改成大写 → 库里: LOWER
post_save 里改 instance.name → 内存里: changed-after  库里: orig

如果要在保存前修改字段,放在 pre_save(或者 save() / clean());在 post_save 里改 instance 不会自动写回。

分步演示

save() 时间线
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;SQL 用 connection.execute_wrapper 记录,只截取前 52 个字符
    • 没有验证:多对多 through 表的信号、继承模型(多表继承)的信号、raw=True(loaddata)时的行为、deferred 字段与 update_fields 的关系、PostgreSQL 上的行为差异
    京ICP备2026067502号-1京公网安备11010502063487号