DRF installation configuration - source code analysis

DRF installation configuration

What is the DRF

Abbreviated Django REST framework, mainly for the front and rear ends of the separate service, used to write api, provides a data interface to the front end.

Why should DRF

Even though we do not apply DRF, as can write RESTful interface to meet specifications, but may choose to use the DRF as a tool to improve the efficiency of development, not only because he was able to quickly help us design a compliant interface, but also provides the authority, certification and other powerful function.

DFF installation command

cnpm install djangoframework

DRF use

Import module, so that class inheritance APIView

class BookAPIView(APIView):
    # 渲染模块的局部配置
    # 局部禁用就是配置空list:[]
    renderer_classes = [JSONRenderer, BrowsableAPIRenderer]

    # 解析模块的局部配置
    # parser_classes = [JSONParser, MultiPartParser, FormParser]

    def get(self, request, *args, **kwargs):
        print(request._request.GET)
        print(request.GET)
        print(request.META)


        return Response({
            'status': 0,
            'msg': 'get ok'
        })

    def post(self, request, *args, **kwargs):
        # print(request._request.POST)
        # print(request.POST)
        # print(request.META.get('HTTP_AUTH'))


        # QueryDict转化为原生dict
        # print(request.query_params.dict())
        # print(type(request.data))
        # if isinstance(request.data, QueryDict):
        #     request.data.dict()


        print(request.data)

        print(a)

        return Response({
            'status': 0,
            'msg': 'post ok',
        })



Source code analysis

drf we need to write a class to inherit APIView class, enter APIView look at the source code.

Because we want to call the way is the following (CBV)

from django.conf.urls import url

from . import views
urlpatterns = [
    url(r'^books/$', views.BookAPIView.as_view()),
]

Direct call as_view (), so we just look at as_view method of APIView.

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings

    schema = DefaultSchema()

    @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.
        """
        # 这里判断 cls(我们写的继承了 APIView 的类)是否是queryset的子类,答案是“不是” ,所以这里就不用看他,直接看下面跳到下面的 2
        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
 # 2 这里调用了父类的 as_view方法。
        view = super().as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

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

There is no doubt inherited View APIView is written. We can see as_view method called as_view parent class, which consequently do not say, and certainly made some upgrades on the original method, can be seen from the comments, drf rewrite as_view do the only upgrade is "csrf_exempt (view)", so that view to avoid csrf check, visible drf feel csrf this check is not useful.

Into the parent method as_view

Then we enter this method and see what there is not the same (in fact the same, but find something is not the same).

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        #这里对 initkwargs 做了一个遍历,但是没有传任何参数,所以直接跳过。
        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):
            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)
        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

In the above code I demonstrated self.dispatch method is the most critical step is the drf the core part.

dispatch here must not be left with ctrl plus point in, because he will find View of dispatch, but in fact we drf write their own, that is, to have this method in APIView in accordance with the search order, we will first go to our own writing class there, and then go looking for parent APIView in, go to the View to find, so here is APIView found in the dispatch.

Into the dispatch method of APIView

    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:
            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

This part of the code is drf the core code.

Step by step point of view, I look at the label on the request was the second package, look into the source code.

A request module fast

Enter self.initialize_request () method

Look into this method is how to achieve the request for secondary packaging.

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        # 这是解析模块,我们后面会讲。
        parser_context = self.get_parser_context(request)
        
        #这里返回了一个 Request类实例化的对象,把我们的request丢了进去。
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

Step must be to request the second package is necessarily Request () to do, and look at him instantiated object is kind of how.

Into the Request (), to see his init method

    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__)
        )
        # 这里用 _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,)

Then, whether you are or what request.b request.a casually point, it is not getattr method will take the request?

Look at his method getattr

 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)

He would go _request found inside to find, and then can not find it, go inside to find their own, __ getattribute__ method is written in C, in short, to return to their properties. So there is the following conclusions:

 drf的请求模块
1、drf的request是在wsgi的request基础上再次封装
2、wsgi的request作为drf的request一个属性:_request
3、新的request对旧的request做了完全兼容
4、新的request对数据解析更规范化:所有的拼接参数都解析到query_params中,所有数据包数据都被解析到data中
        query_params和data属于QueryDict类型,可以 .dict() 转化成原生dict类型

Small summary:

""" 源码分析
1、drf的APIView类:重写了as_view(),但主体逻辑还是调用父类View的as_view(),局部禁用了csrf认证
    重点:所有继承drf的基本视图类APIView的视图类,都不在做csrf认证校验
