Django模型约束:同一bank和mortgage仅允许一个非draft状态的Application
可以实现这类约束,但
CheckConstraint并不适用 为什么CheckConstraint不行?
CheckConstraint是行级约束,只能检查当前记录自身的字段关系,没法跨多条记录验证同一个bank+mortgage组合下的非DRAFT实例数量。你的需求需要跨行校验,所以它满足不了。
两种可靠的实现方式
1. 数据库层面:带条件的UniqueConstraint(推荐)
利用Django 2.2+支持的UniqueConstraint.condition参数,给非DRAFT状态的记录添加bank+mortgage的唯一约束,从数据库层面杜绝非法数据。
模型代码示例:
from django.db import models from django.db.models import Q class Application(models.Model): DRAFT = 'draft' ACTIVE = 'active' IN_PROGRESS = 'in_progress' STATUS_CHOICES = [ (DRAFT, '草稿'), (ACTIVE, '活跃'), (IN_PROGRESS, '处理中'), ] bank = models.ForeignKey('Bank', on_delete=models.CASCADE) # 假设关联Bank模型 mortgage = models.ForeignKey('Mortgage', on_delete=models.CASCADE) # 假设关联Mortgage模型 status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT) class Meta: constraints = [ models.UniqueConstraint( fields=['bank', 'mortgage'], condition=~Q(status=DRAFT), name='unique_non_draft_application' ) ]
这个约束会强制:只要记录的status不是DRAFT,那么它的bank和mortgage组合必须唯一,完全符合你的需求。
2. 应用层:重写clean()方法
在模型的clean()方法里添加校验逻辑,在保存前检查是否存在冲突的非DRAFT记录。但要注意:直接调用save()时不会自动触发clean(),所以需要手动调用full_clean(),或者结合pre_save信号来确保校验执行。
代码示例:
from django.core.exceptions import ValidationError class Application(models.Model): # 字段定义同上... def clean(self): super().clean() # 如果当前记录不是DRAFT,检查同bank+mortgage下是否已有非DRAFT记录 if self.status != self.DRAFT: existing = Application.objects.filter( bank=self.bank, mortgage=self.mortgage, status__ne=self.DRAFT ).exclude(pk=self.pk) # 排除自身(更新场景) if existing.exists(): raise ValidationError( f"该银行({self.bank})和抵押({self.mortgage})组合已存在非草稿状态的申请" )
如果是在admin或表单中使用,会自动触发clean();如果是代码中直接创建/更新,记得调用obj.full_clean()再save()。
两种方式的对比
- 数据库层面的约束最可靠,能防止任何绕过应用层的操作(比如直接通过数据库客户端插入数据)。
- 应用层的校验更灵活,可以返回更友好的错误信息,但需要确保所有保存路径都触发校验。
内容的提问来源于stack exchange,提问作者Milano
相关产品推荐
相关产品推荐

