如何为 Django 模型字段添加检查约束?

发布于 2024-08-22 14:52:05 字数 293 浏览 5 评论 0原文

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

例如,我有一个带有 start_dateend_dateEvent 模型:我想在字段或模型中添加验证,以便 结束日期>开始日期

至少我知道这可以在 ModelForm 验证内的 models.Model 之外完成。但是如何附加到字段和 models.Model 呢?

While subclassing db.models.Model, sometimes it's essential to add extra checks/constraints.

For example, I have an Event model with start_date and end_date: I want to add validation into the fields or the model so that end_date > start_date.

At least I know this can be done outside the models.Model inside the ModelForm validation. But how to attach to the fields and the models.Model?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

治碍 2024-08-29 14:52:05

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

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

As of Django 2.2, database level constraints are supported:

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',
            ),
        ]
森林散布 2024-08-29 14:52:05

我不会在 save 方法中添加这样的约束,为时已晚。在那里引发异常,对以错误方式输入数据的用户没有帮助,因为它最终会变成 500,并且用户不会收到有错误的表单等。

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

另请注意,自版本 1.2 以来,Django 具有 模型验证

它看起来像这样:

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

I would not put constraints like these in the save method, it's too late. Raising an exception there, doesn't help the user who entered the data in the wrong way, because it will end up as a 500 and the user won't get the form with errors back etc.

You should really check for this in the Forms/ModelForms clean method and raise a ValidationError, so form.is_valid() returns false and you can send the errors in the form back to the user for correction.

Also note that since version 1.2, Django has had Model Validation.

It would look something like this:

class Foo(models.Model):
    #  ... model stuff...
    def clean(self):
        if self.start_date > self.end_date:
            raise ValidationError('Start date is after end date')
少年亿悲伤 2024-08-29 14:52:05

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

Do it inside your save method of your model:

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" 
浅忆 2024-08-29 14:52:05

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

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

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

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

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

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

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

As @stefanw says, it's better user experience to check in the form's clean method.

This is enough if you're very sure that there isn't, and never will be, another way to change the value. But since you can rarely be sure of that, if database consistency is important, you can add another check (in addition to the form), one of:

  • The easier and database-independent way is in the model's save method as @umnik700 said. Note that this still doesn't prevent other users of the database (another app, or the admin interface) from creating an inconsistent state.
  • To be 'completely' sure the database is consistent, you can add a database level constraint. E.g. you can create a migration with RunSQL and SQL, something like (not tested):

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

    (Not tested). This may be database dependent, which is a downside of course.

In your example, it's probably not worth it (incorrect start/end times just look a bit weird, but affect only the one inconsistent event), and you don't want manual schema changes. But it's useful in cases where consistency is critical.

EDIT: You can also just save the start time and the duration, instead of the start and end times.

相权↑美人 2024-08-29 14:52:05

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

As of today, both postgres 9.4 and MS SQL Server >= 2008 support check constraints in sql. On top of this, there is django issue 11964 which seems to be ready for review since yesterday, so hopefully we'll see this integrated into django 2. The project rapilabs/django-db-constraints seems to implement this too.

你是年少的欢喜 2024-08-29 14:52:05

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

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

Summarizing the answers from before, here is a complete solution I used for a project:

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.')})

Too bad there is no django.core.validators that can handle this :(

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文