返回顶部

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

版本

 新建一个工程Myproject和一个app名为api

 

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

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

from django.urls import path
from .views import UserView

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

views.py

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

  

后台可以看到当前的版本

 

 如果url中没有传版本参数,则显示默认的版本("DEFAULT_VERSION":'v1')

 访问

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' 

  

 

posted @ 2018-08-29 00:38  Crazymagic  阅读(257)  评论(0编辑  收藏  举报