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类中没有定义任何代码,他就是继承 ViewSetMixinGenericAPIView,也就说他的功能就是将继承的两个类的功能继承到一起。

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中,分别是:ListModelMixinCreateModelMixinRetrieveModelMixinUpdateModelMixinDestroyModelMixin

作用:

提供了几种后端视图(对数据资源进行增删改查)处理流程的实现,如果需要编写的视图属于这五种(增、删、改(含局部修改)、查列表、查单个数据),则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。简言之,就是在这个5个类中已帮我们写好了 listcreateretrieveupdatepartial_updatedestory 方法,就不用自己苦哈哈的在视图类中写了。

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

posted on 2022-06-22 19:56  吃大飞  阅读(241)  评论(0)    收藏  举报