1、内置校验源码
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
if data is not empty:
self.initial_data = data
def is_valid(self, *, raise_exception=False):
assert hasattr(self, 'initial_data'),
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)
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
value = self.to_internal_value(data)
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
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
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
class Field:
def validate_empty_values(self, data):
if self.read_only:
return (True, self.get_default())
if data is empty:
if getattr(self.root, 'partial', False):
raise SkipField()
if self.required:
self.fail('required')
return (True, self.get_default())
if data is None:
if not self.allow_null:
self.fail('null')
# Nullable `source='*'` fields should not be skipped when its named
# field is given a null value. This is because `source='*'` means
# the field is passed the entire object, which is not null.
elif self.source == '*':
return (False, None)
return (True, None)
return (False, data)
1.1 校验的使用
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
class InfoSerializer(serializers.Serializer):
title = serializers.CharField(required=True, max_length=20, min_length=6)
order = serializers.IntegerField(required=False, max_value=100, min_value=10)
level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")])
class InfoView(APIView):
def post(self, request):
ser = InfoSerializer(data=request.data)
if ser.is_valid():
return Response(ser.validated_data)
else:
return Response(ser.errors)
2、正则校验
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
#
from django.core.validators import RegexValidator, EmailValidator
class InfoSerializer(serializers.Serializer):
# required 非空校验
# max_length 最大长度校验
# min_length 最小长度校验
title = serializers.CharField(required=True, max_length=20, min_length=6)
order = serializers.IntegerField(required=False, max_value=100, min_value=10)
# choices 1和2 可以写成字符串和数字,最后都会变为字符串
level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")])
# EmailField 也是校验形式一种方式,等同于CharField 中加上参数 EmailValidator
# email = serializers.EmailField()
email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])
# 内置正则校验,message代表校验异常时抛出的信息
# RegexValidator, EmailValidator都是从django中直接调用使用的
more = serializers.CharField(validators=[RegexValidator(r"\d+", message="格式错误")])
code = serializers.CharField()
class InfoView(APIView):
def post(self, request):
ser = InfoSerializer(data=request.data)
if ser.is_valid():
return Response(ser.validated_data)
else:
return Response(ser.errors)
3、钩子校验
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions
class InfoSerializer(serializers.Serializer):
title = serializers.CharField(required=True, max_length=20, min_length=6)
order = serializers.IntegerField(required=False, max_value=100, min_value=10)
code = serializers.CharField()
def validate_code(self, value):
print(value)
if len(value) > 6:
raise exceptions.ValidationError("字段钩子校验失败")
return value
# 额外校验,有些场景需要对字段组合校验,比如多个字段需要满足什么规则,所以用
# 这个方法
def validate(self, attrs):
print("validate=", attrs)
# api_settings.NON_FIELD_ERRORS_KEY,额外校验时,NON_FIELD_ERRORS_KEY
# 这个值为报错的键值,我们可以根据这个值NON_FIELD_ERRORS_KEY,去寻找对应的报 错信息
# raise exceptions.ValidationError("全局钩子校验失败")
return attrs
class InfoView(APIView):
def post(self, request):
ser = InfoSerializer(data=request.data)
if ser.is_valid():
return Response(ser.validated_data)
else:
return Response(ser.errors)
4、Model校验
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions
from api import models
from django.core.validators import RegexValidator
class RoleSerializer(serializers.ModelSerializer):
more = serializers.CharField(required=True)
class Meta:
model = models.Role
fields = ["title", "order", "more"]
# 用ModelSerializer时,我们需要内置校验和正则校验时,可以在Meta元信息里
# 定义参数
extra_kwargs = {
# 字段名 : 校验规则
# 其他的 required=True, max_length=20, min_length=6 等这些校验
# 规则都可以通过这种字典的形式,输入校验规则
"title": {"validators": [RegexValidator(r"\d+", message="格式错误")]},
"order": {"min_value": 5},
}
def validate_more(self, value):
return value
def validate(self, attrs):
return attrs
class InfoView(APIView):
def post(self, request):
ser = RoleSerializer(data=request.data)
if ser.is_valid():
return Response(ser.validated_data)
else:
return Response(ser.errors)
5、校验+保存
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions
from api import models
from django.core.validators import RegexValidator
class RoleSerializer(serializers.ModelSerializer):
more = serializers.CharField(required=True)
class Meta:
model = models.Role
fields = ["title", "order", "more"]
extra_kwargs = {
"title": {"validators": [RegexValidator(r"\d+", message="格式错误")]},
"order": {"min_value": 5},
}
def validate_more(self, value):
return value
def validate(self, attrs):
return attrs
class InfoView(APIView):
def post(self, request):
ser = RoleSerializer(data=request.data)
if ser.is_valid():
ser.validated_data.pop("more")
# 通过save,序列化类可以直接將validated_data保存到数据库
# 对于保存时,少字段的情况,比如有些字段不需要客户提交的,所以
# validated_data 中会少这个字段的值,这个时候我们需要在
# save中赋值 ser.save(字段名=xxxx)来保存,要不然后会报错
# 自增id客户没有提交时,我们应该也不需要赋值(后面需要确认)
# 对于保存多字段的情况,有时候我们对客户展示的字段是数据库没有的
# 那么获得的数据就会多字段,这种情况跟django不一样,不会剔除
# 要自己手动剔除 比如 ser.validated_data.pop("more")
instance = ser.save() # ser.save(v1=123,v2=234)
print(instance)
# 校验后的字段值 都在validated_data 字典中 跟django中的
# cleaned_data 是一样的
return Response(ser.validated_data)
else:
return Response(ser.errors)
6、 校验+保存+FK+M2M
FK
# 1、当我们提交数据时,涉及到外键字段时,该字段会自动校验,看该数据是否在主表中内的数据,校验通过后会保存到数据库
# 2、如果想要更加复杂的校验,可以自定义钩子方法validatae_外键字段,此时里面传入的value
# 值是一个 主表对象,可以通过 对象.xxx来进行更复杂的校验
# 3、如果我们在Fields中直接写入 外键_id 字段时,传过来数据
# 此时modelSerializer 不会直接生成对应的关系,不能成功保存到
# 数据库
# 4、如果保存 外键_id 可以通过 外键_id = Serializer.字段类型() 然后再加入Fields中,这个时候提交depart_id时可以成功
# 5、post请求的字段是外键的 字段名 不是外键_id
ManytoMany校验
# 1、当我们提交数据时,涉及到关联字段时,该字段会自动校验,看该数据是否在关联表中内的数据,校验通过后会保存到数据库
# 2、如果想要更加复杂的校验,可以自定义钩子方法validatae_关联字段,此时里面传入的value
# 值是一个列表,列表中的元素是关联表的对象,可以循环来校验
# post提交请求的字段是 多对多的字段名
# 3、manytomany 字段自定制校验,可以 通过 Serializer.ListField()或者 Serializer.DictField() [manytomany 自定义字段时只能通过这两种方式定义,更多是ListField] 自定义字段xxx,然后把xxx加入到Fields中,通过validatae_xxx可以自定义校验方法,但是xxx是无法正常保存到数据库,保存到数据库的时候需要删除,如果要想要自定义校验规则并且保存到数据库,需要自定义字段名跟多对多字段名一致,然后返回的是对应的
# 条件的queryset对象 可以正常保存到数据库
from django.db import models
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
order = models.IntegerField(verbose_name="顺序")
class Tag(models.Model):
caption = models.CharField(verbose_name="名称", max_length=32)
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
gender = models.SmallIntegerField(verbose_name="性别", choices=((1, "男"), (2, "女")))
role = models.ForeignKey(verbose_name="角色", to="Role", on_delete=models.CASCADE)
ctime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
tags = models.ManyToManyField(verbose_name="标签", to="Tag")
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions
from api import models
from django.core.validators import RegexValidator
import datetime
class UserInfoSerializer(serializers.ModelSerializer):
more = serializers.CharField(required=True)
class Meta:
model = models.UserInfo
fields = ["name", "gender", "role", "tags", "more"]
extra_kwargs = {
"name": {"validators": [RegexValidator(r"n-\d+", message="格式错误")]},
}
def validate_more(self, value):
return value
def validate(self, attrs):
return attrs
class InfoView(APIView):
def post(self, request):
ser = UserInfoSerializer(data=request.data)
if ser.is_valid():
ser.validated_data.pop("more")
instance = ser.save(ctime=datetime.datetime.now())
print(instance)
# return Response(ser.validated_data)
return Response("成功")
else:
return Response(ser.errors)
7、钩子 Create
自定义钩子方法
#如果我们有需求,用户提交的是gender:1 返回的是 gender:男
# 如果用SerializerMethodField方法去自定义钩子get_xxx,在实例化该类时,会初始化
read_only参数为True,也就是SerializerMethodField这个字不需要接收值,然后这个时候把
gender放在Fields这个字段,这个时候gender会跟数据库原有字段冲突会报错,所以用自定义get_xx 不符合要求,如果跟数据库字段同名,不需要提交数据,只做序列化是不会报错
# 用Charfield重新gender 字段也不可以,还是只是会显示1
# 可以自定义钩子方法类,然后被继承
# 下面代码就解决问题了,自定义 nb_xxxx 钩子方法
class NbHookSerializer(object):
def to_representation(self, instance):
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
# 此处为拓展的钩子方法类,instacne代表obj对象
# 妙哉
if hasattr(self, 'nb_%s' % field.field_name):
value = getattr(self, 'nb_%s' % field.field_name)(instance)
ret[field.field_name] = value
else:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
class SbModelSerializer(NbHookSerializer, serializers.ModelSerializer):
class Meta:
model = models.NbUserInfo
fields = ["id", "name", "age", "gender"]
extra_kwargs = {
"id": {"read_only": True}
}
def nb_gender(self, obj):
return obj.get_gender_display()
def nb_name(self, obj):
return obj.get_gender_display()
8、校验+序列化
1、如果对新增数据 校验过后,成功保存到数据库后,想要序列化返回,此时可以校验定义一个
序列化类 序列化 定义一个序列化类
2、校验可以跟序列化同时用一个序列化类,但是两者的字段是一致的
3、如果想校验和序列化 字段不一致,可以通过read_only 和 write_only控制
read_only表示序列化字段,在提交时,如果有字段设置为True,那么这个字段可以不
用提交,不用校验,知识序列化后显示,比如自增id
write_only为True时,这个字段提交时会校验,但是不会序列化返回,比如密码这种字段
不对外界展示
class UsView(APIView):
def post(self, request, *args, **kwargs):
# 1.获取原始数据
# print(request.data)
# 2.校验
ser = UsModelSerializer(data=request.data)
if ser.is_valid():
print("视图", ser.validated_data)
# ser.validated_data.pop("tags")
# instance返回的是当前序列化类中对应表中对应的数据对象
instance = ser.save()
instance.id
instance.name
instance.age
else:
print("视图", ser.errors)
return Response("成功")
校验和序列化 用同一个序列化类 方式1
class DpView(APIView):
def post(self, request, *args, **kwargs):
ser = DpModelSerializer(data=request.data)
if ser.is_valid():
instance = ser.save()
print(instance)
xx = DpModelSerializer(instance=instance)
return Response(xx.data)
else:
return Response(ser.errors)
校验和序列化 用同一个序列化类 方式2的写法更简单,因为当调用ser.save()时,
会自动把instance传入到DpModelSerializer中,然后调用ser.data再序列化
class DpView(APIView):
def post(self, request, *args, **kwargs):
ser = DpModelSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.errors)
如果一个请求,即需要做 请求校验 又需要做 序列化 ,怎么搞呢?例如:新增数据。
- 字段,可以通过read_only 、write_only、required 来设定
- is_valid校验
- data调用序列化