先说序列化器类验证的三种常用方式
drf有自身的一个验证机制,比较简单,比如匹配字段,类型,字符串长度等,这些洗染满足不了要求,当默认的校验不能完全满足校验需求时,就需要考虑进行补充验证。
方式1:validators(了解)
针对指定序列化器字段添加 validators 选项参数补充校验。
def about_django(value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")
# 注意:必须返回值!!!
return value
class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名称', max_length=20, validators=[about_django])
bpub_date = serializers.DateField(label='发布日期')
bread = serializers.IntegerField(label='阅读量', required=False)
bcomment = serializers.IntegerField(label='评论量', required=False)
运行代码:
from booktest.serializers import BookInfoSerializer
if __name__ == '__main__':
data = {'btitle': 'python', 'bpub_date': '2019-06-01'}
serializer = BookInfoSerializer(data=data)
res = serializer.is_valid()
if res:
print(serializer.validated_data)
else:
print(serializer.errors)
显示结果:
{'btitle': [ErrorDetail(string='图书不是关于Django的', code='invalid')]}
结果说明:
在针对 btitle 字段的内容进行校验时,除了进行基本验证,还调用了 validators 参数指定的补充验证函数 about_django,因为传递的 btitle 字段中未含有 django,所以校验失败。
方式2:validate_<field_name>
在序列化器类中定义特定方法 validate_<field_name>,针对特定字段数据进行补充验证。
class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
...
btitle = serializers.CharField(label='名称', max_length=20)
def validate_btitle(self, value):
"""此方法针对btitle字段的内容进行补充验证"""
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的-2")
# 注意:必须返回值!!!
return value
运行代码:
from booktest.serializers import BookInfoSerializer
if __name__ == '__main__':
data = {'btitle': 'python', 'bpub_date': '2019-06-01'}
serializer = BookInfoSerializer(data=data)
res = serializer.is_valid()
if res:
print(serializer.validated_data)
else:
print(serializer.errors)
显示结果:
{'btitle': [ErrorDetail(string='图书不是关于Django的-2', code='invalid')]}
结果说明:
在针对 btitle 字段的内容进行校验时,除了进行基本验证,还调用了 validate_btitle 函数针对 btitle 字段的内容进行补充验证,因为传递的 btitle 字段中未含有 django,所以校验失败。
方式3:validate
在序列化器类中定义 validate 方法进行补充验证,validate 可以结合多个字段的内容进行补充验证。
class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
...
btitle = serializers.CharField(label='名称', max_length=20)
def validate(self, attrs):
btitle = attrs.get('btitle')
if 'django' not in btitle.lower():
raise serializers.ValidationError("图书不是关于Django的-3")
# 注意:必须返回值!!!
return attrs
运行代码:
from booktest.serializers import BookInfoSerializer
if __name__ == '__main__':
data = {'btitle': 'python', 'bpub_date': '2019-06-01'}
serializer = BookInfoSerializer(data=data)
res = serializer.is_valid()
if res:
print(serializer.validated_data)
else:
print(serializer.errors)
显示结果:
{'non_field_errors': [ErrorDetail(string='图书不是关于Django的-3', code='invalid')]}
结果说明:
在针对 btitle 字段的内容进行校验时,除了进行基本验证,还调用了 validate 函数针对 btitle 字段的内容进行补充验证,因为传递的 btitle 字段中未含有 django,所以校验失败。
上面讲述了序列化器类验证的三种常用方式,接下来围绕这三种方式展开源码解析
def is_valid(self, raise_exception=False):
#一般来说,会直接调用self.run_validation(self.initial_data)
#self.initial_data就是客户端传递过来要反序列化的数据
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.run_validation(self.initial_data)
进入run_validation(self.initial_data)函数看一下:
#对readonly和data做验证的,先不管它
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
#假设程序跳过上步,直接执行这一步
value = self.to_internal_value(data)#field中也有to_internal_value()方法,注意区分
进入self.to_internal_value(data)函数看一下这个函数在干嘛?
def to_internal_value(self, data):
#略过部分无用代码
ret = OrderedDict()
errors = OrderedDict()
#
fields = self._writable_fields #如果验证的属性不为read_only,则返回
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
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#验证完数据没问题,返回
@property
def _writable_fields(self):
for field in self.fields.values():#{'id': IntegerField(label='ID', read_only=True), 'username': CharField(label='用户名'), 'password': CharField(max_length=128, write_only=True), 'token': CharField(label='JWT Token', read_only=True)}
if not field.read_only:
yield field
这个self.fields.values()是什么鬼?干什么的?点进去看一下:
@cached_property
def fields(self):
"""
A dictionary of {field_name: field_instance}.{}
{mobile:models.CharField()}
"""
# `fields` is evaluated lazily. We do this to ensure that we don't
# have issues importing modules that use ModelSerializers as fields,
# even if Django's app-loading stage has not yet run.
#“fields”的计算是惰性的。我们这么做是为了确保我们不会
#在导入使用ModelSerializers作为字段的模块时出现问题,
fields = BindingDict(self)
for key, value in self.get_fields().items():
fields[key] = value
return fields
其他的先不看,英文的解释已经很清楚了,就是惰性获取我们定义的ModelSerializers类的序列化属性的信息,格式是个字典。
点BindingDict(self)实例进去看一下(注意:这个self是序列化器的类的实例):
class BindingDict(MutableMapping):
"""
This dict-like object is used to store fields on a serializer.
This ensures that whenever fields are added to the serializer we call
`field.bind()` so that the `field_name` and `parent` attributes
can be set correctly.
这个类似于dict的对象用于在序列化器中存储字段。
这确保了无论何时将字段添加到我们调用的序列化器中
' field.bind() '使' field_name '和' parent '属性
可以正确设置。
"""
def __init__(self, serializer):
self.serializer = serializer
self.fields = OrderedDict()
事实上,这个代码什么都没干,我们就传递了一self,下面的赋值可以说赋的都是空值,占个位,使程序不报错,后续才能用到,
再次回到 fields(self)函数:
@cached_property
def fields(self):
fields = BindingDict(self)#就是一个空对象实例没接下来才进行赋值
for key, value in self.get_fields().items():
fields[key] = value
return fields
self.get_fields()调用了items()函数,应该是个字典,据猜测,应该是获取序列化类的所有属性和对应的field()的值,然后以字典的形式返回,先进入self.get_fields()函数看一下,印证一下猜测:
def get_fields(self):
"""
Returns a dictionary of {field_name: field_instance}.
返回一个{field_name: field_instance}的字典,之前的猜测应该是对的。
"""
# Every new serializer is created with a clone of the field instances.
# This allows users to dynamically modify the fields on a serializer
# instance without affecting every other serializer instance.
#每个新的序列化器都是用字段实例的克隆创建的。
#允许用户动态修改序列化器上的字段
# instance而不影响其他序列化器实例。
#是个人也看出来了,这里用的是深拷贝,连老底都copy一份,随便改
#事实上,真正干活的是self._declared_fields,他应该是通过某种机制动态获取序列化类的信息的
"""
OrderedDict([('username', CharField(label='用户名')),
('token', CharField(label='JWT Token', read_only=True))])
"""
#然后把这个有序字典copy一份,返回
return copy.deepcopy(self._declared_fields)
再次回 到fields(self)函数:
@cached_property
"""
OrderedDict([('username', CharField(label='用户名')),
('token', CharField(label='JWT Token', read_only=True))])
username是key,CharField(label='用户名')是value,很具有迷惑性
"""
def fields(self):
fields = BindingDict(self)#就是一个空对象实例没接下来才进行赋值
for key, value in self.get_fields().items():
fields[key] = value
return fields
就是说,通过for循环,会创建N个BindingDict()实例对象,并分对属性进行赋值,传进来的参数正是被serlializer给接收了,参照下面代码:
class BindingDict(MutableMapping):
def __init__(self, serializer):
self.serializer = serializer
self.fields = OrderedDict()
然后把fields对象返回给_writable_fields()函数:
@property
def _writable_fields(self):
for field in self.fields.values():#{'id': IntegerField(label='ID', read_only=True), 'username': CharField(label='用户名'), 'password': CharField(max_length=128, write_only=True), 'token': CharField(label='JWT Token', read_only=True)}
#遍历自字典,看看每个属性是否是read_only,如果不是,就返回,咱们这里是反序列化,肯定不是
if not field.read_only:
#注意,出现了yield关键字,说明是生成器,生出来一个返回一个
yield field
再次回到to_internal_value()函数:
def to_internal_value(self, data):
#代码略。。。
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields #如果验证的属性不为read_only,则返回
#注意生成器的用法,fields每循环一次,就再从fields里取一个
#事实上这个for循环就是为从序列化类中取出每个字段进行格式验证的
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)#这个就很熟悉了,应该想通过前面写方式2我们自定义的方式去验证,如果我们确实通过方式2定义了验证函数,就会获取验证函数的地址,方便下面代码进行调用。
primitive_value = field.get_value(data)#获取data这个key的value,这个函数考虑了getlist的情况,先不考虑这种情况
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
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#验证完数据没问题,返回
进入field.run_validation(primitive_value)函数看一下:
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
# inside the `to_internal_value()` method.
#如果data为空字符串类似'',或者‘ ’,则直接返回‘’,super().run_validation(data)
if data == '' or (self.trim_whitespace and str(data).strip() == ''):
if not self.allow_blank:
self.fail('blank')
return ''
return super().run_validation(data)
接下调用super().run_validation(data)函数,进去看一下:
def run_validation(self, data=empty):
#判断是不是readonly 是不是Empty,是不是None,很显然,一般来说都不是,
(is_empty_value, data) = self.validate_empty_values(data)#(False, data) or (True, data)
if is_empty_value:
return data
value = self.to_internal_value(data)#
self.run_validators(value)
return value
接下里进入self.to_internal_value(data)看一下:
def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into strings,
# but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
# and composites such as lists are likely user error.
#允许将数字转成字符串,但是如果data是bool类型,或者data不属于str,int,float类型,则抛出异常
if isinstance(data, bool) or not isinstance(data, (str, int, float,)):
self.fail('invalid')
value = str(data)#转成字符串
return value.strip() if self.trim_whitespace else value#再次进行空白空字符判断,无奈
然后程序执行self.run_validators(value)
然后程序再返回,接着执行self.run_validators(value)函数(注意,这里执行的不是我们自定义的那个validators()方法,别忘了之前的super(),按照c3算法,它应该是从父类进行寻找,何况我们也没通过第一种方式去定义),它主要是去验证长度符不符合要求,字段映射对不对,类型对不对,验证字符串是否包含空字符,看代码:
def run_validators(self, value):
"""
Test the given value against all the validators on the field,
and either raise a `ValidationError` or simply return.
"""
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:
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)
上述代码执行完,再次回到另一个to_internal_value()方法
def to_internal_value(self, data):
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:#通过类型去验证
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
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#验证完数据没问题,返回
代码应该是执行到了这一句,validated_value = field.run_validation(primitive_value),返回验证后的字段的值
接下来要执行validate_method(validated_value)函数,这个应该是我们自定义的,没定义的化,就跳过。
我没看源代码试过定义默认的验证方法,即使有的话,也只是走个过程,不处理,就直接把值返回了。
set_value(ret, field.source_attrs, validated_value)
大概就是把序列化属性和值放到字典里,事实上,它还考虑了其他情况,接受嵌套键的列表,这个我不是很懂,据猜测,应该是连接模型类进行验证,这个保留个人意见。
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}。
函数返回到最开始写的那个run_validation(self, data=empty)函数
那这个for循环第一圈就结束了,接下来验证其他字段和值,过程一样。直到所有字段验证完。
def run_validation(self, data=empty):
"""
We override the default `run_validation`, because the validation
performed by validators and the `.validate()` method should
be coerced into an error dictionary with a 'non_fields_error' key.
"""
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
value = self.to_internal_value(data)#field中也有to_internal_value()方法,注意区分
try:
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
接下来执行try里的代码self.run_validators(value)#这是调用第三种验证方式,我们没写,直接跳过。 接下来执行self.validate(value)函数,这是我们自定义的第三种方式,这个我们在序列化类中是定义的了,要执行。
def validate(self, attrs):
# 获取 username 和 password
username = attrs['username']
password = attrs['password']
# 进行用户名和密码校验
try:
# is_staff=True:表示要查找的是管理员用户
user = User.objects.get(username=username, is_staff=True)
except User.DoesNotExist:
raise serializers.ValidationError('用户名或密码错误')
else:
# 校验密码
if not user.check_password(password):
raise serializers.ValidationError('用户名或密码错误')
# 给 attrs 中添加 user 属性,保存登录用户
attrs['user'] = user
return attrs
再次回到is_valid()方法:
def is_valid(self, raise_exception=False):
assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)
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)
return not bool(self._errors)
self._validated_data = self.run_validation(self.initial_data)这句代码已经执行完了,并且获得了从我们自定义的第三种验证方式验证后的数据了,此时证明验证通过。所以,self._errors = {},然后return not bool(self._errors),这个返回的肯定是True了。
自此,isvalid()函数执行完,程序进入save()