Django模型验证器介绍与源码分析

时间:2021-05-22

前言

在Django的模型字段参数中,有一个参数叫做validators,这个参数是用来指定当前字段需要使用的验证器,也就是对字段数据的合法性进行验证,比如大小、类型等。

Django的验证器可以分为模型相关的验证器和表单相关的验证器,它们基本类似,但在使用上有区别。

本文讨论的是模型相关的验证器。

一、自定义验证器

一个验证器其实就是一个可调用的对象(函数或类),接收一个初始输入值作为参数,对这个值进行一系列逻辑判断,如果不满足某些规则或者条件,则表示验证不通过,抛出一个ValidationError异常。如果满足条件则通过验证,不返回任何内容(也就是默认的return None),可以继续下一步。

验证器具有重要作用,可以被重用在别的字段上,是工具类型的逻辑封装。

下面是一个验证器的例子,它只允许偶数通过验证:

from django.core.exceptions import ValidationErrorfrom django.utils.translation import gettext_lazy as _def validate_even(value): if value % 2 != 0: raise ValidationError( _('%(value)s is not an even number'), params={'value': value}, )

通过下面的方式,将偶数验证器应用在字段上:

from django.db import modelsclass MyModel(models.Model): even_field = models.IntegerField(validators=[validate_even])

因为验证器运行之前,(输入的)数据会被转换为 Python 对象,因此我们可以将同样的验证器用在 Django form 表单中(事实上Django为表单提供了另外一些验证器):

from django import formsclass MyForm(forms.Form): even_field = forms.IntegerField(validators=[validate_even])

你还可以通过Python的魔法方法__cal__()编写更复杂的可配置的验证器,比如Django内置的RegexValidator验证器就是这么干的。

验证器也可以是一个类,但这时候就比较复杂了,需要确保它可以被迁移框架序列化,确保编写了deconstruction()和__eq__()方法。这种做法很难找到参考文献和博文,要靠自己摸索或者研究DJango源码。

二、工作机制

让我们来测试一下上面写的验证器:

>>> from .models import MyModel>>> a = MyModel.objects.create(even_field=3)>>> a<MyModel: MyModel object (1)>>>> a.even_field3

什么?!!!不是说只有偶数才能通过验证吗?这里我提供了数字3,可是为什么创建成功了??

我们接着在admin站点中注册MyModel模型,然后在图形化界面后台中创建MyModel的实例,你会发现这个时候验证器起作用了,奇数是无法通过表单验证的!

为什么会这样??

这就要从Django的源码说起!

Django是这么设计的:

  • 模型的验证器不会在调用save()方法的时候自动执行
  • 表单的验证器会在调用save()方法的时候自动执行

为什么这么设计?个人猜测,Django官方为了序列化、链式调用等功能的兼容性,没有自动进行验证操作。

这个设计在源码中是怎么体现的?

  • Django的模型相关源码中,没有is_valid()方法,也不会自动调用full_clean() 方法,所以Django不会自动进行模型验证。但是它依然提供了四个重要的验证方法,也就是full_clean() 、clean_fields() 、clean() 和validate_unique(),一会细说
  • Django的表单系统forms的相关源码中,表单在save之前会自动执行一个is_valid()方法,这个方法里会调用验证器。

表单的内容在其它章节中讲解。

下面介绍Django模型的验证步骤和四个方法:

模型验证的步骤:

  • 如果你手动调用了full_clean()方法,那么会依次自动调用下面的三个方法
  • clean_fields():验证各个字段的合法性
  • clean():验证模型级别的合法性
  • validate_unique():验证字段的独一无二性

本质上,后面三个方法是具体实现,full_clean()是领头羊,实际操作中,你完全可以具体使用其中一个或多个。用了full_clean()就等于后面三个都用。

full_clean()

签名:Model.full_clean(exclude=None, validate_unique=True)

  • exclude用于指定某些字段不进行验证,也就是所谓的例外字段
  • validate_unique用于指定是否调用validate_unique()方法

让我们看下它的源代码:

