在Django中的字段中添加额外的约束

时间:2010-02-17 13:54:35

标签: django django-models django-forms

在对db.models.Model进行子类化时,有时需要添加额外的检查/约束。

E.g。我有一个Event模型start_dateend_date

我想在字段或模型中添加验证,以便end_date > start_date

有多少种方法可以做到这一点?

至少我知道这可以在models.Model验证中的ModelForm之外完成。

但是如何附加到字段和models.Model

6 个答案:

答案 0 :(得分:46)

我不会在save方法中加入这样的约束,但为时已晚。在那里引发异常对于以错误方式输入数据的用户没有帮助,因为它最终会以500结尾,并且用户将无法获得有错误的表单等。

您应该在Forms / ModelForms clean方法中检查这一点并引发ValidationError,因此form.is_valid()返回false,您可以将表单中的错误发送回用户进行更正。

另请注意,自1.2版以来,Django已经Model Validation

它看起来像这样:

class Foo(models.Model):
    #  ... model stuff...
    def clean(self):
        if self.start_date > self.end_date:
            raise ValidationError('Start date is after end date')

答案 1 :(得分:11)

在模型的 save 方法中执行此操作:

def save(self, *args, **kwargs):
    if(self.end_date > self.start_date):
        super(Foo, self).save(*args, **kwargs)
    else:
        raise Exception, "end_date should be greater than start_date" 

答案 2 :(得分:11)

从Django 2.2开始,支持数据库级别 constraints

from django.db import models
from django.db.models import CheckConstraint, Q, F

class Event(models.Model):
    start_date = models.DatetimeField() 
    end_date = models.DatetimeField()

    class Meta:
        constraints = [
            CheckConstraint(
                check = Q(end_date__gt=F('start_date')), 
                name = 'check_start_date',
            ),
        ]

答案 3 :(得分:9)

正如@stefanw所说,检查表单的干净方法是更好的用户体验。

如果你非常确定没有,也永远不会有另一种改变价值的方法,那就足够了。但由于您很少能够确定,如果数据库一致性很重要,您可以添加另一个检查(除了表单),其中一个:

  • 与@ umnik700相比,模型的保存方法更容易且与数据库无关。请注意,这仍然不会阻止数据库的其他用户(另一个应用程序或管理界面)创建不一致的状态。
  • 要“完全”确保数据库是一致的,您可以添加数据库级别约束。例如。您可以使用RunSQL和SQL创建迁移,类似于(未经测试):

    migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
    

    (未经测试)。这可能与数据库有关,当然这是一个缺点。

在您的示例中,它可能不值得(不正确的开始/结束时间看起来有点奇怪,但只影响一个不一致的事件),并且您不希望手动架构更改。但它在一致性至关重要的情况下很有用。

编辑:您也可以只保存开始时间和持续时间,而不是开始和结束时间。

答案 4 :(得分:2)

截至今天,postgres 9.4MS SQL Server >= 2008都支持在sql中检查约束。除此之外,还有django issue 11964似乎已经准备好从昨天开始审核了,所以我们希望我们可以看到它集成到django 2.项目rapilabs/django-db-constraints似乎也实现了这一点。

答案 5 :(得分:0)

总结以前的答案,这是我用于项目的完整解决方案:

from django.db import models
from django.db.models import CheckConstraint, Q, F
from django.utils.translation import gettext_lazy as _

class Event(models.Model):
    start_date = models.DatetimeField() 
    end_date = models.DatetimeField()

    class Meta:
        constraints = [
            # Ensures constraint on DB level, raises IntegrityError (500 on debug=False)
            CheckConstraint(
                check=Q(end_date__gt=F('start_date')), name='check_start_date',
            ),
        ]

    def clean(self):
        # Ensures constraint on model level, raises ValidationError
        if self.start_date > self.end_date:
            # raise error for field
            raise ValidationError({'end_date': _('End date cannot be smaller then start date.')})

太糟糕了,没有django.core.validators可以解决这个问题:(