如何为Django模型添加指定约束?(SQLite、Django3.2、Python3.7)
Django模型约束问题解答
前两个约束的正确性确认
- Refbook的code唯一约束:你给
code字段添加unique=True是完全正确的。Django会在数据库层面为该字段创建唯一索引,确保不会出现code重复的Refbook实例,完全满足需求1。 - 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,提问作者Артур
相关产品推荐
相关产品推荐

