CBV源代码执行流程分析
我们知道CBV中,访问路由的时候,会先执行View中的dispatch()方法。
那么APIView是如何执行的呢?
我们依然从url.py文件配置入手分析
path('api/v1/auth/',AuthView.as_view()),
ctrl+鼠标左键,点击 as_view() 看看程序是如何执行的。
注意:点进去之后,进入的是rest_framework下的view.py文件,不再是base.py文件了。
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(APIView, cls).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点进去看看,发现class APIView(View):
原来APIView类是继承View类,view类正是from django.views import View下的View。
关键的代码:
view = super(APIView, cls).as_view(**initkwargs)
调用了父类View中的as_view方法,
上篇CBV源码分析中我们知道,当as_view执行后,当浏览器访问某个api接口时候, 就会先触发dispatch。
然而API方法中也有自己的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 = 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
关键的代码:
①
request=self.initialize_request(request, *args, **kwargs)
点initialize_request进去看看,从字面意思就是对请求来的request,进行再次封装,初始化request,跟进代码
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
initialize_request()实现的功能:将django原始的WSGIRequest,进行了改造,重新返回的是rest_framework.request.Request对象。
我们来看下初始化好的,经过处理后的Request类里面的代码参数。
①parsers=self.get_parsers() 是获取解析器(用来解析通过get_parser_context构造的字典数据的)
②parser_context=parser_context是构造好的原生request封装的字典格式数据
③negotiator=self.get_content_negotiator() 一个内容协商类,它决定如何为响应选择一个呈现器,给定一个传入请求 。
④关键点 authenticators=self.get_authenticators()
get_authenticators()实现的功能:获取[auth() for auth in self.authentication_classes]认证列表,里面是存放的一个个认证类对象。
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
再根据authentication_classes,代码定位到:
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
根据 api_settings,定位到:
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
再根据DEFAULTS,定位到
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
)
我们通过from rest_framework.authentication import BasicAuthentication
来简单看下 。
BasicAuthentication的父类BaseAuthentication的代码如下,里面有2个方法,也就是进行认证,我们可以利用此来扩充校验认证规则
class BaseAuthentication(object):
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
pass
get_authenticators方法就是去遍历继承类SessionAuthentication、BasicAuthentication的子类的列表,里面是一个一个子类对象,因为在列表推导式中[auth() for auth in self.authentication_classes]通过auth()获取类的实例对象。
咱们可以在views.py文件中自定义认证类,进行局部配置。
class LoginView(APIView):
authentication_classes = [MyAuthentication]
get_authenticators会从自身找寻,如果查询不到,再去全局配置中查找。
②
self.initial(request, *args, **kwargs),
点initial进去看看
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
#Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
#Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
#Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
完善request请求的一些注意事项,例如用户认证、权限认证、访问频率控制 。
我们先看perform_authentication看看内部到底是如何进行用户认证的?
def perform_authentication(self, request):
request.user
perform_authentication方法中只有一行代码request.user,这个用法是利用了properyt特性,通过属性转换为方法,这是python标准库的语法,那好,我们去request中去搜索带有property语法糖的user属性,去哪里搜呢?还记得我们django自己封装的那个Request嘛?
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
上来就判断not hasattr(self, ‘_user’):如果request首次没有进行request.user=XXX赋值操作,所以会进行如下操作
with wrap_attributeerrors():
self._authenticate()
我们直接看self._authenticate()方法吧
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
代码意思是循环遍历那些认证对象列表,进行authenticator.authenticate(self)认证
方法如下:
class ForcedAuthentication(object):
def __init__(self, force_user, force_token):
self.force_user = force_user
self.force_token = force_token
def authenticate(self, request):
return (self.force_user, self.force_token)
ForcedAuthentication这个是哪里来的呢?我们在最初构建自身的Request对象时候,在初始化__init__方法中通过用户和Token方式
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
进行封装forced_auth类操作,再次赋值,也就是ForcedAuthentication类,通过如下类方法
def authenticate(self, request):
return (self.force_user, self.force_token)
进行用户认证校验,返回一个元组数据格式
我们继续回过头来看_authenticate方法,为了方便查看,我在此贴出此类代码
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
当user_auth_tuple = authenticator.authenticate(self)认证校验过程中出现异常,
会抛出如下异常
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
我们来看下,它是在settings.py 进行定义,它位于rest-framework模块下,这个settings.py不是项目根目录下的settings.py哦
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser'
'UNAUTHENTICATED_TOKEN': None,
最后在_authenticate方法中,接着执行如下代码:
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
就为django rest-framework封装的Request示例,赋值添加属性self.user, self.auth即可,执行操作完毕返回,如果有异常,raise方法self._not_authenticated(),在_authenticate方法末尾去执行_not_authenticated方法,也就是上面提到的异常代码
至此如下的用户认证、权限认证、访问频率,已经处理完毕用户认证
③
response = handler(request, *args, **kwargs)
这里面是执行了对应的请求操作,如get\post请求,也就是执行了我们自定义视图里面的get方法等,在处理完毕后,赋值response然后作为参数进行如下的操作
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
看一下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
assert isinstance(response, HttpResponseBase), (
'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
'to be returned from the view, but received a `%s`'
% type(response)
)
if isinstance(response, Response):
if not getattr(request, 'accepted_renderer', None):
neg = self.perform_content_negotiation(request, force=True)
request.accepted_renderer, request.accepted_media_type = neg
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
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
随后self.response = self.finalize_response(request, response, *args, **kwargs)返回,这一步操作,跟它的父类View是不同的,
在继承APIView的视图中,get\post需要返回HttpResponse,然后再通过self.finalize_response进一步封装,最后返回。
继承APIView的编写的视图,在views.py文件中:
class BookView(APIView):
def get(self,request,*args,**kwargs):
book_list = Book.objects.all()
#当我们输入参数many = True时, serializer还能序列化queryset
bs = BookSerializers(book_list, many=True)
#print(bs.data) # 序列化的结果
return Response(bs.data)