注意:一定要跟着博主的解说再看代码的中文注释及其下面的一行代码!!!
说到api版本控制,就是我们的前端人员请求的后台接口可能有多个版本,后台的接口地址一般是有两种形式,博主现以这两种形式逐一解释api版本控制组件的源码剖析。
第一种api版本控制的url格式一般是:http://localhost:8000/user/select/?version=v1。第二种是:http://localhost:8000/user/v1/select/。分别对应以下两种
1、我们依然是使用流程来解析源码,首先我们肯定是匹配user下select路由的视图类进入as_view方法
from django.conf.urls import url from . import views app_name = ‘[user]‘ urlpatterns = [ # 这是get请求参数的 url(r‘select/‘, views.UserView.as_view(), name="select"), # 用户信息查询所有 # 这是urlpath路径的参数 url(r‘^?P<version>[v1|v2])/select/$‘, views.UserView.as_view(), name="select"), # 用户信息查询所有,与上者只存其一 ]
2、在看下UserView下有没有as_view方法
class UserView(APIView): def get(self, request): print(request.version) return Response(data={"code": 200, "result": "res"})
3、会发现UserView下没有as_view方法,这个是可以查看APIView父类下有没有as_view方法,若没有,就以此类推。显而易见,APIView下有as_view方法,博主添上代码已中文注释
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 # 执行父类的as_view方法,这里的cls就是请求视图类 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)
4、但是APIView类的as_view方法中又去执行了父类的as_view方法,super关键字已经在前几篇博客中提及,朋友们可前往查看。博主就再一次添上View父类的as_view方法代码及中文注释
def as_view(cls, **initkwargs): """Main entry point for a request-response process.""" 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)) # 执行view方法 def view(request, *args, **kwargs): # 实例化请求视图类对象,即self是请求视图类对象 self = cls(**initkwargs) if hasattr(self, ‘get‘) and not hasattr(self, ‘head‘): self.head = self.get # 注意:这里的request对象还是原生的request对象 self.request = request self.args = args self.kwargs = kwargs # dispatch方法至关重要 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
5、在上面的方法中他就会执行到里面的view方法,根据中文注释,最后再一次从请求视图类开始寻找dispatch方法,请求视图类中没有,就又回到了APIView类中,执行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还是请求视图类对象 self.args = args self.kwargs = kwargs # 这里是对原生的request加工处理,返回一个新的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: # 通过python的反射机制反射到请求视图类的方法名称 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
6、api版本控制组件就在initial方法里头,我们点进去看下,添上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. # rest framework的api的版本控制 # 类似于:api/?version=v1 或者是 api/v1/ version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted # 查看源码,用户验证的方法,这个request 是加工之后的request self.perform_authentication(request) # 用户权限验证 self.check_permissions(request) # 用户访问频率限制 self.check_throttles(request)
7、里面就会有版本控制的代码语句:version, schema = self.determine_version(request, *args, **kwargs),接下来就添上涉及determine_version方法的一些重要代码及中文注释
class APIView(View): # 如果请求视图类中没有versioning_class变量及值,就会去匹配全局配置文件的值 versioning_class = api_settings.DEFAULT_VERSIONING_CLASS def determine_version(self, request, *args, **kwargs): """ If versioning is being used, then determine any API version for the incoming request. Returns a two-tuple of (version, versioning_scheme) """ # 这里判断self.versioning_class是否有值 if self.versioning_class is None: return (None, None) # 实例化版本控制类对象 scheme = self.versioning_class() from rest_framework.versioning import BaseVersioning # 这里就对应上一个方法的版本值和调用的版本控制类对象 return (scheme.determine_version(request, *args, **kwargs), scheme)
8、从上面就可以看出我们可以在请求视图类中编写上面提及的变量,但是这个变量的值就不是列表了,还有代码中的注释,值可以是一个我们自定义或者框架自带的版本控制类,随后添上Django框架中versioning.py文件的部分版本控制类
class BaseVersioning(object): # 如果请求视图类中没有以下变量和值,就会匹配全局配置文件中的值 default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS # 这个变量对应的参数的键(用户请求的版本参数名称) version_param = api_settings.VERSION_PARAM def determine_version(self, request, *args, **kwargs): msg = ‘{cls}.determine_version() must be implemented.‘ raise NotImplementedError(msg.format( cls=self.__class__.__name__ )) def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): return _reverse(viewname, args, kwargs, request, format, **extra) def is_allowed_version(self, version): if not self.allowed_versions: return True return ((version is not None and version == self.default_version) or (version in self.allowed_versions)) class QueryParameterVersioning(BaseVersioning): """ GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json """ invalid_version_message = _(‘Invalid version in query parameter.‘) def determine_version(self, request, *args, **kwargs): # 获取版本值 version = request.query_params.get(self.version_param, self.default_version) # 版本不允许,就会抛出版本不存在的一些提示信息 if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) return version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): # 这个方法就是生成反向的url,这里传递视图的名称之外,只需要传递新封装的request对象即可 url = super(QueryParameterVersioning, self).reverse( viewname, args, kwargs, request, format, **extra ) if request.version is not None: return replace_query_param(url, self.version_param, request.version) return url class URLPathVersioning(BaseVersioning): """ To the client this is the same style as `NamespaceVersioning`. The difference is in the backend - this implementation uses Django‘s URL keyword arguments to determine the version. An example URL conf for two views that accept two different versions. # 编写url路由的规则就需要遵循下面的编写规则,这个很重要 urlpatterns = [ url(r‘^(?P<version>[v1|v2]+)/users/$‘, users_list, name=‘users-list‘), url(r‘^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$‘, users_detail, name=‘users-detail‘) ] GET /1.0/something/ HTTP/1.1 Host: example.com Accept: application/json """ invalid_version_message = _(‘Invalid version in URL path.‘) def determine_version(self, request, *args, **kwargs): # 获取版本值 version = kwargs.get(self.version_param, self.default_version) # 版本不允许,就会抛出版本不存在的一些提示信息 if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) return version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): # 这个方法就是生成反向的url,这里传递视图的名称之外,只需要传递新封装的request对象即可 if request.version is not None: kwargs = {} if (kwargs is None) else kwargs kwargs[self.version_param] = request.version return super(URLPathVersioning, self).reverse( viewname, args, kwargs, request, format, **extra )
9、最后,我们若想要自定义,就需要继承里面的基类,就和上面的类似,而且其实我们是不需要自定义,因为Django中已经够开发使用。如果在全局中配置,方式如下:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "URLPathVersioning", "DEFAULT_VERSION": "v1", # 默认的版本 "ALLOWED_VERSIONS": ["v1", "v2"] # 允许的版本 }