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

如何为Django模型添加指定约束?(SQLite、Django3.2、Python3.7)

Django模型约束问题解答

前两个约束的正确性确认

  1. Refbook的code唯一约束:你给code字段添加unique=True是完全正确的。Django会在数据库层面为该字段创建唯一索引,确保不会出现code重复的Refbook实例,完全满足需求1。
  2. VersionRefbook的refbook_id+version联合约束:在Meta类中设置unique_together=('refbook_id', 'version')是有效的。虽然Django 2.2+推荐使用UniqueConstraint写法,但在Django 3.2中这种写法依然被支持,且会在数据库层面创建联合唯一索引,保证同一Refbook下不会出现相同版本号的VersionRefbook,符合需求2。

第三个约束:单个Refbook下的VersionRefbook日期不重复

不需要重写save方法,直接通过数据库层面的联合唯一约束实现最可靠(避免并发场景下的校验失效)。有两种写法可选:

写法1:继续使用unique_together(兼容旧版本写法)

修改VersionRefbook的Meta类,添加新的联合唯一组合:

class VersionRefbook(models.Model):
    refbook_id = models.ForeignKey('Refbook',
                                   on_delete=models.CASCADE,
                                   related_name='versions')
    version = models.CharField(max_length=50)
    date = models.DateField()

    class Meta:
        unique_together = (
            ('refbook_id', 'version'),
            ('refbook_id', 'date')
        )

写法2:使用推荐的UniqueConstraint(更现代)

class VersionRefbook(models.Model):
    refbook_id = models.ForeignKey('Refbook',
                                   on_delete=models.CASCADE,
                                   related_name='versions')
    version = models.CharField(max_length=50)
    date = models.DateField()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['refbook_id', 'version'],
                name='unique_refbook_version'
            ),
            models.UniqueConstraint(
                fields=['refbook_id', 'date'],
                name='unique_refbook_version_date'
            )
        ]

两种写法都会在数据库层面创建约束,确保同一Refbook下不会有两个VersionRefbook使用相同日期。

第四个约束:单个VersionRefbook中Element的code不重复

这个约束的实现取决于你的业务需求:Element是否需要被多个VersionRefbook共享?

场景1:Element仅属于一个VersionRefbook

这种情况下,直接将ManyToManyField改为ForeignKey,然后添加联合唯一约束即可,这是最可靠的方案:

class Element(models.Model):
    version_id = models.ForeignKey(
        'VersionRefbook',
        on_delete=models.CASCADE,
        related_name='elements'
    )
    code = models.CharField(max_length=100)
    value = models.CharField(max_length=300)

    class Meta:
        unique_together = ('version_id', 'code')

数据库层面会直接保证同一VersionRefbook下不会出现code重复的Element。

场景2:Element需要被多个VersionRefbook共享

由于SQLite不支持跨表的联合唯一约束,无法直接在数据库层面实现,需要通过业务逻辑校验处理:

步骤1:自定义中间表模型

先定义ManyToMany的中间表,方便后续添加校验逻辑:

class VersionElement(models.Model):
    version = models.ForeignKey(VersionRefbook, on_delete=models.CASCADE)
    element = models.ForeignKey(Element, on_delete=models.CASCADE)

    class Meta:
        unique_together = ('version', 'element')  # 避免同一Element重复关联同一Version

    def save(self, *args, **kwargs):
        # 校验当前Version下是否已有相同code的Element
        duplicate_exists = VersionElement.objects.filter(
            version=self.version,
            element__code=self.element.code
        ).exclude(element=self.element).exists()
        
        if duplicate_exists:
            raise ValueError(f"当前版本下已存在code为{self.element.code}的元素")
        
        super().save(*args, **kwargs)

步骤2:修改Element的ManyToMany字段

指定使用自定义的中间表:

class Element(models.Model):
    version_id = models.ManyToManyField(
        'VersionRefbook',
        through='VersionElement',
        related_name='elements'
    )
    code = models.CharField(max_length=100)
    value = models.CharField(max_length=300)

这样在给VersionRefbook添加Element时,会触发中间表的save方法进行校验,避免同一Version下出现code重复的Element。

注意:这种业务层校验在高并发场景下可能存在极小概率的冲突,但SQLite本身是单线程数据库,该问题几乎不会出现。


内容的提问来源于stack exchange,提问作者Артур

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 12:45:35