【问题标题】:How to add Check Constraints for Django Model fields?在 Django 中的字段中添加额外的约束
【发布时间】:2011-01-17 21:07:01
【问题描述】:

在继承 db.models.Model 时,有时必须添加额外的检查/约束。

例如我有一个带有start_dateend_dateEvent 模型。

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

有多少种可能的方法来做到这一点?

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

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

【问题讨论】:

  • 您建议的约束不能定义为 sql 语句,因此您期望从此类检查中得到的唯一更改是管理员形式。您可以通过覆盖该类的 adminform 保存功能来做到这一点。 umnik700 的回答显示了您如何做到这一点。
  • 其实SQL中有一个“CHECK”约束。 PostgreSQL 支持这个:postgresql.org/docs/8.1/static/ddl-constraints.html 但是,MySQL 不支持这个:CHECK 子句被解析但被所有存储引擎忽略(参见dev.mysql.com/doc/refman/5.5/en/create-table.html
  • @slack3r:谢谢。我知道有一个检查,但我只希望它在更高级别,在 Django 元数据声明级别。我避免架构更改。
  • 是的,我知道,这只是回复 Numenor 说这不能定义为 sql 语句 :)
  • 在这种情况下,我会考虑使用 DateRange 字段。这将确保 start

标签: django django-models django-forms


【解决方案1】:

在模型的 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" 

【讨论】:

  • 在 django 1.2 或更高版本中,请记住将 *args, **kwargs 添加到覆盖的 save() 方法的定义以及调用它的任何地方
  • 恕我直言,检查保存模式很棒,但还不够。您应该始终在尽可能低的级别施加限制:在这种情况下,您希望数据库阻止存储任何破坏约束的值。
【解决方案2】:

我不会在 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')

【讨论】:

  • 感谢您的第二个/更好(?)答案。这正是我所需要的。
  • 当然,这仅在您使用ModelForm 或手动调用is_valid 时有效。否则,如果你只是打电话给saveit does nothing
  • 从 Django 2.2 开始,您还可以从模型的 Meta 类中添加数据库级别的约束。 docs.djangoproject.com/en/2.2/releases/2.2/#constraints
【解决方案3】:

正如@stefanw 所说,检查表单的 clean 方法会带来更好的用户体验。

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

  • @umnik700 说,更简单且独立于数据库的方法是在模型的保存方法中。请注意,这仍然不能阻止数据库的其他用户(其他应用或管理界面)创建不一致的状态。
  • 要“完全”确定数据库是一致的,您可以添加数据库级别的约束。例如。您可以使用 RunSQL 和 SQL 创建迁移,例如(未测试):

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

    (未测试)。这可能取决于数据库,这当然是一个缺点。

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

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

【讨论】:

【解决方案4】:

截至今天,postgres 9.4MS SQL Server >= 2008 都支持 sql 中的检查约束。最重要的是,django issue 11964 似乎从昨天开始就准备好进行审查了,所以希望我们能看到它集成到 django 2 中。rapilabs/django-db-constraints 项目似乎也实现了这一点。

【讨论】:

    【解决方案5】:

    从 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',
                ),
            ]
    

    【讨论】:

    • 这是目前正确的答案!现在用 Django 的人应该都知道这个功能吧!
    • 这是我一直在寻找的答案,几个小时 :) 所以对于其他人来说,重要的一点是您可以使用 F('other_field') 访问其他字段的值,从而进行比较约束在数据库级别,很棒的东西 :) 仍然需要验证,因为单独的约束会引发错误(服务器上为 500),但它保证数据库将作为最后的手段拒绝。
    【解决方案6】:

    总结之前的答案,这是我在一个项目中使用的完整解决方案:

    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可以处理这个问题:(

    【讨论】:

    • 这是我确认迄今为止有效的完整解决方案。处理数据库数据一致性和模型 (UI) 验证。
    猜你喜欢
    • 2019-01-28
    • 2012-02-04
    • 2019-12-30
    • 2011-07-04
    • 1970-01-01
    • 2015-06-19
    • 2019-04-15
    • 2013-01-21
    • 1970-01-01
    相关资源
    最近更新 更多