django 5.0 推荐用 checkconstraint 和 uniqueconstraint 替代应用层校验与 unique_together:checkconstraint 实现数据库级取值范围检查(如 age 在 0–150),uniqueconstraint 支持条件唯一(如仅对 is_active=true 的 email 唯一);二者均需静态求值,不支持 f 表达式或运行时变量,且各数据库对函数和语法支持差异大,迁移后须手动验证约束是否生效。

用 CheckConstraint 在模型中定义字段取值范围
Django 5.0 支持在 Meta.constraints 中直接声明数据库级检查约束,比应用层校验更可靠。比如限制用户年龄必须在 0–150 之间:
class User(models.Model):
age = models.PositiveSmallIntegerField()
<pre class="brush:python;toolbar:false;">class Meta:
constraints = [
models.CheckConstraint(
check=models.Q(age__gte=0) & models.Q(age__lte=150),
name='age_range_check'
)
]
注意:Django 不会自动为 CheckConstraint 创建迁移时的 SQL 注释,但 PostgreSQL/SQLite/MySQL 都能正确解析该表达式;若使用 age__in 或复杂函数(如 Lower()),需确认后端是否支持对应 SQL 函数。
- PostgreSQL 支持完整表达式,包括
LOWER(name)、LENGTH(description) > 0 - SQLite 对函数支持有限,
LENGTH()可用,但REGEXP需手动启用扩展 - MySQL 8.0.16+ 才支持 CHECK 约束,旧版会静默忽略 —— 运行
python manage.py showmigrations后务必查数据库实际 DDL
用 UniqueConstraint 替代 unique_together 并支持条件去重
unique_together 已被标记为废弃,Django 5.0 推荐统一用 UniqueConstraint。它不仅能替代旧写法,还能加条件,比如“仅对激活状态的邮箱强制唯一”:
class Profile(models.Model):
email = models.EmailField()
is_active = models.BooleanField(default=True)
<pre class="brush:python;toolbar:false;">class Meta:
constraints = [
models.UniqueConstraint(
fields=['email'],
condition=models.Q(is_active=True),
name='active_email_unique'
)
]
关键点在于 condition 参数:它生成的是 *partial index*(PostgreSQL)或 *filtered index*(SQL Server),MySQL 不支持该语法,会退化为全表唯一约束(即忽略 condition)。
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
- 迁移生成后,用
python manage.py sqlmigrate myapp 0001检查输出是否含WHERE is_active = TRUE类语句 - SQLite 不支持 partial index,Django 会跳过该约束(不报错也不建索引)
- 字段名必须是模型字段,不能是
db_column别名
避免在 constraints 中误用 models.F 或动态值
CheckConstraint 和 UniqueConstraint 的所有参数必须能在迁移时静态求值 —— 这意味着不能出现运行时变量、datetime.now()、models.F('created_at') 等。
常见错误写法:
# ❌ 错误:F 表达式无法在迁移时解析
models.CheckConstraint(check=models.Q(updated_at__lt=models.F('created_at')), ...)
<h1>❌ 错误:datetime 是运行时值,迁移无法固化</h1><p>models.CheckConstraint(check=models.Q(created_at__gte=datetime.now()), ...)
</p>
正确做法是用数据库函数(如 Now())或明确常量:
# ✅ 正确:使用数据库时间函数
from django.db.models import Func
class Now(Func):
template = 'NOW()'
<p>models.CheckConstraint(
check=models.Q(created_at__lte=Now()),
name='created_in_past'
)
</p>
-
Func子类必须定义template,且内容需与目标数据库语法一致 - PostgreSQL 用
CURRENT_TIMESTAMP,MySQL 用NOW(),跨库项目慎用时间相关约束 - 任何带
__的查找(如name__icontains)在 CHECK 中均无效 —— 数据库原生 CHECK 不支持全文检索语义
迁移后验证约束是否真正生效
运行 python manage.py migrate 后,约束不一定落地:SQLite 默认关闭 CHECK 支持,PostgreSQL 需要显式启用,MySQL 可能静默降级。最直接的验证方式是手动触发违反约束的操作:
# 假设已有 CheckConstraint 要求 status ∈ ['draft', 'published'] >>> Article.objects.create(title='test', status='archived') # 若数据库返回类似 "violates check constraint" 的错误,说明约束已生效
若没报错,说明约束未启用或被忽略。此时应:
- 查数据库日志或执行
\d table_name(PostgreSQL)看约束是否存在 - SQLite 用户需确认连接时设置了
PRAGMA ignore_check_constraints = OFF(Django 默认开启 CHECK) - MySQL 用户执行
SHOW CREATE TABLE myapp_article;,确认输出中有CONSTRAINT ... CHECK行
约束不是“设了就一定起作用”的东西,尤其在多后端兼容场景下,它的行为差异比字段定义大得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










