• drf APIView源码浅析


    APIView

       在drf中,所有的视图都是以CBV的方式进行,这意味着我们必须使class继承于View类,但是原生DjangoView功能有限。所以drf中有一个APIView,它对View做了更加人性化的处理。

    执行流程

       APIView的使用方式和普通的View使用方式相同,但是源码上的执行流程却有一些差异。

       首先,视图中书写CBV

    from rest_framework.views import APIView
    from django.shortcuts import HttpResponse
    class Users(APIView):
        def get(self,request):
            return HttpResponse("ok")
    

       其次,在urls.py中进行路由配置:

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^user/', views.Users.as_view()),
    ]
    

       可以看到它的定义和使用都是完全一样的。

    as_view()

       在原生的as_view()中,会返回该函数中的闭包函数。

       那么在APIViewas_view()中是如何处理的呢?下面是源码部分:

       首先,APIView是继承于View的:

    class APIView(View)
    

       然后,再来看as_view()的方法:

      @classmethod
        def as_view(cls, **initkwargs):
            if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
                def force_evaluation():
                    raise RuntimeError(
                        'Do not evaluate the `.queryset` attribute directly, '
                        'as the result will be cached and reused between requests. '
                        'Use `.all()` or call `.get_queryset()` instead.'
                    )
                cls.queryset._fetch_all = force_evaluation
    
            view = super().as_view(**initkwargs)  # 这里会执行原生的as_view,返回的就是内层的闭包函数
            view.cls = cls # 进行赋值,cls即为当前的User视图类
            view.initkwargs = initkwargs  # 初始化参数
    
            return csrf_exempt(view)  # 取消csrf认证
    

       观察上面代码,发现两个有趣的地方。

       第一个就是会执行原生的view,另一个就是取消csrf跨域请求伪造的认证。

       看一下原生view吧。

        @classonlymethod
        def as_view(cls, **initkwargs):
            for key in initkwargs:
                if key in cls.http_method_names:
                    raise TypeError(
                        'The method name %s is not accepted as a keyword argument '
                        'to %s().' % (key, cls.__name__)
                    )
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r. as_view "
                                    "only accepts arguments that are already "
                                    "attributes of the class." % (cls.__name__, key))
    
            def view(request, *args, **kwargs):  # 会将这个函数进行返回
                self = cls(**initkwargs)
                self.setup(request, *args, **kwargs)
                if not hasattr(self, 'request'):
                    raise AttributeError(
                        "%s instance has no 'request' attribute. Did you override "
                        "setup() and forget to call super()?" % cls.__name__
                    )
                return self.dispatch(request, *args, **kwargs)
            view.view_class = cls
            view.view_initkwargs = initkwargs
            update_wrapper(view, cls.dispatch, assigned=())
            return view  # 返回了
    

    dispatch()

       接下来就是执行闭包函数view,可以看见在闭包函数view中会执行dispatch()方法并返回。

       那么根据查找顺序,User中没有,AIPView中才能找到该方法,来看一下它都做了什么事儿。

    def dispatch(self, request, *args, **kwargs):
    
            self.args = args
            self.kwargs = kwargs
            request = self.initialize_request(request, *args, **kwargs)  # 执行该方法
            self.request = request  
            self.headers = self.default_response_headers  
    
            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 = 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
    
    

      

    initialize_request()

       那么可以看见,在displatch()中又执行了initialize_request()方法,并将request对象传递了进去。

       我们来找一找该方法:

    def initialize_request(self, request, *args, **kwargs):
    
            parser_context = self.get_parser_context(request) 
            return Request(  # 最终返回一个Request的实例化对象
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    

       该方法会返回一个Request类的实例对象,也就是说,会对request请求做一个二次封装。

    self.request = request  # dispatch中的self.request实际上是二次封装后的对象
    

       紧接着会执行initial(),还是会将request对象传递进去,需要注意的是这里传递的是原生的request对象。

    def initial(self, request, *args, **kwargs):
    
        self.format_kwarg = self.get_format_suffix(**kwargs)
    
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
    
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme
    
     
        self.perform_authentication(request)  # 执行身份认证,是否已登录?
        self.check_permissions(request)  #  执行权限认证
        self.check_throttles(request)  # 执行频率认证
    

       那么源码看到这里就差不多了。

    流程小结

       执行APIView自己的as_view()

       执行APIView自己的dispatch()

       执行initialize_request()request对象进行二次封装。

       执行initial()进行权限、身份、频率等认证。

       所有继承于APIView的类,都没有CSRF认证。

    Request类

       上面看过了,在initialize_request()中,会返回一个Request实例化对象,该对象做了那些事儿?为什么要对request方法做封装呢?

    def initialize_request(self, request, *args, **kwargs):
    
            parser_context = self.get_parser_context(request) 
            return Request(  # 最终返回一个Request的实例化对象
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    

    request._request

       来看Request类的源码,在__init__()中,可以发现原生的request被隐藏了。

     def __init__(self, request, parsers=None, authenticators=None,
                     negotiator=None, parser_context=None):
            assert isinstance(request, HttpRequest), (
                'The `request` argument must be an instance of '
                '`django.http.HttpRequest`, not `{}.{}`.'
                .format(request.__class__.__module__, request.__class__.__name__)
            )
    
            self._request = request  # 这意味着通过 request._request可拿出原生的request
    

       这意味着通过 request._request可拿出原生的request,但是实际上我们一般都不这么做。

       至于为什么可以接着往下看。

    __getattr__

       可以在Request中找到__getattr__()方法,解释为什么不通过requst._request来取出原生对象。

        def __getattr__(self, attr):
            try:
                return getattr(self._request, attr)
            except AttributeError:
                return self.__getattribute__(attr)
    

       它源码写的非常清楚了,该方法的执行时机是通过.来调用实例化属性/方法时且该方法不存在时触发。

       当方法不存在时,它会调用self._request来获取方法并返回,所以我们可以直接对二次封装的request调用原生requst方法,如GET/POST等。

    request.data

       我们知道,如果前后端交互的数据格式采用json编码,Django不会对该数据做任何处理,而是存放至requst.body中,但是APIView会将这些数据存放至request.data中,方便进行存取。

       当你发送一个JSON格式的数据后,查看request.body,就能看到该数据:

    # json
    
    class Users(APIView):
        def get(self,request):
            print(request.data)
            return HttpResponse("get,ok")
        def post(self,request):
            print(request.data)  # {'k1': 'v1', 'k2': 'v2'}
            return HttpResponse("post,ok")
    

       如果不是JSON格式呢?会不会存入到request.body中?答案也是会的。

    # urlencoded
    
    from rest_framework.views import APIView
    from django.shortcuts import HttpResponse
    class Users(APIView):
        def get(self,request):
            print(request.data)
            return HttpResponse("get,ok")
        def post(self,request):
            print(request.data)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
            return HttpResponse("post,ok")
    

       仔细观察上面取出的数据,如果前端页面发送的是JSON数据,则会保存到dict中,如果不是JSON格式的数据,则会保存到QueryDict中。

    request.query_params

       可以看见,request.query_params是一个静态属性,它会返回原生的request.GET

        @property
        def query_params(self):
            """
            More semantically correct name for request.GET.
            """
            return self._request.GET
    

       我们看看它的数据格式组织是什么样的。

    from rest_framework.views import APIView
    from django.shortcuts import HttpResponse
    class Users(APIView):
        def get(self,request):
            print(request.query_params)  # <QueryDict: {'k1': ['v1']}>
            return HttpResponse("get,ok")
    

       它也会存入QueryDict中。

    Request小结

       在看过一圈Request类的源码后,要明白正确的被封装的request的使用方法。

       查询任何GET请求的数据,都从request.query_params取。

       查询任何非GET请求的数据,都从request.data中取。

       如果想使用原生的一些方法,比如FIELS/META等,都是可以直接使用的,这是由于__getattr__()的存在。

  • 相关阅读:
    游标和视图
    sql server 变量与集合运算
    sql server 流程控制
    sql server 基础查询和子查询
    数据库范式快速理解
    创建数据库与数据表
    SQL server 使用
    Android studio 下载依赖jar包慢
    java根据list数据通过接口姓名并填充
    【翻译】Promises/A+规范
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13866050.html
Copyright © 2020-2023  润新知