Django 模型字段验证

64 投票
4 回答
58576 浏览
提问于 2025-04-15 15:23

在Django中,模型字段的验证应该放在哪里呢?

我可以提到至少两个可能的选择:一个是在模型的.save()方法中,另一个是在models.Field子类的.to_python()方法中(当然,要让这个方法有效,你需要自己写自定义字段)。

可能的使用场景有:

  • 当我们必须确保空字符串不会被写入数据库时(这里的blank=False参数不起作用,它只用于表单验证)
  • 当我们需要确保“choices”参数在数据库层面也能被遵循,而不仅仅是在管理界面中(这有点像模拟枚举数据类型)

在models.Field基类定义中,还有一个类级别的属性empty_strings_allowed,派生类也可以覆盖它,但这似乎对数据库没有任何影响,这意味着我仍然可以创建一个包含空字符串字段的模型并将其保存到数据库中。我想避免这种情况(是的,这是必要的)。

可能的实现方式有:

在字段级别:

class CustomField(models.CharField):
    __metaclass__ = models.SubfieldBase
    def to_python(self, value):
        if not value:
            raise IntegrityError(_('Empty string not allowed'))
        return models.CharField.to_python(self, value)

在模型级别:

class MyModel(models.Model)
    FIELD1_CHOICES = ['foo', 'bar', 'baz']
    field1 = models.CharField(max_length=255, 
               choices=[(item,item) for item in FIELD1_CHOICES])

    def save(self, force_insert=False, force_update=False):
        if self.field1 not in MyModel.FIELD1_CHOICES:
            raise IntegrityError(_('Invalid value of field1'))
        # this can, of course, be made more generic
        models.Model.save(self, force_insert, force_update)

也许我漏掉了什么,这样做是否有更简单(更干净)的方法呢?

4 个回答

3

这个问题的根本原因在于,验证应该在模型上进行。这个话题在django社区讨论了很长时间(可以在开发者邮件列表中搜索“表单模型验证”)。如果不这样做,就会导致重复代码,或者在数据进入数据库之前就绕过了验证。

虽然这个问题没有影响到主干代码,但Malcolm的“简易模型验证解决方案”可能是避免重复代码的最简洁的方法。

7

我觉得你想要这个 ->

from django.db.models.signals import pre_save

def validate_model(sender, **kwargs):
    if 'raw' in kwargs and not kwargs['raw']:
        kwargs['instance'].full_clean()

pre_save.connect(validate_model, dispatch_uid='validate_models')

(复制自 http://djangosnippets.org/snippets/2319/)

75

Django 从 1.2 版本开始就有一个 模型验证 系统。

在评论中,sebpiq 说:“好吧,现在有地方可以放模型验证了……不过,这个验证只有在使用 ModelForm 时才会运行!所以问题来了,当我们需要确保数据库层面也遵循验证时,该怎么办?在哪里调用 full_clean?”

通过 Python 层面的验证,无法确保数据库层面也遵循验证。最接近的做法可能是在重写的 save 方法中调用 full_clean。但这不是默认的做法,因为这意味着每个调用这个保存方法的人都需要准备好捕捉和处理 ValidationError

即使你这样做了,还是有人可以通过 queryset.update() 批量更新模型实例,这样就会绕过这个验证。Django 没办法实现一个既高效又能在每个更新对象上执行 Python 层面验证的 queryset.update()

要真正保证数据库层面的完整性,唯一的方法就是通过数据库层面的约束;你通过 ORM 做的任何验证都需要应用代码的编写者知道何时执行验证(并处理验证失败的情况)。

这就是为什么模型验证默认只在 ModelForm 中执行的原因——因为在 ModelForm 中已经有明显的方法来处理 ValidationError

撰写回答