Django rest framework源碼分析(4)----版本

版本

 新建一個工程Myproject和一個app名爲apipython

 

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

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

from django.urls import path
from .views import UserView

urlpatterns = [
    url('users/$', UserView.as_view()),
]

views.pyapi

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

  

後臺能夠看到當前的版本app

 

 若是url中沒有傳版本參數,則顯示默認的版本("DEFAULT_VERSION":'v1')函數

 訪問this

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' 
相關文章
相關標籤/搜索