一、DRF版本控制介绍
随着项目更新,版本会越来越多,不能新的版本出现,旧版本就不再使用维护了。因此不同的版本会有不同的处理,且接口会返回不同的信息。
API版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。
DRF提供了许多不同的版本控制方案。可能会有一些客户端因为某些原因不再维护了,但是我们后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案。
rest_framework.versioning里提供了五种版本控制方案如下所示:
from rest_framework import versioning # view中引入版本控制 # 查看 rest_framework/versioning.py文件: # 最基础的版本控制类,给其他版本控制类提供一些共用方法 class BaseVersioning:... # 在accept请求头中配置版本信息 # accept代表希望返回的数据类型,可以携带版本信息 # Accept: application/json; version=1.0 class AcceptHeaderVersioning(BaseVersioning): # 将版本信息放到请求头中 """ GET /something/ HTTP/1.1 Host: example.com Accept: application/json; version=1.0 """ # 在url上携带版本信息 # url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'), class URLPathVersioning(BaseVersioning): # 将版本信息放入URL中 """ 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 """ # 把版本信息放在路由分发里,并把路由的namespace配置成版本 # url(r'^v1/', include('users.urls', namespace='v1')) class NamespaceVersioning(BaseVersioning): # 通过namespace来区分版本 """ To the client this is the same style as `URLPathVersioning`. The difference is in the backend - this implementation uses Django's URL namespaces to determine the version. An example URL conf that is namespaced into two separate versions # users/urls.py urlpatterns = [ url(r'^/users/$', users_list, name='users-list'), url(r'^/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail') ] # urls.py urlpatterns = [ url(r'^v1/', include('users.urls', namespace='v1')), url(r'^v2/', include('users.urls', namespace='v2')) ] GET /1.0/something/ HTTP/1.1 Host: example.com Accept: application/json """ # 在我们的host上配置版本信息 # Host:v1.example.com class HostNameVersioning(BaseVersioning): # 通过主机名来区分版本 """ GET /something/ HTTP/1.1 Host: v1.example.com Accept: application/json """ # 在url过滤条件上配置版本信息 # GET /something/?version=0.1 HTTP/1.1 class QueryParameterVersioning(BaseVersioning): # 通过url查询参数区分版本 """ GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json """
二、源码分析
1、查看APIView类中的dispatch方法
APIView中重新定义了dispatch方法,重新封装了request。同时try中的代码一定会执行,因此会执行self.initial()方法。
class APIView(View): 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 # deprecate? try: self.initial(request, *args, **kwargs)
2、查看initial方法
iniital方法中做了各种初始化操作。
其中version和scheme是版本控制组件的入口,也是determine_version方法的返回值。
determine_version()的返回值,传值给 request.version 和 request.versioning_scheme。
class APIView(View):
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
...
可以看到在这里是有version的,也就是版本。
3、查看determine_version方法
可以看到首先判断 self.versioning_class 是否为None。如果为None,则返回None、None。
scheme实际是自己配置的版本控制类的实例化对象。默认的版本控制类是null,我们必须要有一个自己配置的版本控制类。
且配置的类里必须要有一个 determine_version 方法(返回值就是版本号)。
class APIView(View): 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)
(1)继续查看versioning_class
class APIView(View): ... versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
由此可知就是在api_sttings中配置了一个类。
因此在前面determine_version()中self.versioning_class()是做了一个实例化的动作。
继续查看api_settings:在site-packages/rest_framework/settings.py文件中可以看到api_settings其实是APISettings的实例化:
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
DEFAULTS默认读取的是 site-packages/rest_framework/settings.py中DEFAULTS字段:
DEFAULTS = {
...
'DEFAULT_VERSIONING_CLASS': None,
...
}
因此,默认情况下self.versioning_class的返回值为None。
(2)return (scheme.determine_version(request, *args, **kwargs), scheme)返回了一个元组
如果在/views/course.py的视图类中定义versioning_class:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): self.dispatch return Response('...')
则可以实例化得到scheme实例,并在函数返回语句中返回scheme。
(3)进一步查看QueryParameterVersioning中的determine_version
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
这里返回的是version,预计就是版本号。
(4)进一步查看request.query_params定义
class Request(object): @property def query_params(self): """ More semantically correct name for request.GET. """ return self._request.GET
因此version = request.query_params.get(self.version_param, self.default_version)其实是去URL中获取GET传来的version对应的参数。
(5)查看version_param
class BaseVersioning(object): default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM
由此可知这是一个全局配置,默认值就等于version,由此可知前面返回的version就是版本号。
4、version返回,分析inital方法
def initial(self, request, *args, **kwargs): # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme
version是版本,scheme是对象(自己配置的版本控制类的实例化对象)并分别赋值给request.version和request.scheme。
5、在视图类中拿到版本
class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): print(request.version) return Response('...')
6、页面访问测试
(1)直接访问
此时python后台输出:none
(2)用url参数传递版本
此时python后台输出:v1
二、版本控制类及源码解析
在项目中要引入rest_framework框架提供的版本控制类:
from rest_framework import versioning
1、查看QueryParameterVersioning类
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
可以看到version拿到后,用self.is_allowed_version方法做了一个判断。
2、查看is_allowed_version方法
class BaseVersioning(object): default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM 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))
可以看到ALLOWED_VERSIONS也是存放在配置文件中。
3、在settings.py中添加允许的版本
凡是关于restframework框架的配置都需要写 REST_FRAMEWORK,并让它等于一个字典。
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'] # 允许的版本 }
4、访问验证
三、默认版本与版本参数
settings.py做如下配置
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本 'VERSION_PARAM': 'version', # 把默认的version修改为其他参数:http://127.0.0.1:8000/api/course/?versionsss=v1 'DEFAULT_VERSION': 'v1', # 默认版本 }
1、修改参数为其他值访问效果
2、配置默认版本后不写版本参数也可获取默认版本
python后台输出:v1。
四、配置版本控制
1、局部版本控制
前面都是在单个类中配置了版本控制,如下所示:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): self.dispatch return Response('...')
2、全局版本控制
源码查看到全局版本控制配置信息:
class APIView(View): versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # Allow dependency injection of other settings to make testing easier. settings = api_settings
因此也可以在settings.py中配置全局版本控制:
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本 'VERSION_PARAM': 'version', # 把默认的version修改为其他参数:http://127.0.0.1:8000/api/course/?versionsss=v1 'DEFAULT_VERSION': 'v1', # 默认版本 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning', # 全局版本控制 }
显示效果如下:
五、写版本推荐方式——基于url的正则方式(如:/v1/users/)
前面写的是基于url的get传参方式,如:/users?version=v1,但是这种方式显示版本不是最推荐的。一般需要把版本号写在前面。改写需要调整urls.py配置。
1、项目urls.py修改
from django.conf.urls import url, include urlpatterns = [ # path('admin/', admin.site.urls), url(r'^api/', include('api.urls')), ]
2、应用目录下创建urls.py文件及配置
from django.conf.urls import url, include from api.views import course urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view()), ]
3、修改/api/views/course.py类视图文件
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning class CourseView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): print(request.version) return Response('...')
4、访问显示效果
以后都推荐用这种方式写版本,全局配置修改同上。
六、其他版本使用方式
详见:http://www.cnblogs.com/wupeiqi/articles/7805382.html
基于 accept 请求头方式,如:Accept: application/json; version=1.0
基于主机名方法,如:v1.example.com
基于django路由系统的namespace,如:example.com/v1/users/
七、项目示例
1、DRFDemo项目中引入版本控制应用
引入versionDemo应用。配置url.py和view.py文件如下所示:
# versionDemo/urls.py from django.urls import path, include from .views import DemoView urlpatterns = [ path(r"", DemoView.as_view()), ] # versionDemo/views.py from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response class DemoView(APIView): def get(self, request): print(request.version) print(request.versioning_scheme) # 得到版本号,根据版本号的不同返回不同的信息 if request.version == "v1": return Response("v1版本的数据") elif request.version == "v2": return Response("v2版本的数据") return Response("不存在的版本")
2、在settings.py文件中添加版本控制类
创建定义自己的版本控制类,类中必须自定义 determine_version 方法:
# 创建文件/DRFDemo/utils/version.py class MyVersion: def determine_version(self, request, *args, **kwargs): # 该方法返回值给了 request.version # 返回版本号 # 版本号携带在过滤条件中 xxx?version=v1 version = request.query_params.get("version", "v1") return version
再在settings.py中全局引入版本控制,覆盖默认值为None的 versioning_class:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion" }
3、测试获取version信息
默认访问“127.0.0.1:8000/version/”,返回得到信息:“v1版本的数据”
访问“127.0.0.1:8000/version/?version=v2”,返回得到信息:“v2版本的数据”
访问"127.0.0.1:8000/version/?version=v3",返回得到信息:“不存在的版本”
4、使用内置的版本控制类
引入内置的版本控制类:
from rest_framework.versioning import QueryParameterVersioning,AcceptHeaderVersioning,NamespaceVersioning,URLPathVersioning #基于url的get传参方式:QueryParameterVersioning------>如:/users?version=v1 #基于url的正则方式:URLPathVersioning------>/v1/users/ #基于accept请求头方式:AcceptHeaderVersioning------>Accept: application/json; version=1.0 #基于主机名方法:HostNameVersioning------>v1.example.com #基于django路由系统的namespace:NamespaceVersioning------>example.com/v1/users/
上述五种版本控制类都默认继承 BaseVersioning:
class BaseVersioning: 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))
依据BaseVersioning配置settings.py中版本控制:
REST_FRAMEWORK = { # "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion", # 默认使用的版本控制类 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning", # 默认使用的版本 "DEFAULT_VERSION": "v1", # 允许的版本 "ALLOWED_VERSIONS": "v1, v2", # 版本使用的参数名称 "VERSION_PARAM": "ver" }
访问成功,显示如下所示:
访问失败,会使用框架提示信息: