zoukankan      html  css  js  c++  java
  • 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' 
    

      

  • 相关阅读:
    Mybatis动态数据源
    [Java基础]判断字符串指定字符类型
    [Java基础]让Map value自增
    (转载)UTF-8占几个字符
    JVM程序计数器
    Mybatis异常总结
    通过类对象来获取类中的属性,方法,构造器
    主动引用和被动引用
    ClassLoader类加载器浅见
    反射----获取class对象的五种方法
  • 原文地址:https://www.cnblogs.com/crazymagic/p/9551852.html
Copyright © 2011-2022 走看看