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方法,自定义新建和更新的逻辑(那么问题来了,如何校验的呢?...)
标签:self,校验,value,instance,源码,validated,data,Serializer,drf 来源: https://www.cnblogs.com/chidafy/p/16412375.html