drf APIView源码浅析
APIView
在drf中,所有的视图都是以CBV的方式进行,这意味着我们必须使class继承于View类,但是原生Django的View功能有限。所以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()中,会返回该函数中的闭包函数。
那么在APIView的as_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__()的存在。

浙公网安备 33010602011771号