drf 02 APIView的请求生命周期及各个模块源码分析和配置

django 视图类和drf视图类区别

模块 请求类型 继承关系 响应数据格式 需注销csrf
django get View JsonResponse(字典),没有Response
django post View JsonResponse(字典),没有Response
drf get APIView JsonResponse(字典),Response(python基本数据类型皆可)
drf post APIView JsonResponse(字典),Response(python基本数据类型皆可)
# d_proj>urls.py
from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/',include('api.urls')),
]


# api>urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^v1/books/$',views.BookViews.as_view()),
    url(r'^v1/books/(?P<pk>\d+)/$',views.BookViews.as_view()),

    url(r'^v2/books/$',views.BookAPIViews.as_view()),
    url(r'^v2/books/(?P<pk>\d+)/$',views.BookAPIViews.as_view()),
]


# views.py
from rest_framework.views import APIView
from django.views import View
from django.http import JsonResponse
class BookViews(View):

    def get(self,*args,**kwargs):
        return JsonResponse({
            'msg':'get BookViews'
        })

    def post(self,*args,**kwargs):
        return JsonResponse({
            'msg': 'post BookViews'
        })


class BookAPIViews(APIView):
    def get(self,*args,**kwargs):
        return JsonResponse({
            'msg': 'get BookAPIViews'
        })

    def post(self,*args,**kwargs):
        return JsonResponse({
            'msg': 'post BookAPIViews'

APIView的请求生命周期

APIView的请求生命周期
 1)APIView类继承View类,重写了as_view和dispatch方法
 2)重写的as_view方法,主体还是django中View的as_view,只是在返回视图view函数地址时,局部禁用csrf认证,所以以后只要是视图类继承了APIView,就不需要注销settings中的csrf中间件了,其实这是就相当于注销掉了settings的中间件csrf。然后调用djangoas_view内部的闭包函数view,在函数view内部返回cls(我们定义的视图类)实例化的对象self.dispatch(request,*args,**kwargs),按照属性的查找顺序(自己类》继承的父类》父类的父类),在APIView中定义的有dispatch方法,所以不会走django》View类中的dispatch方法了。
 3)重写的dispatch方法,
       在执行请求逻辑前:请求模块(二次封装request)、解析模块(三种数据包格式的数据解析)
       在执行请求逻辑中:异常模块(执行出现任何异常交给异常模块处理)
       在执行请求逻辑后:响应模块(二次封装response)、渲染模块(响应的数据可以是两种渲染方式:可以是JSON数据格式,也可以是页面),在浏览器输入接口是页面渲染,postman是JSON格式
# urls.py

urlpatterns = [
    url(r'^books/$', views.BookAPIViews.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIViews.as_view()),
]


# 调用中APIView中的as_view()方法:
# rest_framework>views.py>APIView(View)类>as_view方法
   @classmethod
    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)  # 调用django中的as_view()
        view.cls = cls
        view.initkwargs = initkwargs
        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.

        # 局部禁用csrf
        return csrf_exempt(view)

# 调用父类View中的as_view方法
class View(object):
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in six.iteritems(kwargs):
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (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):  # 接收路由的有名和无名分组的参数
            # print(request)
            # print(request.method)  # <WSGIRequest: DELETE '/api/books/1/'>
            # print(args,kwargs)    # () {'pk': '1'}
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)  # APIView类中有dispatch方法,就不会走APIView类继承的View中(django中的View)的dispatch
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

# APIView类中的dispatch方法
    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对象进行二次封装,包含
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
            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)
        # 二次封装response,处理了结果 渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    
    
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response

class BookAPIViews(APIView):
    def get(self,*args,**kwargs):
        return Response({
            'msg': 'get BookAPIViews'
        })

    def post(self,*args,**kwargs):
        return Response({
            'msg': 'post BookAPIViews'
        })

请求模块

# 走drf的dispatch方法,
 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对象进行二次封装,包含
        request = self.initialize_request(request, *args, **kwargs)  # *********************
        self.request = request
        # print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
            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)
        # 二次封装response,处理了结果 渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    
    
