DRF安装配置-源码分析
DRF安装配置
什么是DRF
Django REST framework 的简写,主要是为前后端分离服务的,用来写api,为前端提供数据接口。
为什么要有DRF
虽然我们即使不适用DRF,一样能够写出满足RESTful规范的接口,但是选择使用DRF作为工具可以提高开发效率,因为他不仅能够快速帮我们设计出符合规范的接口,还提供了 权限、认证等强大的功能。
DFF安装命令
cnpm install djangoframework
DRF的使用
导入模块,让类继承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',
})
源码分析
drf 需要我们写的类去继承 APIView 类,进入APIView看一下源码。
因为我们要调用的方式就是以下(CBV)
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^books/$', views.BookAPIView.as_view()),
]
直接调用 as_view( ),所以我们就直接看APIView的as_view方法。
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)
毫无疑问的是 APIView 是继承View写的。我们可以看到 as_view 方法中调用了 父类的 as_view方法,这就啥都不用说了,肯定是在原来的方法上做了一些升级,从注释中都可以看出,drf 重写了 as_view 做的唯一的升级就是 “csrf_exempt(view)” ,让 view 避开csrf校验,可见drf也觉得csrf这个校验不太好用。
进入父类as_view方法
然后我们就进入这个方法看看有什么不一样(其实一样,只不过查找到的东西不一样了)。
@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
在上面代码中我表明了 self.dispatch 方法是最关键的一步,也是 drf最核心的部分。
这里的 dispatch 一定不能用ctrl加左键点进去,因为他会找到 View 的dispatch,但其实我们drf自己写了,也就是在 APIView 中有这个方法,按照查找顺序,我们是会先去找我们自己写的类中有没有,然后再去父类 APIView 中找,再去 View中找,所以这里找到的是 APIView中的 dispatch。
进入 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:
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
这一部分的代码,就是 drf 最核心的代码了。
一步一步来看,先看我标注的 对request进行了二次封装,进入源码来看一下。
一、请求模快
进入 self.initialize_request()方法
进入这个方法看一下是怎么实现对 request 进行二次封装的。
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
)
想必对 request 进行二次封装的步骤就一定是 Request() 做的了,看一下他实例化的对象是怎么样的。
进入Request(),查看他的init方法
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,)
然后, 无论你是 request.a 或者 request.b 随便点什么,是不是都会走 request 的getattr方法?
看一下他的 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)
发现他会先去 _request 里面找,然后找不到了,再去自己里面找,__getattribute__方法是用 C 写的,总之是返回自己的属性的。所以就有了以下结论:
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类型
小总结:
""" 源码分析
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
这就是drf请求模块的流程,接下来讲一下drf渲染模块的流程。
二、渲染模块
""" drf的渲染模块(了解)
这个模块就是决定最后你是可以用浏览器来访问还是json等等
1、可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置
2、可以在项目的配置文件的drf配置中通过DEFAULT_RENDERER_CLASSES对该视图的数据响应渲染做配置 - 全局配置
注:如果一个视图类在有全局配置下,还进行了局部配置,优先走自己的局部配置
"""
源码分析:
这次就不用再从头开始找了,因为之前讲过了最重要的部分就是重写的 dispatch。所以直接从 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
把已经得到的response结果和request丢进这个方法里进行二次处理,然后再返回。
进入finalize_response()方法
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
进入perform_content_negotiation( )方法
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
进入 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]
是一个列表推导式,要从 self.renderer_classes 里面拿,诶,这里就可以和我开始写的红字里的第一条对上了 *** “可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置”***
他是怎么查找的呢,先从自己找,这里的self就是我们自己写的类本身,然后如果我们没有设置的话,就会去找父类的,父类,也就是 APIView 中的。
# 第一行就是,父类也是先去自己的配置文件中找,配置文件又先走自己的配置文件,然后再去找默认的配置文件。
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
查找流程:从自己的视图类(局部配置) > APIView的类属性(从配置文件中找) >自己的项目配置文件(全局配置) > drf磨人的配置文件
在回去看刚才的列表推导式,在renderer后面加了一个 括号,这就是实例化对象了,这个这个列表里面存放的就是一个一个的渲染类的实例化对象。
继续回到上面 finalize_response() 方法 中看 2
小总结:
""" 渲染模块源码分析
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方式渲染
"""
解析模块
""" drf的解析模块(了解) - 服务的对象是数据包数据
这个模块的作用是来解析你传来的数据的,因为有可能你传来的是 formdata类型,也有可能是json类型,它需要解析。
1、可以在视图类中通过parser_classes类属性对该视图的数据包解析做配置 - 局部配置
2、可以在项目的配置文件的drf配置中通过DEFAULT_PARSER_CLASSES对该视图的数据包解析做配置 - 全局配置
"""
解析数据是在哪里完成的呢?
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', {})
}
所以这个方法返回的就是一个字典。那么parser_context就是一个字典了。
进入get_parsers()方法
def get_parsers(self):
"""
Instantiates and returns the list of parsers that this view can use.
"""
#看到这,就知道他能完成局部配置和全局配置
return [parser() for parser in self.parser_classes]
所以,可以在自己写的类里面写上
parser_classes = [JSONParser, MultiPartParser, FormParser],这就会允许解析 json类的数据、form-data、urlencoding。
# JSONParser: json数据
# FormParser: urlencoded
# MultiPartParser:form-data
至于这个在哪里可以看到呢,在parsers里面
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
#drf默认配置了下面前三个
class JSONParser(BaseParser)
class FormParser(BaseParser)
class MultiPartParser(BaseParser)
class FileUploadParser(BaseParser)
查找顺序和渲染模块一样。 也可以在settings文件里面自己配置
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',
}
小总结
""" 解析模块源码分析
1、APIView的dispatch方法:self.initialize_request(request, *args, **kwargs)内部还提供了数据解析
2、self.get_parser_context(request)提供要解析的数据,self.get_parsers()提供解析的类对象(内部从配置中找解析类)
"""
"""
核心:请求的数据包格式会有三种(json、urlencoded、form-data),drf默认支持三种数据的解析,可以全局或局部配置视图类具体支持的解析方式
"""
所以 渲染模块和解析模块 最重要的就是局部配置和全局配置。会配置就行了。
异常模块
""" 异常模块(重点):重写异常模块目的是记录异常信息(项目上线)
1、在settings的drf配置中配置EXCEPTION_HANDLER,指向自定义的exception_handler函数
2、drf出现异常了,都会回调exception_handler函数,携带 异常对象和异常相关信息内容,
在exception_handler函数完成异常信息的返回以及异常信息的logging日志
"""
先讲怎么去使用异常模块,然后再分析源码
先在自己的应用下配置一个文件 utils,然后写一个 exception_handler.py 文件。这个文件里面写 exception_handle(exc, context) 函数,然后在settings里面配置,有异常的时候走这个函数。
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',
}
exception_handle(exc, context) 函数最初的版本
from rest_framework.response import Response
def exception_handler(exc, context):
# 开发阶段一定要记录日志
# logging.error(exc)
#因为只能返回字符串,所以要用这种形式来写
return Response('%s - %s' % (context['view'].__class__.__name__, exc))
接下来分析源码,我们知道的,所有的入口都是 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
然后我们去看一下handle_exception(exc)方法
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
当我们不自己配,用drf的异常处理的时候,先看一下drf自己的 exception_handler函数
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
以上是drf处理的时候
接下来再看一下我们自己处理得时候
升级版的自定义异常处理
升级版
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
小总结
""" 源码分析
1、在APIView的dispatch方法中,有一个超大的try...except...,将代码运行异常都交给异常处理模块处理self.handle_exception(exc)
2、从配置中映射出配置处理异常的函数(自定义异常模块就是自定义配置指向自己的函数):self.get_exception_handler()
3、异常函数exception_handler(exc, context)处理异常,就会走自己的:
先交给系统处理(客户端的异常),系统没处理(服务器异常),再自己处理
"""
"""
核心:异常信息都需要被logging记录,所以需要自定义;drf只处理客户端异常,服务器异常需要手动处理,统一处理结果
"""
响应模块
响应模块其实就是response,所以直接去看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
所以我们为什么要搞出一个终极版,因为之前的版本你出错了,状态码都是200,这不对,所以要自己给他配置响应状态码。
终极版:
""" 究极版
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')
})
好了,结束。