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__()的存在。

posted @ 2020-10-23 20:29  云崖先生  阅读(143)  评论(0编辑  收藏  举报