def initialize_request(self, request, *args, **kwargs):
    """
        Returns the initial request object.
        """
    parser_context = self.get_parser_context(request)

    return Request(  # 初始化
        request,  # 将wsgi的request也出入Request的__init__内部进行初始化
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

# rest_framework>request.py>Request类
class 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  # 我们自定义的类内部只有通过self._request访问原始的request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)
            
    @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
class BookAPIViews(APIView):
    def get(self,request,*args,**kwargs):
        return Response({
            'msg': 'get BookAPIViews'
        })

    def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
        
        #在内部将wsgi的request赋值给request._request
        print(type(request))  # <class 'rest_framework.request.Request'>  
      
    	# 就是通过__getattr__走的是request._request.method
        print(request.method)  # POST  这里request是Request的对象,对象.属性 触发类内部的__getattr__方法  Request类见最下面在最下方
        print(request._request.method)  # POST 等价于上面
        
        # 走的是方法属性,就是给request._request.GET重新命名
        print(request.query_params)  # <QueryDict: {'a': ['10']}>
        
        # 走的是方法属性,值依赖于request._full_data
        print(request.data)     # <QueryDict: {'{\n\t"name":"zhang"\n}': ['']}>
        
        return Response({
            'msg': 'post BookAPIViews'
        })
    
# ==========================================================================================
 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)

    @property
    def DATA(self):
        raise NotImplementedError(
            '`request.DATA` has been deprecated in favor of `request.data` '
            'since version 3.0, and has been fully removed as of version 3.2.'
        )

    @property
    def POST(self):
        # Ensure that request.POST uses our request parsing.
        if not _hasattr(self, '_data'):
            self._load_data_and_files()
        if is_form_media_type(self.content_type):
            return self._data
        return QueryDict('', encoding=self._request._encoding)

    @property
    def FILES(self):
        # Leave this one alone for backwards compat with Django's request.FILES
        # Different from the other two cases, which are not valid property
        # names on the WSGIRequest class.
        if not _hasattr(self, '_files'):
            self._load_data_and_files()
        return self._files

    @property
    def QUERY_PARAMS(self):
        raise NotImplementedError(
            '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
            'since version 3.0, and has been fully removed as of version 3.2.'
        )
"""
request.DATA
request.FILES
request.QUERY_PARAMS
"""
# ===========================================================================================

解析模块

解析模块:只处理数据包参数 - form-data,urlencoded,json
1)全局配置所有视图类的解析方式,解析配置可以配置三种
2)局部配置当前视图类的解析方式,解析配置可以配置三种
3)配置的查找顺序:局部(视图类的类属性) => 全局(settings文件的drf配置) => 默认(drf的默认配置)
# 三种解析类解析数据所对应的数据编码格式:
    JSONParser---->media_type = 'application/json'
    FormParser---->'application/x-www-form-urlencoded'
    MultiPartParser---->'multipart/form-data'
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
from rest_framework.views import APIView
from rest_framework.response import Response
class BookAPIViews(APIView):
	# 视图类内部自己配置解析类属性(局部配置)************
    parser_classes = [JSONParser,FormParser,MultiPartParser]

    # def get(self,request,*args,**kwargs):
    #     return Response({
    #         'msg': 'get BookAPIViews'
    #     })

    def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
        print(request.query_params)  #  接口拼接的参数
        print(request.data)  # 前端三种编码格式数据解析后都在data内 ,form-data  form-urlencoded  raw(application/json)
        return Response({
            'msg': 'post BookAPIViews'
        })

解析模块配置

1) 局部配置:
 parser_classes = [JSONParser,FormParser,MultiPartParser]
    
2) 全局配置(在api的settings文件内):# 全局配置解析类:适用于所有视图类
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',  # json
        'rest_framework.parsers.FormParser',  # form 表单提交form-urlencoded编码格式的数据
       # 'rest_framework.parsers.MultiPartParser'  # form 表单提交form-data编码格式的数据
        ],
}

