You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:00:51