django 2.0+ 强制要求 foreignkey 必须显式指定 on_delete 参数,否则迁移报 typeerror;常用选项包括 cascade(级联删)、set_null(需 null=true)、protect(阻止删除)等,配置不当易致数据误删或运行时报错。

外键字段没写 on_delete 直接报错
Django 2.0+ 强制要求所有 ForeignKey 必须显式指定 on_delete 参数,否则迁移时会直接抛 TypeError: __init__() missing 1 required positional argument: 'on_delete'。
这不是警告,是硬性语法要求。哪怕你只是临时测试,也得填上——Django 不再替你猜行为。
-
on_delete=models.CASCADE:关联对象删了,本条记录也删(最常用,但要小心误删) -
on_delete=models.SET_NULL:关联对象删了,本字段设为NULL(前提:字段得加null=True) -
on_delete=models.PROTECT:只要还有子记录,就不让删父对象(适合关键主表,比如用户不能被删) -
on_delete=models.DO_NOTHING:完全不干预,靠数据库外键约束兜底(极少用,容易出IntegrityError)
on_delete=models.CASCADE 导致意外级联删除
看起来省事,但一个 user.delete() 可能顺手干掉几百条订单、地址、日志——尤其在管理后台批量操作时,连确认都没有。
真实场景里,很多“删除”其实是软删或归档,不该物理清除数据。级联删一旦执行无法回滚。
- 检查模型关系链:A → B → C,如果 A 的
on_delete是CASCADE,B 也是,那删 A 就等于删 C - 用
django-admin showmigrations确认迁移是否已生效,避免本地改了代码但没跑migrate - 开发期加个信号钩子临时拦截:
@receiver(pre_delete, sender=User)打印日志,看实际删了哪些关联对象
SET_NULL 或 SET_DEFAULT 报 NOT NULL constraint failed
常见于字段没开 null=True,却配了 on_delete=models.SET_NULL。数据库建表时该列仍是 NOT NULL,Django 迁移会通过,但运行时一触发就崩。
同理,SET_DEFAULT 要求字段定义里有 default=xxx,且默认值必须能被数据库接受(比如 CharField(default='') 可以,IntegerField(default=None) 就不行)。
- 查字段定义:
address = models.ForeignKey(Address, on_delete=models.SET_NULL, null=True)——null=True缺一不可 - 已有数据的表改
null=True,迁移会提示输入默认值;选1(提供空值)比瞎填数字安全 - SQLite 下
SET_NULL有时静默失败,建议开发期切 PostgreSQL 或 MySQL 验证外键行为
想禁用外键约束?别碰 on_delete=models.DO_NOTHING
它只是让 Django 不动手,不代表数据库没约束。如果数据库本身开了外键(默认开启),照样会抛 IntegrityError: insert or update on table "xxx" violates foreign key constraint。
真要绕过,得两头动:Python 层关 Django 检查(不推荐),数据库层关外键(更不推荐)。绝大多数情况,是你没想清业务逻辑——比如导入历史数据时临时禁用,应该用 transaction.atomic() + 显式 SQL 关,而不是靠 DO_NOTHING 混过去。
真正需要“无约束”的场景极少,优先考虑 PROTECT + 手动清理,或拆成两个阶段操作(先解绑,再删除)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