3) 系统配置
rest-framework>settings>__init__()方法内加载DEFAULT

解析类源码分析

入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
获取解析类:parsers=self.get_parsers(),
进行局部全局默认配置查找顺序进行查找:return [parser() for parser in self.parser_classes]

响应模块

data:响应数据
status:响应的网络状态码

template_name:drf完成前后台不分离返回页面,但是就不可以返回data(不许用了解)
headers:响应头,一般不规定,走默认
exception:一般异常响应,会将其设置成True,默认False(不设置也没事)
content_type:默认就是 application/json,不需要处理
from rest_framework.response import Response
from rest_framework import status  # 网络状态码

 def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
        print(request.query_params)  #  接口拼接的参数
        print(request.data)  # 前端三种编码格式数据解析后都在data内 ,form-data  form-urlencoded  raw(application/json)

        response = Response(
            data = {
            # 就是让Response类中的__init__的关键字形参data接受
            'msg': 'post BookAPIViews'
        },
        status= status.HTTP_400_BAD_REQUEST
        )
        print(response.data)  # {'msg': 'post BookAPIViews'}
        return response

Response类对应内部源码

class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.

        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)
# ===========================================================================================
    # 把属性全部加载到Response类的对象的名称空间 
    	self.data = data  # self 是Response类的对象
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

渲染模块

全局配置渲染类

REST_FRAMEWORK = {
    # 全局配置渲染类:适用于所有视图类
    'DEFAULT_RENDERER_CLASSES': [
        # 控制JSON数据在postman的显示
        'rest_framework.renderers.JSONRenderer',
        # 控制drf页面在浏览器页面渲染
        # 'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器端能访问到drf页面,上线后尽量关闭
    ],
}

局部配置

from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
 renderer_classes = [JSONRenderer,BrowsableAPIRenderer]

系统配置

# rest_framework>views.py>APIView类
 renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

异常模块

源码分析

	# rest_framework>views.py>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对象进行二次封装,包含
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        # print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
            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)
        # 二次封装response,处理了结果 渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    
    # rest_framework>views.py>APIView(View)类内部的方法
    def handle_exception(self, exc):
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        # 出现三大认证相关异常,额外处理一下响应头
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
        # 获取异常处理函数exception_handler地址,就独立存在该文件内
        exception_handler = self.get_exception_handler()
        # 给异常处理提供额外的参数
        context = self.get_exception_handler_context()  # exc异常对象,context中有视图对象和请求对象
        response = exception_handler(exc, context)
        # 默认的exception_handler函数只处理客户端异常形成response对象,服务器异常不做处理,返回None
        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response



# 在rest_framework>views.py独立存在的函数
def exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.

    By default we handle the REST framework `APIException`, and also
    Django's built-in `Http404` and `PermissionDenied` exceptions.

    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    return None

自定义异常处理函数

# settings.py配置
REST_FRAMEWORK = {
    # 异常模块:
    # 系统异常处理函数:
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    # 自定义异常处理函数:指向自己定义的exception.py 文件中的exception_handler函数
    'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}

# api>exception.py
# 在settings文件中将异常模块配置自己的异常处理函数
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
# 先交给drf处理客户端异常,如果结果response为None代表服务器异常,自己处理
def exception_handler(exc,context):
    response = drf_exception_handler(exc,context)
    detail = '%s-%s-%s' % (context.get('view'), context.get('request').method, exc)
    if not response: # 服务器端错误

       response =  Response({'detail':detail})
    else:
       response.data = {'detail':detail}

    # 要将response.data.get('detail)信息记录下来
    # print(response.data.get('detail'))
   # < api.views.BookAPIViewsobjectat0x000000000B9FA3C8 > -PUT - 方法 “PUT” 不被允许。
    return response
posted on 2019-12-29 20:21  jueyuanfengsheng  阅读(290)  评论(0编辑  收藏  举报