2、drf的APIView类:重写了dispatch(),在内部对request进行了二次封装:self.initialize_request(request, *args, **kwargs)
    内部核心:
        走drf的Request初始化方法__init__:self._request = request
        drf的Request的getter方法__getattr__:先从self._request反射取属性,没取到再冲drf的request中取
"""
"""
核心:request除了可以访问原wsgi协议的request所有内容,还可以访问 query_params、data

This is drf module processes the request, then talk about drf rendering module processes.

Second, the rendering module

""" drf的渲染模块(了解)
这个模块就是决定最后你是可以用浏览器来访问还是json等等
1、可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置
2、可以在项目的配置文件的drf配置中通过DEFAULT_RENDERER_CLASSES对该视图的数据响应渲染做配置 - 全局配置
注:如果一个视图类在有全局配置下,还进行了局部配置,优先走自己的局部配置
"""

Source analysis:

This will not have to start from scratch to find, because previously talked about the most important part is to rewrite the dispatch. So start watching directly from the 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:
            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了
            response = self.handle_exception(exc)
        # 这里对response进行了二次处理响应,内部完成了多种结果的渲染方式。
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

The result has been the response and request thrown into this approach in secondary treatment, and back again.

Enter finalize_response () method

    def finalize_response(self, request, response, *args, **kwargs):
        """
        Returns the final response object.
        """
        # Make the error obvious if a proper response is not returned
        # 这是断言,判断你的response 是不是 HttpResponseBase的子类,断言通过的话就会继续往下走代码,没通过就会抛出异常,这里的意思就相当于是,如果你的视图函数没有返回 HttpResponse 类的对象,就会报错,也叫作响应基类。这里能通过,所以往下走。
        assert isinstance(response, HttpResponseBase), (
            'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
            'to be returned from the view, but received a `%s`'
            % type(response)
        )
        # 这里继续走,因为我们的response 是Response的子类。
        if isinstance(response, Response):
            # 第二个参数代表是允许的渲染类,去request里面拿,默认我们是没有写的,所以这里会进这个if。
            if not getattr(request, 'accepted_renderer', None):
                #内部解析了配置的渲染类
                neg = self.perform_content_negotiation(request, force=True)
                #这一步是解压赋值,就是解压缩,前面接受的是允许的渲染类,后面接受的是允许的渲染头的类型,所以说上一步的 neg 一定会被赋值一个元组,而且元组里面一定会有一个渲染类,进去看一下这个方法做了什么 ,accepted_media_type里面就是表示到底是渲染 json 还是 标签 还是页面
                request.accepted_renderer, request.accepted_media_type = neg
#  2        这里把一个一个东西都丢给了response,格式化他,然后要怎么渲染?这里是交给中间件来完成的,中间件会做这件事 response.accepted_renderer.render(response.renderer_context),来进行渲染,到此为止,再往下就会有很多很多东西,不用在看了。
            response.accepted_renderer = request.accepted_renderer
            response.accepted_media_type = request.accepted_media_type
            #这个就是他的内容,有view,有args,有kwargs,和request请求相对应的信息。
            response.renderer_context = self.get_renderer_context()

        # Add new vary headers to the response instead of overwriting.
        vary_headers = self.headers.pop('Vary', None)
        if vary_headers is not None:
            patch_vary_headers(response, cc_delim_re.split(vary_headers))

        for key, value in self.headers.items():
            response[key] = value

        return response

Enter perform_content_negotiation () method

 def perform_content_negotiation(self, request, force=False):
        """
        Determine which renderer and media type to use render the response.
        """
        #进入这个方法看一下 renderers 是什么东西。
        renderers = self.get_renderers()
        conneg = self.get_content_negotiator()

        try:
            return conneg.select_renderer(request, renderers, self.format_kwarg)
        except Exception:
            if force:
                #这里确实返回了一个元组,就看看是什么东西吧,看上面
                return (renderers[0], renderers[0].media_type)
            raise

Method entered get_renderers

 def get_renderers(self):
        """
        Instantiates and returns the list of renderers that this view can use.
        """
        return [renderer() for renderer in self.renderer_classes]

The Scarlet Letter is a list of the first derivation, from self.renderer_classes get inside, eh, where you can and I started writing on the inside of a *** "by renderer_classes class properties on the view in the view class configuration data do render response - local configuration "***

He is how to find it, start to find their own, self here is to write our own class itself, and if we do not set it, it will go to the parent class, parent class, which is APIView in.

