DRF 源码解析 requeset&视图(一)

143 阅读5分钟

DRF视图源码入口

基于Django的CBV

urls.py

from django.urls import path  
from app01 import views  
  
urlpatterns = [  
path('user/', views.UserView.as_view()), # CBV  
]

app01.views.py

from django.views import View  
from rest_framework.response import Response  
  
  
class UserView(View):  
  
    def get(self, request):  
        return Response({'status': True, 'message': 'GET'})  

    def post(self, request):  
        return Response({'status': True, 'message': 'POST'})  

    def put(self, request):  
        return Response({'status': True, 'message': 'PUT'})  

    def delete(self, request):  
        return Response({'status': True, 'message': 'DELETE'})

UserView类没有as_view()方法,去其父类django.views.View找。

class View:
    ...
    @classonlymethod  
    def as_view(cls, **initkwargs):  
        # 封装进来的cls是UserView类
        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):...
            
        view.view_class = cls  
        view.view_initkwargs = initkwargs  

        # __name__ and __qualname__ are intentionally left unchanged as  
        # view_class should be used to robustly determine the name of the view  
        # instead.  
        view.__doc__ = cls.__doc__  
        view.__module__ = cls.__module__  
        view.__annotations__ = cls.dispatch.__annotations__  
        # Copy possible attributes set by decorators, e.g. @csrf_exempt, from  
        # the dispatch method.  
        view.__dict__.update(cls.dispatch.__dict__)  

        # Mark the callback if the view class is async.  
        if cls.view_is_async:  
            markcoroutinefunction(view)  

        return view   
    ...

as_view()方法实际返回的是其内部定义的一个view()方法。

class View:
    ...
    @classonlymethod
    def as_view(cls, **initkwargs):
        ...
        def view(request, *args, **kwargs): 
            # cls是通过闭包传递过来的,指的就是UserView类
            self = cls(**initkwargs)  
            # 实例化了一个试图类(UserView)的对象
            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__  
                )  
            """执行了视图类实例的dispatch()方法,并返回其结果"""
            return self.dispatch(request, *args, **kwargs)

UserView类没有dispatch()方法,去其父类View找。

class View:

    http_method_names = [  
        "get",  
        "post",  
        "put",  
        "patch",  
        "delete",  
        "head",  
        "options",  
        "trace",  
    ]
    ...
    def dispatch(self, request, *args, **kwargs):  
        # Try to dispatch to the right method; if a method doesn't exist,  
        # defer to the error handler. Also defer to the error handler if the  
        # request method isn't on the approved list.  
        if request.method.lower() in self.http_method_names:  
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            # getattr 反射
            # 如果在HTTP方法内,就得到视图类执行其对应的HTTP方法的函数(get/post/put/deldete...)
            # 如果视图类不存在和HTTP方法对应的函数,得到http_method_not_allow方法
        else:  
            handler = self.http_method_not_allowed  
        return handler(request, *args, **kwargs) 
        # 执行对应的视图类方法并返回
    ...

基于REST framwwork的CBV

urls.py

urlpatterns = [  
    ...
    path('info/', views.InfoView.as_view()), # CBV  
]

app01.views.py

from rest_framework.response import APIView

class InfoView(APIView):
    def get(self, request):
        return Response({"status": True})
        

去父类APIView找as_view()方法。 rest_framework/views.py

from django.views.generic import View

class APIView(View):
    def as_view(cls, **initkwargs):
        """  
        Store the original class on the view function.  

        This allows us to discover information about the view when we do URL  
        reverse lookups. Used for breadcrumb generation.  
        """  
        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()方法
        # 其父类是Django的View
        # 
        view.cls = cls  
        view.initkwargs = initkwargs  

        # Note: session based authentication is explicitly CSRF validated,  
        # all other authentication is CSRF exempt.  
        return csrf_exempt(view)
        # 免除csrf校验
    ...

DRF的APIView比Django的View提供了更多的功能。

APIView有dispatch()方法,所以父类View的as_view()方法内部定义的view()在执行return self.dispatch(request, *args, **kwargs)时,这里的dispatch()方法是APIView中的。

