rest-framework框架——版本

一、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方法中做了各种初始化操作。

  其中versionscheme是版本控制组件的入口,也是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"
}

 

 

  访问成功,显示如下所示:

  

  访问失败,会使用框架提示信息:

  

 

 

 

 

 

 

posted @ 2019-12-04 14:46  休耕  阅读(935)  评论(0编辑  收藏  举报