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
这一段显示出的其实有三种校验规则,我们的用例中也都有使用,下面来逐一分析:
-
调用字段内置校验 & 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=[?,..]
,如本例中的email
和email2
的字段,其实也还有一些其他的在实例化字段对象时,内部就放进去了一些验证器,这里不再展开讲,只要知道我们自定义的验证器会在这里执行即可。我们来看下它验证的源码: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
方法。 -
再次调用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对象时,数据源会通过参数instance
或data
传值,如果是数据库中取出来的数据,自然是传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-数据校验】的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南