Django rest framework源码分析(4)----版本
版本
新建一个工程Myproject和一个app名为api
api/models.py 数据结构如下:
from django.db import models class UserGroup(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): user_type_choices = ( (1,'普通用户'), (2,'VIP'), (3,'SVIP'), ) user_type = models.IntegerField(choices=user_type_choices) username = models.CharField(max_length=32,unique=True) password = models.CharField(max_length=64) group = models.ForeignKey("UserGroup") roles = models.ManyToManyField("Role") class UserToken(models.Model): user = models.OneToOneField(to='UserInfo') token = models.CharField(max_length=64) class Role(models.Model): title = models.CharField(max_length=32)
Myproject/urls.py
from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^api/', include('api.urls')), ]
api/urls.py
from django.urls import path from .views import UserView urlpatterns = [ url('users/$', UserView.as_view()), ]
views.py
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning class UserView(APIView): versioning_class = QueryParameterVersioning def get(self,request,*args,**kwargs): #获取版本 print(request.version) return HttpResponse('用户列表')
settings.py
REST_FRAMEWORK = { "DEFAULT_VERSION":'v1', #默认的版本 "ALLOWED_VERSIONS":['v1','v2'], #允许的版本 "VERSION_PARAM":'version' #GET方式url中参数的名字 ?version=xxx }
1.url中通过GET传参
QueryParameterVersioning用于去GET参数中取version
访问
http://127.0.0.1:8000/api/users/?version=v2
后台可以看到当前的版本
如果url中没有传版本参数,则显示默认的版本("DEFAULT_VERSION":'v1')
访问
http://127.0.0.1:8000/api/users/
如果url传的版本超过settings中的允许范围则报错
访问
http://127.0.0.1:8000/api/users/?version=v3
2.在URLPATH中获取
(1)修改api/urls.py
通常情况我门应该用URLPATH的方式,而不是用前面GET()传参方式
url里面通过正则表达式定义哪些版本,
urlpatterns = [ url('(?P<version>[v1|v2]+)/users/$', views.UserView.as_view()), ]
(2)views.py
URLPathVersioning:去url路径里面获取版
修改 UserView 类视图 代码如下
from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning class UserView(APIView): versioning_class = URLPathVersioning def get(self,request,*args,**kwargs): #获取版本 print(request.version) return HttpResponse('用户列表')
浏览器访问地址
http://127.0.0.1:8000/api/v2/users/
然后后台拿到版本信息
这个URLPathVersioning我们可以放到settings里面,全局配置,就不用写到views里面,每个类都要写一遍了
settings.py
# 版本 # REST_FRAMEWORK = { # "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", # "DEFAULT_VERSION":'v1', #默认的版本 # "ALLOWED_VERSIONS":['v1','v2'], #允许的版本 # "VERSION_PARAM":'version' #get方式url中参数的名字 ?version=xxx # } #全局 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", }
修改views.py
class UserView(APIView): def get(self,request,*args,**kwargs): #获取版本 print(request.version) return HttpResponse('用户列表')
我们可以得到同样的结果
反向解析访问的url
添加name = 'api_user'
urlpatterns = [ url('(?P<version>[v1|v2]+)/users/$', views.UserView.as_view(),name='api_user'), ]
在视图函数中添加反向解析
class UserView(APIView): def get(self,request,*args,**kwargs): #获取版本 print(request.version) #获取处理版本的对象 print(request.versioning_scheme) #获取浏览器访问的url,reverse反向解析 #需要两个参数:viewname就是url中的别名,request=request是url中要传入的参数 #(?P<version>[v1|v2]+)/users/,这里本来需要传version的参数,但是version包含在request里面(源码里面可以看到),所有只需要request=request就可以 url_path = request.versioning_scheme.reverse(viewname='api_user',request=request) print(url_path) # self.dispatch return HttpResponse('用户列表')
浏览器访问
http://127.0.0.1:8000/api/v1/users/
后台获取
源码流程
源码入口 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
执行认证 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)
在版本处理我们追踪 self.determine_version(request, *args, **kwargs) 代码如下
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) """ if self.versioning_class is None: return (None, None) # 处理版本类的对象 scheme = self.versioning_class() return (scheme.determine_version(request, *args, **kwargs), scheme)
在上面的源码中我们可以看到 ,它会首先检查我们是否自己在类视图中配置了版本控制 的类属性 versioning_class 注意在这里它是一个类(返回的是版本和处理版本的对象), 如果我们没有自己去定义,就会去执行下面的 self.versioning_class(),其代码如下
在上面的源码中我们可以看到,它回去配置文件中找 DEFAULT_VERSIONING_CLASS 类的路径
那么在 determine_version 函数中,返回的结果究竟是什么呢
我们可以任意的追踪下面的 QueryParameterVersioning 和 URLPathVersioning 类中的 determine_version 的返回值
from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning
determine_version 代码如下
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
它返回的是版本,所以上述返回的是版本和调用版本的对象 在封装到request对象中
request.version, request.versioning_scheme = version, scheme
这里我使用的是 URLPathVersioning 其源码如下
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. 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
可以看到 url配置
里面有个is_allowed_version,点进去可以看到一些基本参数 (它是继承BaseVersioning基类)
class BaseVersioning(object): #默认的版本 default_version = api_settings.DEFAULT_VERSION #允许的版本 allowed_versions = api_settings.ALLOWED_VERSIONS #默认参数(是version,比如你可以自定义为v) 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))
在BaseVersioning 类中有3个类属性,我们可以在配置文件中设置
"DEFAULT_VERSION":'v1', #默认的版本 "ALLOWED_VERSIONS":['v1','v2'], #允许的版本 "VERSION_PARAM":'version'