在 Unique Constraint 上制定条件 Q 以进行异常处理

Formulate conditional Q on Unique Constraint for exception handling

Django 不会抛出 ValidationError,因为 UniqueConstraint 中缺少条件,我不知道如何制定正确的条件。


我的一个模型包含一个包含外键的唯一约束:

class Entry(models.Model):
    """
    Entry on a List
    """
    name= models.CharField()
    expiration_date = models.DateField()
    list = models.ForeignKey(List,
                             related_name="entries",
                             on_delete=models.CASCADE)
    ...

    class Meta:
        constraints = [
            UniqueConstraint(fields=['list', 'name'],
                             name='unique_entry')  # Each entry_name may only occur once per list.
        ]

提交违反此约束的新条目时,数据库拒绝查询并且 Django 抛出未处理的异常 IntegrityError

根据 Django documentation 这是预期的行为:

Validation of Constraints

In general constraints are not checked during full_clean(), and do not raise ValidationErrors. Rather you’ll get a database integrity error on save(). UniqueConstraints without a condition (i.e. non-partial unique constraints) are different in this regard, in that they leverage the existing validate_unique() logic, and thus enable two-stage validation. In addition to IntegrityError on save(), ValidationError is also raised during model validation when the UniqueConstraint is violated.

我希望您能帮助我制定条件解决方案或其他建议的解决方案来解决此问题。目标是像对待任何其他不会验证的字段一样对待 UniqueConstraint:Django 抛出一个由 Django Rest Framework 捕获的 ValidationError,最终原始请求将收到 HTTP 400 Bad Request,而不是 500 Internal Server Error。

理想情况下,我想实现一个解决方案,该解决方案有助于从模型中抛出 ValidationError 而不是 IntegrityError,而不必求助于 SerializerView(我想避免的替代解决方案是从 View 查询数据库以验证唯一约束。)。

例如 condition=Q(name=self.name, list=self.list)。 但是,我无法从 Meta:

中引用模型实例
class Meta:
        constraints = [
            UniqueConstraint(fields=['list', 'name'],
                             condition=Q(name=self.name, list=self.list), # this is incorrect
                             name='unique_entry')
        ]

我是不是在尝试做一些不可能的事情?提前致谢,非常感谢您的想法和建议。

您可以在模型的 clean 方法中添加检查。如果您随后使用 ModelForm,它将调用 clean() 方法,从而检查该项目是否有效。

模型因此看起来像:

from django.core.exceptions import ValidationError

class Entry(models.Model):
    name= models.CharField()
    expiration_date = models.DateField()
    list = models.ForeignKey(
        List,
        related_name='entries',
        on_delete=models.CASCADE
    )
    
    def <strong>clean</strong>(self, *args, **kwargs):
        qs = Entry.objects.exclude(pk=self.pk).filter(<strong>name=self.name, list_id=self.list_id</strong>)
        if qs.exists():
            raise ValidationError('Can not add an entry with the same name to a list')
        return super().clean(*args, **kwargs)
    
    class Meta:
        constraints = [
            UniqueConstraint(
                fields=['list', 'name'],
                name='unique_entry'
            )
        ]