添加自定义Django模型验证

59 投票
4 回答
55635 浏览
提问于 2025-04-17 01:55

我有一个Django模型,它有一个开始日期和一个结束日期。我想要确保没有两个记录的日期范围是重叠的。有没有简单的方法来实现这个功能,这样我就不需要在不同地方重复写这个逻辑了?

比如说,我不想在表单、ModelForm、管理员表单和模型的save()方法中都重新实现这个逻辑。

据我所知,Django并没有很方便的方式来全局强制执行这些类型的规则。

在网上搜索也没什么帮助,因为“模型验证”通常指的是验证特定的模型字段,而不是整个模型的内容或字段之间的关系。

4 个回答

16

我觉得你应该使用这个链接:https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects

只需要在你的模型里定义一个叫做 clean() 的方法,像这样:(这是链接里的例子)

def clean(self):
    from django.core.exceptions import ValidationError
    # Don't allow draft entries to have a pub_date.
    if self.status == 'draft' and self.pub_date is not None:
        raise ValidationError('Draft entries may not have a publication date.')
    # Set the pub_date for published items if it hasn't been set already.
    if self.status == 'published' and self.pub_date is None:
        self.pub_date = datetime.datetime.now()
29

我会在模型上重写 validate_unique 这个方法。为了确保在验证时忽略当前这个对象,你可以使用以下代码:

from django.db.models import Model, DateTimeField
from django.core.validators import NON_FIELD_ERRORS, ValidationError

class MyModel(Model):
    start_date = DateTimeField()
    end_date = DateTimeField()

    def validate_unique(self, *args, **kwargs):
        super(MyModel, self).validate_unique(*args, **kwargs)

        qs = self.__class__._default_manager.filter(
            start_date__lt=self.end_date,
            end_date__gt=self.start_date
        )

        if not self._state.adding and self.pk is not None:
            qs = qs.exclude(pk=self.pk)

        if qs.exists():
            raise ValidationError({
                NON_FIELD_ERRORS: ['overlapping date range',],
            })

ModelForm 会通过 full_clean() 自动调用这个方法,你也可以手动使用它。

PPR 有一个很不错的讨论,讲了一个简单而正确的 日期范围重叠条件

71

我发现一个很有用的基本模式,就是把所有自定义的验证逻辑放在 clean() 这个方法里,然后在 save() 方法里面简单地调用一下 full_clean()(这个方法会调用 clean() 和其他一些方法),比如:

class BaseModel(models.Model):
    
    def clean(self, *args, **kwargs):
        # add custom validation here
        super().clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

默认情况下并不会这样做,具体原因可以在 这里 找到,因为这样会影响某些功能,但这些对我的应用来说并不是问题。

撰写回答