drf 源码分析之【Serializer-数据校验】

2022/6/26 14:21:26

本文主要是介绍drf 源码分析之【Serializer-数据校验】,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

引入一个例子:

models.py 点击查看
# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class Department(models.Model):
    """ 部门表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
    level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)

    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄", default=0)
    email = models.CharField(verbose_name="邮箱", max_length=64) # 注意CharField不合适
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    # 外键
    depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE)
    
    # 多对多
    roles = models.ManyToManyField(verbose_name="角色", to="Role")

 
定义Serializer(此例基于ModelSerializer)和视图中使用:

views.py 点击查看
# views.py
import re
from django.core.validators import EmailValidator  # 邮箱验证
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions

from app01 import models


class RegexValidator:
    def __init__(self, base):
        self.base = str(base)

    def __call__(self, value):  # 对象加()自动执行call方法
        match_obj = re.match(self.base, value)
        if not match_obj:
            raise serializers.ValidationError("格式错误")


class UserSerializer(serializers.ModelSerializer): # 继承ModelSerializer类
    email2 = serializers.CharField(label="邮箱2", validators=[RegexValidator(r"^\w+@\w+\.\w+$"), ])
    email3 = serializers.CharField(label="邮箱3")

    class Meta:
        model = models.UserInfo  # 类似ModelForm
        fields = ['username', 'age', 'email', 'email2', 'email3']
        extra_kwargs = {
            'username': {'min_length': 6, 'max_length': 32}, # 加上限制
            'email': {'validators': [EmailValidator, ]}  # 加个验证器
        }

    def validate_email3(self, value):  # 固定写法,validate_字段名
        '''钩子函数,自定义验证字段'''
        if re.match(r"^\w+@\w+\.\w+$", value):
            return value
        raise exceptions.ValidationError("邮箱格式错啦!!")  # 这里用的是exceptions.


class UserView(APIView):
    '''用户管理的视图函数'''

    def post(self, request):
        '''添加用户'''
        # 1.实例化Serializer对象
        ser = UserSerializer(data=request.data, )
        # 2.数据校验
        if not ser.is_valid():
            return Response({"code": 1006, "data": ser.errors})
        # 3.获取校验后的数据
        print(ser.validated_data)
        # 存入数据库(基于ModelSerializer的,字段与上面的序列化器一致,所以需要修改一下才可存入数据库)
        ser.validated_data.pop('email2')  # 数据库中没有的字段剔除掉
        ser.validated_data.pop('email3')
        # 4.存入数据库
        ser.save(level=1, password='123', depart_id=1)  # 存入到数据库,没有数据的字段要添加上。另,save()有返回值(新增的一行数据对象)

        return Response({"code": 0, "data": "创建成功!"})

 
首先是创建类的过程,与上篇序列化中的基本一致,这里不再赘述。直接看视图中使用的执行流程:

 

(1) 实例化Serializer对象

我们知道,在实例化对象时,会先执行__new__方法而后执行__init__方法,

顺着继承关系(UserSerializer --> ModelSerializer --> Serializer --> BaseSerializer )可以在BaseSerializer中找到这两个方法:

class BaseSerializer(Field):

    def __init__(self, instance=None, data=empty, **kwargs):
        self.instance = instance
        # data初始化
        if data is not empty:
            self.initial_data = data
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)
        super().__init__(**kwargs)

    def __new__(cls, *args, **kwargs):
        if kwargs.pop('many', False):
            return cls.many_init(*args, **kwargs)
        # 未定义many时,走这里直接创捷了对象
        return super().__new__(cls, *args, **kwargs)

 

(2) 数据校验

ser.is_valid(),上面创建的Serializer实例调用is_valid方法,顺着继承关系,可以在BaseSerializer中找到:

class BaseSerializer(Field):
    
    def is_valid(self, raise_exception=False):

        if not hasattr(self, '_validated_data'):
            try: # 走这执行校验
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}

        if self._errors and raise_exception:
            raise ValidationError(self.errors)
		# 通过校验则返回True
        return not bool(self._errors)

转到self.run_validation(self.initial_data)执行数据校验,顺着继承关系可以在Serializer中找到:

class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
    
    def run_validation(self, data=empty):

        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data
		# 1.调用字段内置校验 & validator校验 & 自定义的钩子方法校验
        value = self.to_internal_value(data)
        try:
            # 2.再次调用validator校验(为了啥?)
            self.run_validators(value)
            value = self.validate(value)
            assert value is not None, '.validate() should return the validated data'
        except (ValidationError, DjangoValidationError) as exc:
            raise ValidationError(detail=as_serializer_error(exc))

        return value

这一段显示出的其实有三种校验规则,我们的用例中也都有使用,下面来逐一分析:

  1. 调用字段内置校验 & validator校验 & 自定义的钩子方法校验

    class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
        
        def to_internal_value(self, data):
            """
            Dict of native values <- Dict of primitive datatypes.
            """
            ret = OrderedDict()
            errors = OrderedDict()
            # 获取字段对象生成器(Meta中定义的字段 + 类变量中定义的字段)(可写的字段)
            fields = self._writable_fields 
    		# 遍历每个字段实例,逐一校验
            for field in fields:
                # 获取自定义的钩子方法
                validate_method = getattr(self, 'validate_' + field.field_name, None)
                # 数据格式处理
                primitive_value = field.get_value(data)
                try:
                    # 1.1 执行字段内置的校验规则 & validator校验
                    validated_value = field.run_validation(primitive_value)
                    if validate_method is not None:
                        # 1.2 执行钩子方法校验规则
                        validated_value = validate_method(validated_value)
                except ValidationError as exc:
                    errors[field.field_name] = exc.detail
                except DjangoValidationError as exc:
                    errors[field.field_name] = get_error_detail(exc)
                except SkipField:
                    pass
                else: # 校验后的数据放入字典中
                    set_value(ret, field.source_attrs, validated_value)
    
            if errors:
                raise ValidationError(errors)
    
            return ret
    

    1.1 执行字段内置的校验规则 & validator校验:字段实例调用run_validation(primitive_value)方法,比如CharField实例调用的执行源码如下:

    class CharField(Field):
        
        def run_validation(self, data=empty):
    		# 空字符串就别凑热闹了,直接回吧
            if data == '' or (self.trim_whitespace and str(data).strip() == ''):
                if not self.allow_blank:
                    self.fail('blank')
                return ''
            # 转到父类中执行
            return super().run_validation(data)
        
    class Field:
        
        def run_validation(self, data=empty):
    
            (is_empty_value, data) = self.validate_empty_values(data)
            if is_empty_value:
                return data
            # 字段内置的数据校验
            value = self.to_internal_value(data)
            # 调用validator验证器校验
            self.run_validators(value)
            return value
    

    可以看到,最终先进行了字段内置的数据校验,不同字段类型的校验方式都不相同,这里不再一一解析。而后又对校验过的数据,再次进行validator验证器校验,这个validator有我们在字段实例中传的参数validators=[?,..],如本例中的emailemail2的字段,其实也还有一些其他的在实例化字段对象时,内部就放进去了一些验证器,这里不再展开讲,只要知道我们自定义的验证器会在这里执行即可。我们来看下它验证的源码:

    class Field:        
        def run_validators(self, value):
    		# 遍历每个validator校验数据
            errors = []
            for validator in self.validators:
                if hasattr(validator, 'set_context'):
                    warnings.warn(
                        "Method `set_context` on validators is deprecated and will "
                        "no longer be called starting with 3.13. Instead set "
                        "`requires_context = True` on the class, and accept the "
                        "context as an additional argument.",
                        RemovedInDRF313Warning, stacklevel=2
                    )
                    validator.set_context(self)
    
                try: # 执行validator对象的`__call__`方法
                    if getattr(validator, 'requires_context', False):
                        validator(value, self)
                    else:
                        validator(value)
                except ValidationError as exc:
                    # If the validation error contains a mapping of fields to
                    # errors then simply raise it immediately rather than
                    # attempting to accumulate a list of errors.
                    if isinstance(exc.detail, dict):
                        raise
                    errors.extend(exc.detail)
                except DjangoValidationError as exc:
                    errors.extend(get_error_detail(exc))
            if errors:
                raise ValidationError(errors)
    

    1.2 执行钩子方法校验规则

    上面的校验执行完后,再往下走就是:如果有自定义了校验方法,就会执行这个方法validate_method(validated_value)

    这里需要注意的地方就是关于钩子方法的定义:validate_method = getattr(self, 'validate_' + field.field_name, None),可以看到是有固定的格式的,所以我们在自定义的时候要按validate_字段名这样的格式来写,否则无法获取,比如用例中自定义的:validate_email3方法。

  2. 再次调用validator校验

    self.run_validators(value)执行源码如下:

    class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
        
        def run_validators(self, value):
            """
            Add read_only fields with defaults to value before running validators.
            """
            if isinstance(value, dict):
                to_validate = self._read_only_defaults()
                to_validate.update(value)
            else:
                to_validate = value
            super().run_validators(to_validate)
            
    
    

    super().run_validators(to_validate),在BaseSerializer的父类Field类中找到:

    class Field:        
        def run_validators(self, value):
            pass
    

    可以发现最终又回到了field类中的run_validators方法,执行validator验证器校验,那与前面的有什么不同呢?可以发现参数不同,也就是校验的对象不同,在上面它把read_only的字段也加入到数据中去校验了(看英文注释)。这里就不再展开讲了(其实是暂时没想明白...),总之,校验数据的流程到这就完了,接下来看看获取校验后的数据:

 

(3) 获取校验后的数据

Serializer对象调用validated_data属性即可获取,顺着继承关系,在BaseSerializer中可以找到:

class BaseSerializer(Field):
    
    @property
    def validated_data(self):
        if not hasattr(self, '_validated_data'):
            msg = 'You must call `.is_valid()` before accessing `.validated_data`.'
            raise AssertionError(msg)
        return self._validated_data

返回的是self._validated_data,这个其实就是我们在执行数据校验后所得到的数据:

def is_valid(self, raise_exception=False):
	if not hasattr(self, '_validated_data'):
        try: # 走这执行校验
        	self._validated_data = self.run_validation(self.initial_data)
        pass

 

(4) 存入数据库

Serializer对象调用save方法即可获取,顺着继承关系,在BaseSerializer中可以找到:

class BaseSerializer(Field):
    
    def save(self, **kwargs):

        validated_data = {**self.validated_data, **kwargs}
		# 如果传了instance参数,执行update方法
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else: # 如果没有传instance参数(如用例中传的data),执行create方法
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )

        return self.instance

在实例化Serializer对象时,数据源会通过参数instancedata传值,如果是数据库中取出来的数据,自然是传instance,那就执行update方法,而如果是用户传来的数据,自然传的就是data,要存入数据库那就得执行update方法,这两个方法,在ModelSerializer中都有定义:

class ModelSerializer(Serializer):
    # 创建数据
    def create(self, validated_data):
   
        raise_errors_on_nested_writes('create', self, validated_data)

        ModelClass = self.Meta.model

      	# 剥离m2m字段后,逐一存入数据库
        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        try:
            instance = ModelClass._default_manager.create(**validated_data)
        except TypeError:
			pass

        # m2m字段单独再保存
        if many_to_many:
            for field_name, value in many_to_many.items():
                field = getattr(instance, field_name)
                field.set(value)

        return instance
    
    # 更新数据
    def update(self, instance, validated_data):
        raise_errors_on_nested_writes('update', self, validated_data)
        info = model_meta.get_field_info(instance)
        
		# 剥离m2m字段后,逐一存入数据库
        m2m_fields = []
        for attr, value in validated_data.items():
            if attr in info.relations and info.relations[attr].to_many:
                m2m_fields.append((attr, value))
            else:
                setattr(instance, attr, value)

        instance.save()
        
		# m2m字段单独再保存
        for attr, value in m2m_fields:
            field = getattr(instance, attr)
            field.set(value)

        return instance

至此,完结。

 

补充和疑问:

关于:为什么在校验数据阶段,校验完一次后要再次调用run_validators方法,把read_only字段加入校验?的一点思考:

当我们定义了嵌套序列化器的时候,创建类时并不会把自定义的嵌套序列化器加入_declared_fields中(因为其不属于字段实例),所以后面校验阶段的字段是Meta元数据添加进去的。(m2m和Fk怎么加入字段的呢?)

所以当有嵌套的Serializer,要进行数据校验时,只有两种选择:

  • 将嵌套的序列化器设置成 read_only
  • 自定义create和update方法,自定义新建和更新的逻辑(那么问题来了,如何校验的呢?...)


这篇关于drf 源码分析之【Serializer-数据校验】的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程