class APIView(View):
    ...
    def dispatch(self, request, *args, **kwargs):  
        """  
        `.dispatch()` is pretty much the same as Django's regular dispatch,  
        but with extra hooks for startup, finalize, and exception handling.  
        """  
        self.args = args  
        self.kwargs = kwargs  
        request = self.initialize_request(request, *args, **kwargs)  
        self.request = request  
        self.headers = self.default_response_headers # deprecate?  

        try:  
            self.initial(request, *args, **kwargs)  

            # Get the appropriate handler method  
            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)  

        self.response = self.finalize_response(request, response, *args, **kwargs)  
        return self.response

APIView类的dispatch()方法在分发执行视图类的方法之前和之后,有执行其他的方法(initialize_request, initial, finalize_response)。

总结

APIView的as_view()方法可以免除csrf认证,dispatch()方法在视图类的方法执行前后的方法,就是DRF的核心组件。

DRF URL参数和request对象分析

URL参数

URL参数会通过as_view(cls, **initkwargs)传递到view(request, *args, **kwargs),再传递到dispatch(request, *args, **kwargs),最终传递到get/post/delete/put(self, request, *args, **kwargs)

request

请求传入后, 在Django中,request就是请求相关的所有的数据。

在DRF中,当request传入到APIView类的dispatch方法后,通过initial_request()方法创建了新的request。

class APIView(View):
    ...
    def dispatch(self, request, *args, **kwargs):  

        self.args = args  
        self.kwargs = kwargs 
        # 把参数传递给了self.args和self.kwargs, 可以在视图类的方法中
        # 通过self.args和self.kwargs直接访问参数
        request = self.initialize_request(request, *args, **kwargs)  
        # 传入的request作为参数,通过initial_request()方法返回了处理后的新request
        self.request = request  
        self.headers = self.default_response_headers 
        
        try:  
            self.initial(request, *args, **kwargs)  

            # Get the appropriate handler method  
            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)  
            # 新的request作为参数传入了视图类的对应方法中。
        except Exception as exc:  
            response = self.handle_exception(exc)  

        self.response = self.finalize_response(request, response, *args, **kwargs)  
        return self.response
    ...

initial_request()方法在APIView类中定义。

from rest_framework.request import Request

class APIView(View):
    ...
    def initialize_request(self, request, *args, **kwargs):  
        # Returns the initial request object. 
        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  
        )
    ...

initial_request()方法返回一个Request类实例。

class Request:
    def __init__(self, request, parsers=None, authenticators=None,  
                 negotiator=None, parser_context=None):
        ...
        self._request = request 
        # request._requset就是传入的request
        ...
    ...

drf中的request参数,有一层封装。

request&面向对象(oop)知识

__getattr__()

class Foo(object):
    # 初始化
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def show(self):
        return 123
        
    def __getattr__(self, item):
        return f"Foo has not attr {item}"
        
        
obj = Foo("Ada", 23)  # 创建了一个Foo对象的实例
obj.name    # "Ada"
obj.age     # 23
obj.show    # show()方法
obj.show()  # 执行了show()方法,返回123

# 反射
getattr(obj, "name")    # "Ada"
getattr(obj, "age")     # 23
getattr(obj, "show")    # show()方法
getattr(obj, "show")()  # 执行show()方法,返回123

obj.gender              # Foo类没有gender属性,所以会执行__getattr__()方法,而不会报错
getattr(obj, "height")  # 因为Foo类没有geight属性,同样会执行__getattr__()方法

__getattribute__()

class Foo(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def show(self):
        return 123
        
    def __getattribute__(self, item):
        return 999
        
        
obj = Foo("Ada", 23)  # 创建了一个Foo对象的实例
obj.name    # 999
obj.age     # 999
obj.gender  # 999
# 无论属性是否存在,访问它得到的结果都是执行__getattribute__()方法

如果__getattribute__()未设置返回值,则返回的是None

在DRF的Reuest类中,

class Request:
    def __init__(self, request, ...):
        ...
        self._request = request
        ...
    ...
    def __getattr__(self, attr):
        # If an attribute does not exist on this instance, then we also attempt
        # to proxy it to the underlying HttpRequest object.
        try:
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)
    ...

经过initial_request()创建的新request,是可以当做Django中的HttpRequest类来访问参数的,例如request.method实际上是request._request.method