# 第一行就是,父类也是先去自己的配置文件中找,配置文件又先走自己的配置文件,然后再去找默认的配置文件。
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

Find Process: APIView class attribute (looking from the configuration file) from their own view class (local configuration)>> own project profile (global configuration)> drf weary profile

Just look at the back of the list comprehensions, behind renderer added a brace, this is the instance of the object is instantiated objects, this list is stored inside this is a rendering of a class.

Continue back to the top finalize_response () method seen in 2

Small summary:

""" 渲染模块源码分析
1、二次处理响应对象:APIView的dispatch方法 - self.finalize_response(request, response, *args, **kwargs)
2、获取渲染类对象:进入finalize_response方法 - self.perform_content_negotiation(request, force=True)
3、从配置文件中得到渲染类对象:perform_content_negotiation -> self.get_renderers() -> [renderer() for renderer in self.renderer_classes]
"""
"""
核心:可以全局和局部配置视图类支持的结果渲染:默认可以json和页面渲染,学习该模块的目的是开发可以全局只配置json方式渲染
"""

Analysis module

""" drf的解析模块(了解) - 服务的对象是数据包数据
这个模块的作用是来解析你传来的数据的,因为有可能你传来的是 formdata类型,也有可能是json类型,它需要解析。
1、可以在视图类中通过parser_classes类属性对该视图的数据包解析做配置 - 局部配置
2、可以在项目的配置文件的drf配置中通过DEFAULT_PARSER_CLASSES对该视图的数据包解析做配置 - 全局配置
"""

Where data is parsed to complete it?

 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 进行了二次封装,就是在这里面完成的,然后丢尽了data和query_params里面,再次进入这个方法看一下
        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
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        #准备要解析的内容,看一下这个方法返回的是什么。
        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
        )
    def get_parser_context(self, http_request):
        """
        Returns a dict that is passed through to Parser.parse(),
        as the `parser_context` keyword argument.
        """
        # Note: Additionally `request` and `encoding` will also be added
        #       to the context by the Request object.
        return {
            'view': self,
            'args': getattr(self, 'args', ()),
            'kwargs': getattr(self, 'kwargs', {})
        }

So this is the method returns a dictionary. So parser_context is a dictionary of.

Enter get_parsers () method

    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        #看到这,就知道他能完成局部配置和全局配置
        return [parser() for parser in self.parser_classes]

So, you can write in your own writing class inside

parser_classes = [JSONParser, MultiPartParser, FormParser], which will allow the parsed data json class, form-data, urlencoding.

# JSONParser: json数据
# FormParser: urlencoded
# MultiPartParser:form-data

As for where you can see it, in which parsers

from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
#drf默认配置了下面前三个
class JSONParser(BaseParser)
class FormParser(BaseParser)
class MultiPartParser(BaseParser)
class FileUploadParser(BaseParser)

Find the same order and rendering module. They can also configure their own settings in a file inside

REST_FRAMEWORK = {
    # 渲染模块的全局配置:开发一般只配置json
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    # 解析模块的全局配置
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],

    # 异常模块
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'EXCEPTION_HANDLER': 'api.utils.exception_handler',
}

Small summary

""" 解析模块源码分析
1、APIView的dispatch方法:self.initialize_request(request, *args, **kwargs)内部还提供了数据解析
2、self.get_parser_context(request)提供要解析的数据,self.get_parsers()提供解析的类对象(内部从配置中找解析类)
"""
"""
核心:请求的数据包格式会有三种(json、urlencoded、form-data),drf默认支持三种数据的解析,可以全局或局部配置视图类具体支持的解析方式
"""

So rendering module and analysis module is the most important local configuration and the global configuration. It will be configured on the line.

Abnormal module

""" 异常模块(重点):重写异常模块目的是记录异常信息(项目上线)
1、在settings的drf配置中配置EXCEPTION_HANDLER,指向自定义的exception_handler函数
2、drf出现异常了,都会回调exception_handler函数,携带 异常对象和异常相关信息内容,
    在exception_handler函数完成异常信息的返回以及异常信息的logging日志
"""

Talk about how to use the exception model, then analyze the source code

Utils configure a file in your application, and then write a exception_handler.py file. This file is written inside exception_handle (exc, context) function, and then configure the settings inside, there is an exception when walking this function.

settings.py

REST_FRAMEWORK = {
    # 渲染模块的全局配置:开发一般只配置json
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    # 解析模块的全局配置
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],

    # 异常模块
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'EXCEPTION_HANDLER': 'api.utils.exception_handler',
}

