反序列化类校验部分源码解析
# 反序列化校验类,什么时候,开始执行校验呢?
-只要在BookView视图类中调用 ser.is_valid(),就会执行校验,检验通过就会返回True,不通过返回False,将错误提示返回到前端
# 入口:ser。is_valid()是序列化类的对象,假设序列化类是BookSerializer对象,所以我们要到BookSerializer去找is_valid,然后就会发现找不到,就到父类BaseSerializer中有;
【raise_exception=True 】
代码如下:
def is_valid(self, *, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
# self序列化类的对象,属性中一开始肯定没有_validated_data,一定会走下面这句,下次再来的话就不会走这句了【核心重点】
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的时候要知道这是自己写的序列化类的对象
——而且找run_validation不要直接点的方式去找,而在继承的类中去找,找不到就到它的父类中去找,不然就会找到filed的这个类中去
-- 它的执行顺序是 从下往上找,找不到就再往上
--最终从Serializer类中找到了run_validation 而不是filed中的run_validation
def run_validation(self, data=empty):
# 这里执行的是字段自己的,validates方法
(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)
# 全局钩子--》如果在BookSerializer中写了validate,优先就走它,他如果抛了异常,就会被捕获到,如果正常反回,value就拿到正常反回,所以反回的value就是校验过后的数据
--在源码中这个功能是没有写的,若果自己写了全局钩子,就就会执行自己写的,如果没有写就执行父类的全局钩子,但是父类中的全局够字没有写,所以也不会拦截校验的数据,而是直接返回。
value = self.validate(value)
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
# --局部钩子
value = self.to_internal_value(data)
--self是自己系诶的序列化类对象,然后从下往上找
def to_internal_value(self, data):
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
# 在这里for循环就是 fields写在序列化类中一个个字段类的对象(name=CharFiled)
for field in fields:
#通过反射找self self BookSerializer的对象,如果在类里面写了validate_字段名,就会将这个字段名取出来,就会执行下面的这个方法,( 反射validate_name)
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 在执行BookSerializer类中的validate_name方法,传入了要校验的数据,这里就是为什么需要传入name的原因
validated_value = validate_method(validated_value)
# 抛出异常
except ValidationError as exc:
errors[field.field_name] = exc.detail
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
如果有异常就将异常加到了ValidationError中,所以我们才能点的方式那到错误信息
小知识扩展:
【在视图类的方法中写raise_exception=True,就不要写判断,如果校验不同过就会直接抛异常,那么程序就会终止,之前学过的全局捕获异常,可以做统一异常处理,到后期这里用一句话就可以,就不需要加if判断了 】
总结:
- 反序列化类校验时通过is_valied开始执行的
- is_valid是Serializer的父类BaseSerializer中的
- 在Serializer类中找到了run_validation,执行里面的局部钩子/全局钩子
断言
# 断言,和try在源码中使用的较多
断言关键字: assert
【说明一下:就是我断定你是什么,如果是xxx,那就没什么事,如果不是的话就抛出异常】
name="jason"
# if name == 'jason':
# print('yes')
# else:
# print('no')
raise Exception ('断言的不对,不往下执行')
assert name ='jason' # 断定是,如果不是就抛出异常
DRF --请求
Request能够解析的前端传入的编码格式
能解析的编码格式三种
- json格式
- x-www-form- urlencoded
- form-data
案列实现:
需求:要求这个接口只能接收json格式,不能接收其他格式的数据
方式一:
通过继承APIView极其子类的视图类中配置(就是视图类自身)(局部配置)
parser_classes:解析类 能够解析前端传入的数据格式
有三个:from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
class BookView(APIView):
parser_classes = [JSONParser,]
对应关系:
json : JSONParser
x-www-form-urlencoded:FormParser
form-data:MultiPartParser
方式二:在配置文件中配置(影响真个项目,是全局配置)
--django中有套默认的配置,每个项目都有配置文件
--drf 也有套默认的配置,每个项目也有个配置,就在django的配置文件中
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
方式三: 全局配置了一个,某一个视图类中想要三个
- 在视图类中配置三个即可
- 因为:执行功能的方式是会先从自身开始找。找不到就会到项目的drf配置文件中找,再找不到就到drf默认的配置中去找
补充小知识:
jason 格式传入的数据是普通字典
FormParser,MultiPartParser,传入的数据是queryDict
django.http.request.QueryDict它是继承了Dict
getlist :传进来的数据是 K:Y的格式
总结:
- 解析前端传入的数据有三种格式
- 可以在自己写的视图类中通过
parser_classes =
来定制 - 还可以在项目的配置文件中配置
- 在配置文件配置的是全局生效的,想要单独给某一个视图类定制就需要在视图类中配置就行
- 如果在两个地方都配置了,执行顺序就是先执行自身的,没有再执行配置文件中的
DRF --响应
Response能够响应的编码格式
drf 是Django的一个App,我们在浏览器执行的时候就会报错,所以我们要在settings中去注册
INSTALLED_APPS = [
'app01.apps.App01Config',
'rest_framework'
]
#drf的响应:
使用浏览器和postman的访问同一个接口,返回的数据格式是不一样的
-drf的底层 做了一个判断,如果是浏览器就做的好看些,如果是postman就只要json数据
--renderer_classes: 渲染的类
默认渲染的class
DEFAULT_RENDERER_CLASSES: [
# json的render
'rest_framework.renderers.JSONRenderer',
# 浏览器的API的render
'rest_framework.renderers.TemplateHTMLRenderer',
],
控制响应的编码格式
通过视图类到drf的配置文件中找到两个类
方式一:在视图类中写(局部配置)
-from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes=[JSONRenderer,]
方式二:在配置文件中写(全局配置)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
],
}
方式三:执行的顺序(默认使用内置即可)
优先使用视图类中自身的配置,其次使用项目配置文件中配置,最后再使用内置的
Response的源码和属性
drf Response的源码分析
from rest_framework.response import Response
--通过看源码,了解到Response继承SimpleTemplateResponse ,而他继承了HttpResponse
--视图类返回方法的时候,return Response,走的是Response的__init__,这里需要关注的就是__init__中可以传什么参数
class Response(SimpleTemplateResponse):
def __init__(self,
data=None,
status=None,
template_name=None,
headers=None,
exception=False,
content_type=None):
--data :之前我们写的ser.data 可以是字典或列表。字符串,它会序列化后返回给前端,所以前端在响应体(body体中)中看到的就是这个。
--status :这是响应的状态码,默认是200,可以自己修改
drf 在status这个包下面,把所有的http响应状态码都写了一遍
from rest_framwork.status import HTTH_200_OK
return Response('jason',status=status.HTTP_200_OK)
--template_name: 修改相应模板的样子,BrowsableAPIRender 是固定的样子,后期可以根据公司自己定制(了解即可)
--headers: 响应头 http响应的响应头
小问题:原生django中怎么在响应投资中加数据?(这会涉及到跨域问题)
# 四件套 render redirect,HttpResponse,JasonResponse 返回的数据的时候都有一个接受的对象
obj=HttpResponse('aaa')
obj['xx']='yy'
return obj
content_type: 响应状态码的格式,一般默认即可
总结:
- 上面就讲了两种,响应状态码和状态码属性
- 其中重要的就是,data,headers,status
视图组件介绍和两个视图基类
- 讲的就是drf的视图,视图类,之前学过的APIView是drf的基类,是一个drf 提供的最顶层的类
APIView和之前的View的区别
1.传入到视图的方式是RESR framework的Response对象,而不是django的HttpResponse对象
2.视图方法可以返回REST framework的(drf的)Response对象
3.任何APIException异常都会被捕获到,并且处理成合适的响应信息
4.再进行dispatch()分发前,会对请求进行身份认证,权限检查,流量控制(频率)
两个视图基类
# APIView 视图类
会使用到的类属性:
renderer_classes :响应格式
parser_classes :能够解析的请求格式
authentication_classes :认证类
throttle_classes :评率类
permission_classes :权限类
基于APIView+ModelSerializer+Response 写五个接口
APIView+ModelSerializer+Response 写五个接口 代码
视图类
### 视图类基于APIView写五个接口 第一层 初级层
class BookView(APIView):
def get(self,request):
books=Book.objects.all()
ser=BookSerializer(instance=books,many=True)
return Response(ser.data)
def post(self,request):
ser=BookSerializer(data=request.data)
if ser.is_valid():
ser.save()
# 现在只有序列化的对象,但是我们想要新增的对象序列化成字典
# 前提是和序列化类中BookSerializer的create方法一定要返回新增的对象
return Response({'code':100,'msg':'新增成功','result':ser.data})
else:
return Response({'code':101,'msg':ser.errors})
class BookViews(APIView):
def get(self,request,pk):
books=Book.objects.filter(pk=pk).first()
ser=BookSerializer(instance=books)
return Response(ser.data)
def put(self,request,pk):
books=Book.objects.filter(pk=pk).first()
ser=BookSerializer(instance=books,data=request.data)
if ser.is_valid():
ser.save()
return Response({'code':100,'msg':'修改成功'})
else:
return Response({'code':101,'msg':ser.errors})
def delete(self,request,pk):
Book.objects.filter(pk=pk).delete()
return Response('删除成功')
序列化类
class BookSerializer(serializers.ModelSerializer):
# 和表建立关系
class Meta:
model = Book # 序列化类和表建立了关系
fields = ['name', 'price', 'publish', 'author_list', 'publish_detail', 'authors']
extra_kwargs = {'name': {'max_length': 8},
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True}
}
路由
urlpatterns = [
path('admin/', admin.site.urls),
path ('books',views.BookView.as_view()),
path('books/<int:pk>/',views.BookViews.as_view())
]
- 根据代码分析,我们可以得知,我们写的是一个book的接口,如果我们要写的是publish的接口,用的视图类代码就是上面的代码改改一部分就可以快速写出publish的五各接口,他们的区别就是在表模型和序列化类
- 考虑能不能想一种方式,通过继承的方式,少写代码
- 就是GenerricAPIView,他继承了APIView有一些新的属性和方法;以后可以基于这个类来写五个接口
基于GenerricAPIView 写五个接口
- 代码是差不多的,只不过基于APIView之上多写了几个属性和方法
五个接口的效果还是一样的,但是代码的可用性变高了
这里面有两个重要的属性和方法
属性:
1.queryset : 这是要序列化或反序列化的模型数据
serializer_class :使用的序列化类
lookup_field='pk' 查询单条数据的路由,分出来的字段名
filter_backends : 过滤类的配置(了解)
pagination_class: 分页类的配置(了解)
方法:
get_queryset 获取要序列化的对象
get_object 获取要序列的单个对象
get_serializer 获取序列化类 还有和它差不多的,一般不调用它,会重写它
filter_queryset 他和后续的过滤有关系(了解)
'''
通过看原代码可以了解到:
# queryset=Book.objects.all() # 这里的all已经在源码中return出来了,所以可以省略不写
queryset=Book.objects
serializer_class = BookSerializer
# obj=self.queryset # 这里提供了可以拿到的方法,但是不要这么使用,
objs=self.get_queryset()
'''
这么写有什么好处呢?
这是一个方法,我们可以在这个类中去重写这个方法,给它剔除点数据,或者假如点数据,返回点别的
那么他序列的就可以不一样了。它的可扩展性高
如果直接拿,拿到的数据就是写死的,
def get_queryset(self):的话就能重写着个方法
'''
ser=self.get_serializer()
看了原代码发现,serializer_class,又套了一层
serializer_class = self.get_serializer_class()
这越套它的扩展性就会越高,它这里做了什么事呢? 执行了上面的那句调用了get_serializer_class(),而get_serializer_class()就只是返回了要序列化的序列化类,然后拿到传进来的序列化类的东西,传进了serializer_class
同理,后期重写get_serializer_class,因为它返回那个序列化类,serializer_class就会拿哪个序列化类序列化
因为后期我们可能跟会使用到的序列化类不一样,序列化类和反序列化类,我们不能知己而在serializer_class = 写什么或许什么,所以我么就可以通过重写这个方法,来控制,我们使用那个序列化类做序列化
代码:
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = Book.objects
serializer_class = BookSerializer
def get(self, request):
objs = self.get_queryset()
# 使用 get_serializer的好处就是以后可以重写get_serializer_class指定哪个序列化类序列化
ser = self.get_serializer(instance=objs, many=True)
return Response(ser.data)
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
# 现在只有序列化的对象,但是我们想要新增的对象序列化成字典
# 前提是和序列化类中BookSerializer的create方法一定要返回新增的对象
return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
class BookViews(GenericAPIView):
queryset = Book.objects
serializer_class = BookSerializer
lookup_field = 'pk'
def get(self, request, pk):
obj = self.get_object() # 获取单条数据拿的是数据对象 这里不加pk的原因是get_object方法中制定了
ser = self.get_serializer(instance=obj)
return Response(ser.data)
def put(self, request, pk):
obj = self.get_object()
ser = BookSerializer(instance=obj, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功'})
else:
return Response({'code': 101, 'msg': ser.errors})
def delete(self, request, pk):
self.get_object().delete()
return Response('删除成功')
- 根据上述代码,能够知道,我们只需要修改那两个属性,其他的代码不用变就能够再写出其他模型表的类的五个接口
- 如果我们写publish类属性的五个接口,继承字GeneralAPIView,跟Book的区别只有那两个类属性 ,
- 通过这点我门得出一个结论,它还能封装。
- drf的思路是封装了五个视图扩展类--->get 所有,get一个,post 获取,put修改,delete 删除
通过思路想到,写五个类,每一个类利旧一个方法,想用那个方法就继承那个方法,为什么不将五个类写到一起呢,因为有的时候我们可能不会都会使用得到这五个接口。
drf中它的五各视图扩展类他不是视图类。没有继承APIView极其子类,所以它不能但单独使用,必须配合GeneralAPIView一起才能使用。
五个类名:
from rest_framework.mixins import CreateModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin,ListModelMixin
CreateModelMixin:新增
UpdateModelMixin:更新
DestroyModelMixin:删除
RetrieveModelMixin:查单条
ListModelMixin :查所有
是图类代码:
'''基于GeneralAPIView + 五个扩展类写接口'''
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookViews(GenericAPIView, DestroyModelMixin, UpdateModelMixin, RetrieveModelMixin):
queryset = Book.objects
serializer_class = BookSerializer
lookup_field = 'pk'
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
-
观察上述代码,通过继承GeneralAPIView + 五个扩展类以后写的代码,如果后面还要写其他的模型类,只需要将代码复制,最后改下面的两个属性就可以了
- queryset
serializer_class
- queryset
-
那么就想着,在这个的基础之上再进行封装,将所有的扩展类封装成一个父类,最后使用变成 9个类
-
from rest_framework.generics import ListAPIView,CreateAPIView,ListCreateAPIView from rest_framework.generics import DestroyAPIView,UpdateAPIView,RetrieveAPIView,\ RetrieveUpdateDestroyAPIView,RetrieveDestroyAPIView
class BookView(这里继承的是自己写的封装类):
queryset = Book.objects
serializer_class = BookSerializer
from rest_framework.viewsets import ModelViewSet
五个接接口使用同一个路由就会发现有点问题,因为有连个get,但是drf写了个魔法类,就是上面的这个
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
补充小知识:
ser.errors所有的错误都放到了这个序列化类的errors字典里面
标签:ser,05,self,get,序列化,data,class,DRF From: https://www.cnblogs.com/qiguanfusu/p/17092598.html