def full_clean(self, exclude=None, validate_unique=True): errors = {} if exclude is None: exclude = [] else: exclude = list(exclude) try: self.clean_fields(exclude=exclude) #1 except ValidationError as e: errors = e.update_error_dict(errors) try: self.clean() #2 except ValidationError as e: errors = e.update_error_dict(errors) if validate_unique: for name in errors: if name != NON_FIELD_ERRORS and name not in exclude: exclude.append(name) try: self.validate_unique(exclude=exclude) #3 except ValidationError as e: errors = e.update_error_dict(errors) if errors: raise ValidationError(errors)

可以看出,它依次调用了其它三个方法,如果最后的errors中有内容,则抛出ValidationError异常。

我们最好不要去修改full_clean()方法的源代码,一般也不用重写它,直接调用即可。

模型的save()方法不会自动调用full_clean()方法,你必须手动调用。

如果调用验证器后,抛出ValidationError异常,Django会将所有的异常信息放置在e.message_dict字典中供使用。比如下面的例子:

# 在视图中我们可以这么做from django.core.exceptions import ValidationErrortry: article.full_clean()except ValidationError as e: # 在这里做一些异常处理操作 pass

在模型定义中我们可以如下重写save()方法,实现自动验证功能,不需要在视图中反复调用了:

# models.pyfrom django.core.exceptions import ValidationErrorfrom django.utils.translation import gettext_lazy as _def validate_even(value): if value % 2 != 0: raise ValidationError( _('%(value)s is not an even number'), params={'value': value}, ) from django.db import modelsclass MyModel(models.Model): even_field = models.IntegerField(validators=[validate_even]) def save(self, *args, **kwargs): # 重写save方法是关键 try: self.full_clean() super().save(*args, **kwargs) except ValidationError as e: print('模型验证没通过: %s' % e.message_dict)

执行过程展示:

>>> from .models import MyModel>>> a = MyModel.objects.create(even_field=5)模型验证没通过: {'even_field': ['5 is not an even number']}

这样,我们就实现了自动的模型验证。

小技巧:可以通过打印e来查看,Django怎么封装的错误信息,给我们提供了哪些键值,比如上例中,我们可以使用e.message_dict['even_field']。

clean_fields()

签名:Model.clean_fields(exclude=None)

参数同上,看下它的源代码:

def clean_fields(self, exclude=None): if exclude is None: exclude = [] errors = {} for f in self._meta.fields: if f.name in exclude: continue raw_value = getattr(self, f.attname) if f.blank and raw_value in f.empty_values: continue try: setattr(self, f.attname, f.clean(raw_value, self)) #核心是这一句 except ValidationError as e: errors[f.name] = e.error_list if errors: raise ValidationError(errors)

我们最好也不要去修改和重写它的源代码。

这个方法本质上就是循环模型中的所有字段,找出其中定义了验证器的那些,并执行它们。

我们前面自定义的偶数验证器,其实就是在这里被调用的。

clean()

这个方法很特别,我们看看它的源代码:

def clean(self): """ Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS. """ pass

什么都没有!实际上,这个方法是给你留了个钩子,你需要重写它,然后在里面编写模型级别的验证,比如修改模型的属性,以及跨字段相关的验证逻辑。

下面我们通过一个例子来展示它的用法:

import datetimefrom django.core.exceptions import ValidationErrorfrom django.db import modelsfrom django.utils.translation import gettext_lazy as _class Article(models.Model): content = models.TextField() status = models.CharField(max_length=32) pub_date = models.DateField(blank=True, null=True) def clean(self): # 不允许草稿文章具有发布日期字段 if self.status == '草稿' and self.pub_date is not None: raise ValidationError(_('草稿文章尚未发布,不应该有发布日期!')) # 如果已发布的文章还没有设置发布日期,则将发布日期设置为当天 if self.status == '已发布' and self.pub_date is None: self.pub_date = datetime.date.today()# 更多Django技术文章请访问https:///video/

总结

到此这篇关于Django模型验证器介绍与源码分析的文章就介绍到这了,更多相关Django模型验证器与源码内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章