The original version exception_handle (exc, context) Function

from rest_framework.response import Response

def exception_handler(exc, context):
    # 开发阶段一定要记录日志
    # logging.error(exc)
    #因为只能返回字符串,所以要用这种形式来写
    return Response('%s - %s' % (context['view'].__class__.__name__, exc))

Next source code analysis, we know, all entries are 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 = 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)
        #捕获到了一个异常,命名为 exc
        except Exception as exc:
            #然后调用这个方法,我们配置的叫做 exception_handle,所以对应的还不是我们写的那个。   这个方法就是异常模块
            response = self.handle_exception(exc)

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

Then we take a look handle_exception (exc) method

    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 = self.get_exception_handler()
        #这个就是内容,view 啊 args啊 kwargs啊这些的,和上面得到的异常对象一起丢给下面的异常处理函数,由于上面的那个步骤,这里的异常处理函数已经是我们自己写的那个 exception_handler了。
        context = self.get_exception_handler_context()
        #异常函数:接收 exc, context,返回response
        response = exception_handler(exc, context)
        
        #如果是response不是none,就代表drf自己处理了。那什么时候会为none呢?看下面,当我们不自己配,用drf的异常处理的时候会怎么样。
        if response is None:
            #交给中间件处理,原生django处理
            self.raise_uncaught_exception(exc)
        
        #告诉前台是异常返回
        response.exception = True
        return response
    def get_exception_handler(self):
        """
        Returns the exception handler that this view uses.
        """
        #再熟悉不过了,你有,就找你的,没有就找系统的
        return self.settings.EXCEPTION_HANDLER

When we do not own distribution, with exception handling drf of time, look at drf own exception_handler function

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.
    """
    #这里判断异常是不是 属于 404 这种类型的
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    #这里判断异常是不是关于权限的
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()
    #这里判断异常是不是属于基类的,就是drf最大的异常收集范围了,如果超出了这个,就说明你的异常比这个基类还要大,就是原生的异常了,类似于什么没定义就调用某个变量这种的。
    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()
        #如果处理的了的话,就返回一个response对象
        return Response(data, status=exc.status_code, headers=headers)
    #上面这个过程就相当于是drf在处理他能处理的范围,超出了能处理的范围的话,就返回none
    #处理不了的时候就返回none,让django来处理。
    return None

These are handled when drf

Next, look at what we have to deal with their own

Upgraded version of the custom exception handling

升级版
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
def exception_handler(exc, context):
    #默认是drf可以解决的异常
    response = drf_exception_handler(exc, context)
    #当drf不能解决时,就会是none,这时候就直接返回一个响应
    if response is None: # drf没有处理的异常
        response = Response({'detail': '%s' % exc})

    # 项目阶段,要记录到日志文件
    return response

Small summary

""" 源码分析
1、在APIView的dispatch方法中,有一个超大的try...except...,将代码运行异常都交给异常处理模块处理self.handle_exception(exc)
2、从配置中映射出配置处理异常的函数(自定义异常模块就是自定义配置指向自己的函数):self.get_exception_handler()
3、异常函数exception_handler(exc, context)处理异常,就会走自己的:
    先交给系统处理(客户端的异常),系统没处理(服务器异常),再自己处理
"""
"""
核心:异常信息都需要被logging记录,所以需要自定义;drf只处理客户端异常,服务器异常需要手动处理,统一处理结果
"""

Response module

Response module is actually a response, so go directly to the source code like a response

class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """
    #从升级版可以看出,这里的data就是你传进来的exc,status就是状态码
    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)

        self.data = data
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

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

So why should we come up with a final version, because the previous version you wrong, status code is 200, it does not, so to him myself configuration response status code.

Final version:

""" 究极版
response = {
    'status': 7,
    'exc': '异常信息'
}
"""
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)

    if response is None: # drf没有处理的异常(服务器异常)
        #第二个参数就是500,
        return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={
            'status': 7,
            'exc': '%s' % exc
        })

    # 项目阶段,要记录到日志文件
    return Response(status=response.status_code, data={
        'status': 7,
        # drf处理的客户端异常,原始处理方式是将异常信息放在response对象的data中,data的格式是{'datail': '具体的异常信息'}
        'exc': '%s' % response.data.get('detail')
    })

Well, the end.

Guess you like

Origin www.cnblogs.com/chanyuli/p/11898933.html