API 版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。 DRF提供了许多不同的版本控制方案。
可能会有一些客户端因为某些原因不再维护了,但是我们后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案。
DRF提供的版本控制方案 DRF提供了五种版本控制方案,如下图:
文章图片
版本控制系统的使用 全局配置
这里我们以 URLPathVersioning 为例,还是在项目的settings.py中REST_FRAMEWORK配置项下配置:
REST_FRAMEWORK = {
...
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1',# 默认的版本
'ALLOWED_VERSIONS': ['v1', 'v2'],# 有效的版本
'VERSION_PARAM': 'version',# 版本的参数名与URL conf中一致
}
局部配置
注意,通常我们是不会单独给某个视图设置版本控制的,如果你确实需要给单独的视图设置版本控制,你可以在视图中设置versioning_class属性,如下:
class PublisherViewSet(ModelViewSet):...
versioning_class = URLPathVersioning
urls.py
urlpatterns = [
...
url(r'^(?P[v1|v2]+)/publishers/$', views.PublisherViewSet.as_view({'get': 'list', 'post': 'create'})),
url(r'^(?P[v1|v2]+)/publishers/(?P\d+)/$', views.PublisherViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),]
【Python|Django REST framework 版本】我们在视图中可以通过访问 request.version 来获取当前请求的具体版本,然后根据不同的版本来返回不同的内容:
我们可以在视图中自定义具体的行为,下面以不同的版本返回不同的序列化类为例
class PublisherViewSet(ModelViewSet):def get_serializer_class(self):
"""不同的版本使用不同的序列化类"""
if self.request.version == 'v1':
return PublisherModelSerializerVersion1
else:
return PublisherModelSerializer
queryset = models.Publisher.objects.all()
REST framework 提供的默认版本的源码
############################## versioning.py #############################
# coding: utf-8
from __future__ import unicode_literalsimport refrom django.utils.translation import ugettext_lazy as _from rest_framework import exceptions
from rest_framework.compat import unicode_http_header
from rest_framework.reverse import _reverse
from rest_framework.settings import api_settings
from rest_framework.templatetags.rest_framework import replace_query_param
from rest_framework.utils.mediatypes import _MediaType# 基础类,其他类要继承此类
class BaseVersioning(object):
default_version = api_settings.DEFAULT_VERSION# 配置文件中获取相应信息
allowed_versions = api_settings.ALLOWED_VERSIONS
version_param = api_settings.VERSION_PARAMdef 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 AcceptHeaderVersioning(BaseVersioning):
"""
GET /something/ HTTP/1.1
Host: example.com
Accept: application/json;
version=1.0
"""
invalid_version_message = _('Invalid version in "Accept" header.')def determine_version(self, request, *args, **kwargs):
media_type = _MediaType(request.accepted_media_type)
version = media_type.params.get(self.version_param, self.default_version)
version = unicode_http_header(version)
if not self.is_allowed_version(version):
raise exceptions.NotAcceptable(self.invalid_version_message)
return version# We don't need to implement `reverse`, as the versioning is based
# on the `Accept` header, not on the request URL.# 版本信息在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.urlpatterns = [
url(r'^(?P[v1|v2]+)/users/$', users_list, name='users-list'),
url(r'^(?P[v1|v2]+)/users/(?P[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 version is None:
version = self.default_versionif not self.is_allowed_version(version):
raise exceptions.NotFound(self.invalid_version_message)
return versiondef reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:
kwargs = {} if (kwargs is None) else kwargs
kwargs[self.version_param] = request.versionreturn super(URLPathVersioning, self).reverse(
viewname, args, kwargs, request, format, **extra
)class NamespaceVersioning(BaseVersioning):
"""
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[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
"""
invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.')def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None)
if resolver_match is None or not resolver_match.namespace:
return self.default_version# Allow for possibly nested namespaces.
possible_versions = resolver_match.namespace.split(':')
for version in possible_versions:
if self.is_allowed_version(version):
return version
raise exceptions.NotFound(self.invalid_version_message)def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:
viewname = self.get_versioned_viewname(viewname, request)
return super(NamespaceVersioning, self).reverse(
viewname, args, kwargs, request, format, **extra
)def get_versioned_viewname(self, viewname, request):
return request.version + ':' + viewnameclass HostNameVersioning(BaseVersioning):
"""
GET /something/ HTTP/1.1
Host: v1.example.com
Accept: application/json
"""
hostname_regex = re.compile(r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$')
invalid_version_message = _('Invalid version in hostname.')def determine_version(self, request, *args, **kwargs):
hostname, separator, port = request.get_host().partition(':')
match = self.hostname_regex.match(hostname)
if not match:
return self.default_version
version = match.group(1)
if not self.is_allowed_version(version):
raise exceptions.NotFound(self.invalid_version_message)
return version# We don't need to implement `reverse`, as the hostname will already be
# preserved as part of the REST framework `reverse` implementation.# 通过url参数区分
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 versiondef reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
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
推荐阅读
- 推荐系统论文进阶|CTR预估 论文精读(十一)--Deep Interest Evolution Network(DIEN)
- Python专栏|数据分析的常规流程
- Python|Win10下 Python开发环境搭建(PyCharm + Anaconda) && 环境变量配置 && 常用工具安装配置
- Python绘制小红花
- Pytorch学习|sklearn-SVM 模型保存、交叉验证与网格搜索
- OpenCV|OpenCV-Python实战(18)——深度学习简介与入门示例
- python|8. 文件系统——文件的删除、移动、复制过程以及链接文件
- 爬虫|若想拿下爬虫大单,怎能不会逆向爬虫,价值过万的逆向爬虫教程限时分享
- 分布式|《Python3网络爬虫开发实战(第二版)》内容介绍
- java|微软认真聆听了开源 .NET 开发社区的炮轰( 通过CLI 支持 Hot Reload 功能)