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__()
的存在。