在对db.models.Model
进行子类化时,有时需要添加额外的检查/约束。
E.g。我有一个Event
模型start_date
和end_date
。
我想在字段或模型中添加验证,以便end_date > start_date
。
有多少种方法可以做到这一点?
至少我知道这可以在models.Model
验证中的ModelForm
之外完成。
但是如何附加到字段和models.Model
?
答案 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所说,检查表单的干净方法是更好的用户体验。
如果你非常确定没有,也永远不会有另一种改变价值的方法,那就足够了。但由于您很少能够确定,如果数据库一致性很重要,您可以添加另一个检查(除了表单),其中一个:
要“完全”确保数据库是一致的,您可以添加数据库级别约束。例如。您可以使用RunSQL和SQL创建迁移,类似于(未经测试):
migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
(未经测试)。这可能与数据库有关,当然这是一个缺点。
在您的示例中,它可能不值得(不正确的开始/结束时间看起来有点奇怪,但只影响一个不一致的事件),并且您不希望手动架构更改。但它在一致性至关重要的情况下很有用。
编辑:您也可以只保存开始时间和持续时间,而不是开始和结束时间。
答案 4 :(得分:2)
截至今天,postgres 9.4和MS 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
可以解决这个问题:(