Django 5.0 的 CheckConstraint 仅支持 Q 对象或布尔值作为 check 参数,不支持字符串或原生 SQL;字段名须真实存在且不可嵌套;UniqueConstraint 仅纯字段组合参与 full_clean() 校验;约束名须全局唯一并用 %(app_label)s_%(class)s 格式化;JSONField 路径约束不触发 ValidationError。

直接在 Meta.constraints 里声明约束,Django 5.0 就会在迁移时生成对应数据库 DDL,并在 full_clean() 和 save() 时做两级校验——但前提是约束名唯一、条件可表达、且不涉及 JSONField 的路径操作。
CheckConstraint 必须用 check 参数传 Q 对象或布尔表达式
Django 5.0 的 CheckConstraint 不接受字符串条件,也不支持原生 SQL 片段。传错类型会导致迁移失败或运行时静默失效。
-
check参数只能是Q对象(如Q(age__gte=18))或 Python 布尔值(如True/False),不能是字符串"age >= 18" - 字段名必须是模型中真实存在的字段,不支持嵌套关系字段(如
author__name__startswith)——这会触发FieldError - 若使用
Q组合多个条件,注意括号优先级:Q(status='active') & Q(score__gt=0)是合法的,但漏掉&或写成and会报错
UniqueConstraint 在 Django 5.0 支持 condition 和 expressions,但验证行为不同
UniqueConstraint 是少数能在 full_clean() 阶段提前报 ValidationError 的约束,但仅限于不含 condition 或 expressions 的“纯字段组合”场景;一旦加了过滤条件或数据库函数,就退化为仅靠数据库报 IntegrityError。
- 纯字段组合(如
UniqueConstraint(fields=['email'])):调用full_clean()时会查库确认重复,违反则抛ValidationError - 带
condition(如UniqueConstraint(fields=['slug'], condition=Q(is_published=True))):只在save()时由数据库强制,Django 不做预检 - 含
expressions(如UniqueConstraint(fields=['Lower("email")'])):同上,不参与full_clean()校验
约束名必须全局唯一,抽象基类里不能硬编码 name
把 Meta.constraints 写在抽象基类里,子类继承后会复用完全相同的 name 字符串,导致迁移时报 duplicate constraint name 错误。
立即学习“Python免费学习笔记(深入)”;
- 正确做法是用格式化占位符:
name='%(app_label)s_%(class)s_email_required',Django 会在实际模型上替换成类似blog_post_email_required - 不要用
__name__或self.__class__.__name__动态拼接——Meta类定义时这些都不可用 - 如果手动生成迁移文件,检查
operations列表里每个AddConstraint的name是否真的不同,尤其跨 app 时容易撞名
JSONField 上的 CheckConstraint 几乎不触发 ValidationError
哪怕你在 CheckConstraint 里写了 Q(data__type='user') 这样的条件,Django 5.0 也不会在 full_clean() 中真正执行它,因为 JSON 路径操作在不同数据库实现差异太大,校验逻辑被跳过。
- 你会看到日志里出现
Got a database error calling check() on ...,这是预期行为,不是 bug - 这类约束只在
save()时由数据库执行,违反时抛IntegrityError,不会变成用户友好的ValidationError - 如需前端友好提示,得额外在模型
clean()方法里手动解析data字段并校验,不能依赖约束本身
最易忽略的是:约束名格式化字符串里的 %(app_label)s 和 %(class)s 只在具体模型类上展开,如果你用 type(instance).__name__ 手动构造约束名,结果和迁移时生成的不一致,会导致后续 migrate --fake 或重命名出问题。


















