drf 源码分析之【视图】
django restframework(简称drf)本质上其实就是一个别人编写好的app,里面集成了很多编写restful API的功能,而它的视图是基于django的CBV模式编写的,那么先回顾一下django的CBV
1. django的CBV视图
CBV,class base views,相对于FBV的用函数来处理业务,CBV其实就是编写类来处理业务请求。
from django.urls import path
from app01 import views
# 路由
urlpatterns = [
path('users/', views.UserView.as_view()),
]
from django.views import View
# 视图
class UserView(View):
def get(self, request, *args, **kwargs):
return JsonResponse({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return JsonResponse({"code": 1000, "data": "xxx"})
项目启动加载路由时就会先执行views.UserView.as_view(),as_view()方法在自定义的视图类的继承类View中,看源码(部分):
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def as_view(cls, **initkwargs): # cls是调用的视图类
def view(request, *args, **kwargs):
self = cls(**initkwargs) # 调用的视图类的实例对象
self.setup(request, *args, **kwargs)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
return view
# 视图类中没有dispatch的,还是会走到这里来
# 根据请求的方法,执行视图类中对应请求的业务逻辑
def dispatch(self, request, *args, **kwargs): # 注意self是视图类的实例对象
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
所以请求进来后会执行as_view()中的view函数,然后经过dispatch方法根据请求的方式不同,定位到视图类中不同的视图函数去执行。流程比较简单,需要注意的是,其中的self指向的是调用的视图类的实例对象。(本例中就是UserView的实例对象)
2. APIView视图
APIView是rest_framework提供的drf中所有视图的基类,继承自Django的View父类。
使用:
# urls.py
from django.urls import path
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view()),
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# 认证、权限、限流等
def get(self, request):
# 业务逻辑:查看列表
return Response({"code": 0, 'data': "..."})
def post(self, request):
# 业务逻辑:新建
return Response({'code': 0, 'data': "..."})
看起来基本与CBV没啥区别,只是视图类中的继承类改为了APIView,但其实在View的基础上扩展了很多功能,实现drf基础的组件的使用,例如:版本、认证、权限、限流等,这些组件后面再详细介绍。
来看下APIView中请求处理的流程:
class APIView(View):
@classmethod
def as_view(cls, **initkwargs): # cls为调用的视图类
view = super().as_view(**initkwargs) # 执行父类View中的as_view
view.cls = cls # view中的cls定义为调用的视图类
view.initkwargs = initkwargs
return csrf_exempt(view) # 免除csrftoken,返回的还是view
# 视图类的实例对象调用,会走这执行
def dispatch(self, request, *args, **kwargs):
pass
这一段会有点绕:
首先,路由加载时会执行APIView中的as_view方法,然后会跳到父类View中的as_view方法,返回一个父类View中的view函数,赋值给view(这绕口令呢。。。)
view = super().as_view(**initkwargs) # 执行父类View中的as_view
然后,关键的一步:
view.cls = cls # view中的cls定义为调用的视图类(本例中就是UserView)
最后,当请求来时,会执行view函数,注意是在父类View中的view函数,因为上面的view.cls = cls,将view中的cls定义为调用的视图类,那么self = cls(**initkwargs)就是调用的视图类的实例对象(本例中就是UserView的实例对象)
class View:
def as_view(cls, **initkwargs): # cls是View
# 执行的是这里的view函数
def view(request, *args, **kwargs):
self = cls(**initkwargs) # 调用的视图类的实例对象
self.setup(request, *args, **kwargs)
return self.dispatch(request, *args, **kwargs)
return view
所以,最后return的self.dispatch(request, *args, **kwargs),又会回到APIView中执行其中的dispatch方法(因为视图类继承的是APIView)
来看下APIView中的dispatch方法:
class APIView(View):
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# 重新封装了request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 版本/认证/权限/限流等其他功能
self.initial(request, *args, **kwargs)
if request.method.lower() in self.http_method_names:
# 找到对应的视图函数
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 执行视图函数得到response
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
# 重新封装了response后返回
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
APIView中的initialize_request,用于封装request,包含了除django原request对象以外,新增了一些后期会使用的其他对象。
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
所以在APIView中的dispatch中做的事情就是:
- 重新封装request对象
- 新增组件:版本/认证/权限/限流等其他功能(后面介绍)
- 匹配视图函数,执行得到reponse
- 重新封装reponse后返回
前面说过APIView是drf中所有视图的基类,所以说,一定还有其他的视图类,它们基于APIView又扩展了一些功能方便我们使用。下面分别简单介绍下:
3. GenericAPIView
GenericAPIView 继承APIView,主要增加了操作序列化器和数据库查询的方法。
实际在开发中一般不会直接继承它,更多的是担任 “中间类”的角色,为子类提供公共功能。
class GenericAPIView(views.APIView):
queryset = None # 数据库查询提取到类变量,方便自定义
serializer_class = None # 序列化类的定义提取到类变量
def get_queryset(self): # 提供一些钩子函数
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
def get_object(self):
pass
def get_serializer(self, *args, **kwargs):
pass
4. GenericViewSet
GenericViewSet类中没有定义任何代码,他就是继承 ViewSetMixin 和 GenericAPIView,也就说他的功能就是将继承的两个类的功能继承到一起。
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
pass
继承的两个类的功能:
GenericAPIView,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。ViewSetMixin,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。
ViewSetMixin源码解析:
class ViewSetMixin:
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.action_map = actions
# 把 methods 绑定 actions
for method, action in actions.items():
handler = getattr(self, action) # 如:handler = self.list
setattr(self, method, handler) # 如:self.get = self.list
self.request = request
self.args = args
self.kwargs = kwargs
# ViewSetMixin没有dispatch方法,又去到APIView中执行dispatch
return self.dispatch(request, *args, **kwargs)
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
使用案例:
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destory"})),
]
# views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
class UserView(GenericViewSet):
# 认证、权限、限流等
queryset = models.UserInfo.objects.filter(status=True)
serializer_class = 序列化类
def list(self, request):
# 业务逻辑:查看列表
queryset = self.get_queryset()
ser = self.get_serializer(intance=queryset,many=True)
print(ser.data)
return Response({"code": 0, 'data': "..."})
def create(self, request):
# 业务逻辑:新建
return Response({'code': 0, 'data': "..."})
def retrieve(self, request,pk):
# 业务逻辑:查看某个数据的详细
return Response({"code": 0, 'data': "..."})
def update(self, request,pk):
# 业务逻辑:全部修改
return Response({'code': 0, 'data': "..."})
def partial_update(self, request,pk):
# 业务逻辑:局部修改
return Response({'code': 0, 'data': "..."})
def destory(self, request,pk):
# 业务逻辑:删除
return Response({'code': 0, 'data': "..."})
NOTE:开发中一般也很少直接去继承他,因为他也属于是 “中间类”,在原来 GenericAPIView 基础上又增加了一个映射而已。
5. 五大视图扩展类
也叫混入类(Mixin),都定义在rest_framework.mixin.py中,分别是:ListModelMixin、CreateModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin。
作用:
提供了几种后端视图(对数据资源进行增删改查)处理流程的实现,如果需要编写的视图属于这五种(增、删、改(含局部修改)、查列表、查单个数据),则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。简言之,就是在这个5个类中已帮我们写好了 list、create、retrieve、update、partial_update、destory 方法,就不用自己苦哈哈的在视图类中写了。
NOTE:这五个扩展类需要搭配GenericViewSet使用。
(1) ListModelMixin
列表视图扩展类,提供list方法快速实现列表视图,返回200状态码。
另外,该Mixin的list方法会对数据进行过滤和分页。
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
(2) CreateModelMixin
创建视图扩展类,提供create方法,可以实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
(3) RetrieveModelMixin
详情视图扩展类,提供retrieve方法,返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
(4) UpdateModelMixin
更新视图扩展类,提供update方法,更新一个存在的数据对象。
同时也提供partial_update方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
(5) DestroyModelMixin
删除视图扩展类,提供destroy方法,删除一个存在的数据对象。
成功返回204,不存在返回404。
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
五大类总结
这5个视图扩展类(配合GenericViewSet),可以实现基本的5个API接口:
- ListModelMixin 提供了list方法,获取多条数据
- CreateModelMixin 提供了create方法,添加一条数据
- RetrieveModelMixin 提供了retrieve方法,获取一条(或局部)数据
- UpdateModelMixin 提供了update方法,更新一条数据
- DestroyModelMixin 提供了destroy方法,删除一条数据
ok,如果5个类全都要用,一定会有人想:每次都要写上6个类继承(5+GenericViewSet),好像也还是有点麻烦啊,搞一个类把他们全给继承了,我就只用写一个不是更好吗,那自然是有的:ModelViewSet
6. ModelViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
在开发过程中使用 五大类 或 ModelViewSet 是比较常见的,并且如果他们内部的某些功能不够用,还可以进行重写某些方法进行扩展。
最后的问题:drf中提供了这么多视图,怎么选?
-
接口与数据库操作无关,直接继承APIView
-
接口背后需要对数据库进行操作,一般:
ModelViewSet或五大类中某些类+GenericViewSet
浙公网安备 33010602011771号