基于另一个字段的值为Django模型添加约束

8

我有一个简单的模型,只有1个主键和3个字段(简化版):

  • 及格分数
  • 最高分数
  • 最大尝试次数

这个模型是通过继承django.db.models创建的。这是最小可重现代码:

from django.db import models
class QuestionSet(models.Model):
    passingscore = models.PositiveSmallIntegerField("passing score")
    maxscore = models.PositiveSmallIntegerField("max score")
    maxattempt = models.PositiveSmallIntegerField("max attempt")

我希望增加一个约束条件,使得数据库中的passingscore不能大于maxscore
我已经使用了跨多个字段的约束条件,例如unique_together,如StackOverflow上的这个线程所述。但显然这是一个不同的用例。
我也曾简要考虑过直接编写PostgreSQL代码来添加约束条件:
ALTER TABLE tableB ADD CONSTRAINT score_policy_1 CHECK (maxscore >= passingscore) 

但是这样做违背了使用ORM的初衷并且违反"松耦合"思想,使得在不同数据库后端之间迁移变得困难。

如果可能的话,请指出Django中更为惯用的方式来编写此约束条件。


这个能帮到你吗?https://docs.djangoproject.com/en/2.2/ref/models/constraints/ - cwhisperer
@cwhisperer并不完全正确。元约束是我知道并且善加利用的,但是将约束放置在值或其他字段上的主体限制并未得到描述或阐述。不过还是谢谢! - onlyphantom
2个回答

10

这应该适用于你的情况。

from django.db import models
class QuestionSet(models.Model):
    passingscore = models.PositiveSmallIntegerField("passing score")
    maxscore = models.PositiveSmallIntegerField("max score")
    maxattempt = models.PositiveSmallIntegerField("max attempt")
    
    class Meta:
        constraints = [
            models.CheckConstraint(
                name="%(app_label)s_%(class)s_passingscore_lte_maxscore",
                check=models.Q(passingscore__lte=models.F("maxscore")),
            )
        ]

看起来很有前途 - 我会尝试一下(已经超过一年了,所以我从这个项目中退出了),当我能够确认它有效时,我会回来将其标记为被接受的答案。非常感谢! - onlyphantom

3

您可以在视图层或数据库层面实现此功能。

如果您想在数据库层面实现此功能,可以重写模型的save方法并在那里检查值:

def save(self, *args, **kwargs):

    if self.passingscore > self.maxscore:
        raise ValueError("passingscore can't be greater than maxscore")

    super().save(*args, **kwargs)

如果你想处理数据库约束,可以通过继承BaseConstraint来创建自己的约束类,并将你的SQL语句放在constraint_sql方法中。


这是一个有帮助的建议(因此被点赞),但有些更新在将值持久化到数据库时甚至不调用save()。我希望在数据库层面上强制执行约束,并考虑是否有方法可以覆盖meta来实现这一点。如果这不是一个选项,那么我会采取你上面建议的在视图/模型中编写逻辑而不是在数据库层面上进行强制执行的方法。 - onlyphantom
@onlyphantom 你可以通过继承 BaseConstraint 类并在 constraint_sql 方法中放置你的 SQL 代码,来创建自己的约束类。 - heemayl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接