1. 程式人生 > >Django rest framework 版本控制(原始碼分析四)

Django rest framework 版本控制(原始碼分析四)

基於上述分析

   #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
            self.initial(request, *args, **kwargs)
  #2.1處理版本資訊
        #version代表版本  scheme代表版本管理的類  determine_version返回的是一個元祖
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme 
= version, scheme

進入determine_version方法

複製程式碼
    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)
        """
#versioning_class = api_settings.DEFAULT_VERSIONING_CLASS #如果我們沒有配置版本控制類,將不做版本控制 if self.versioning_class is None: return (None, None) #scheme就是版本控制的類 #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS scheme = self.versioning_class()
#返回版本 和版本控制的類 return (scheme.determine_version(request, *args, **kwargs), scheme)
複製程式碼

scheme.determine_version的執行取決與我們所引用的版本控制類是哪一個

這裡以常用的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_param  default_version DEFAULT_VERSION 都是在settings配置
        #version_param :url中獲取值的key
        #default_version :預設版本
        #ALLOWED_VERSIONS:允許的版本
        version = kwargs.get(self.version_param, self.default_version)
        #如果version不存在,或者version不在允許訪問的版本列表中
        if not self.is_allowed_version(version):
            #丟擲異常
            raise exceptions.NotFound(self.invalid_version_message)
        #返回版本
        return version
複製程式碼

is_allowed_version方法

複製程式碼
 #判斷是否能訪問當前版本
    def is_allowed_version(self, version):
        if not self.allowed_versions:
            return True
        #version存在並且等於預設版本或者version在允許訪問的版本中
        #返回True or Fasle
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
複製程式碼

到這裡我們獲取到了具體訪問的版本和控制版本的類

回到最開始

 #2.1處理版本資訊
        #version代表版本  scheme代表版本管理的類  determine_version返回的是一個元祖
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

我們將version 和scheme封裝在request中如果我們訪問的版本符合要求我們可以通過呼叫

request.version, request.versioning_scheme 來獲得版本號和控制版本的類

 

 

BaseVersioning所有版本控制類都要繼承的基類

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
        #version存在並且等於預設版本或者version在允許訪問的版本中
        #返回True or Fasle
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
BaseVersioning

例子

a. 基於url的get傳參方式(應用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

    def 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
QueryParameterVersioning

如:/users?version=v1

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',            # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允許的版本
    'VERSION_PARAM': 'version'          # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^test/', TestView.as_view(),name='test'),
]

urls.py
urls
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning


class TestView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):

        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)

        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

 

b. 基於url的正則方式

如:/v1/users/

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',            # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允許的版本
    'VERSION_PARAM': 'version'          # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
]
urls
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning


class TestView(APIView):
    versioning_class = URLPathVersioning

    def get(self, request, *args, **kwargs):
        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)

        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

這種方式傳參url寫法原始碼有說明(URLPathVersioning):

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
        #version存在並且等於預設版本或者version在允許訪問的版本中
        #返回True or Fasle
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
BaseVersioning
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_param  default_version DEFAULT_VERSION 都是在settings配置
        #version_param :url中獲取值的key
        #default_version :預設版本
        #ALLOWED_VERSIONS:允許的版本
        version = kwargs.get(self.version_param, self.default_version)
        #如果version不存在,或者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):
        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
        )
URLPathVersioning

 

c. 基於 accept 請求頭方式

如:Accept: application/json; version=1.0

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',            # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允許的版本
    'VERSION_PARAM': 'version'          # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^test/', TestView.as_view(), name='test'),
]
urls
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import AcceptHeaderVersioning


class TestView(APIView):
    versioning_class = AcceptHeaderVersioning

    def get(self, request, *args, **kwargs):
        # 獲取版本 HTTP_ACCEPT頭
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)
        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

基於版本控制類AcceptHeaderVersioning

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.
AcceptHeaderVersioning

 

d. 基於主機名方法

如:v1.example.com

ALLOWED_HOSTS = ['*']
REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',  # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允許的版本
    'VERSION_PARAM': 'version'  # URL中獲取值的key
}
settings.py
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^test/', TestView.as_view(), name='test'),
]
urls.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import HostNameVersioning


class TestView(APIView):
    versioning_class = HostNameVersioning

    def get(self, request, *args, **kwargs):
        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)
        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

引用的的版本控制類

class 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.
HostNameVersioning

 

e. 基於django路由系統的namespace

如:example.com/v1/users/

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',  # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允許的版本
    'VERSION_PARAM': 'version'  # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^v1/', ([
                      url(r'test/', TestView.as_view(), name='test'),
                  ], None, 'v1')),
    url(r'^v2/', ([
                      url(r'test/', TestView.as_view(), name='test'),
                  ], None, 'v2')),

]
urls.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import NamespaceVersioning


class TestView(APIView):
    versioning_class = NamespaceVersioning

    def get(self, request, *args, **kwargs):
        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)
        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

引用的版本控制類NamespaceVersioning

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<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
    """
    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 + ':' + viewname
NamespaceVersioning

 

基於上述分析

   #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
            self.initial(request, *args, **kwargs)
  #2.1處理版本資訊
        #version代表版本  scheme代表版本管理的類  determine_version返回的是一個元祖
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

進入determine_version方法

複製程式碼
    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)
        """
        #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
        #如果我們沒有配置版本控制類,將不做版本控制
        if self.versioning_class is None:
            return (None, None)

        #scheme就是版本控制的類
        #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
        scheme = self.versioning_class()
        #返回版本 和版本控制的類
        return (scheme.determine_version(request, *args, **kwargs), scheme)
複製程式碼

scheme.determine_version的執行取決與我們所引用的版本控制類是哪一個

這裡以常用的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_param  default_version DEFAULT_VERSION 都是在settings配置
        #version_param :url中獲取值的key
        #default_version :預設版本
        #ALLOWED_VERSIONS:允許的版本
        version = kwargs.get(self.version_param, self.default_version)
        #如果version不存在,或者version不在允許訪問的版本列表中
        if not self.is_allowed_version(version):
            #丟擲異常
            raise exceptions.NotFound(self.invalid_version_message)
        #返回版本
        return version
複製程式碼

is_allowed_version方法

複製程式碼
 #判斷是否能訪問當前版本
    def is_allowed_version(self, version):
        if not self.allowed_versions:
            return True
        #version存在並且等於預設版本或者version在允許訪問的版本中
        #返回True or Fasle
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
複製程式碼

到這裡我們獲取到了具體訪問的版本和控制版本的類

回到最開始

 #2.1處理版本資訊
        #version代表版本  scheme代表版本管理的類  determine_version返回的是一個元祖
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

我們將version 和scheme封裝在request中如果我們訪問的版本符合要求我們可以通過呼叫

request.version, request.versioning_scheme 來獲得版本號和控制版本的類

 

 

BaseVersioning所有版本控制類都要繼承的基類

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
        #version存在並且等於預設版本或者version在允許訪問的版本中
        #返回True or Fasle
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
BaseVersioning

例子

a. 基於url的get傳參方式(應用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

    def 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
QueryParameterVersioning

如:/users?version=v1

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',            # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允許的版本
    'VERSION_PARAM': 'version'          # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^test/', TestView.as_view(),name='test'),
]

urls.py
urls
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning


class TestView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):

        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)

        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test', request=request)
        print(reverse_url)

        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
views.py

 

b. 基於url的正則方式

如:/v1/users/

REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1',            # 預設版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允許的版本
    'VERSION_PARAM': 'version'          # URL中獲取值的key
}
settings
from django.conf.urls import url, include
from web.views import TestView

urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
]
urls
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
<