zoukankan      html  css  js  c++  java
  • Django Rest Framework源码剖析(四)-----API版本

    一、简介

    在我们给外部提供的API中,可会存在多个版本,不同的版本可能对应的功能不同,所以这时候版本使用就显得尤为重要,django rest framework也为我们提供了多种版本使用方法。

    二、基本使用

    版本使用方式:

    1.在url中传递版本:如http://www.example.com/api?version=v1

    和其他组建一样,我们在utils里面建立version.py,添加版本类

    #!/usr/bin/env python3
    #_*_ coding:utf-8 _*_
    #Author:wd
    from  rest_framework.versioning import BaseVersioning
    
    class Myversion(BaseVersioning):
        def determine_version(self, request, *args, **kwargs):
            myversion=request.query_params.get('version')
            return myversion

    在订单视图中应用版本,(当然直接可以使用request.get获取)

    class OrderView(APIView):
        '''查看订单'''
        from utils.permissions import MyPremission
        from utils.version import Myversion
        authentication_classes = [Authentication,]    #添加认证
        permission_classes = [MyPremission,]           #添加权限控制
        versioning_class = Myversion   #添加版本
        def get(self,request,*args,**kwargs):
            print(request.version)#获取版本
             #当然使用request._request.get('version')也可以
            ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}
            return JsonResponse(ret,safe=True)

    models.py

    from django.db import models
    
    class UserInfo(models.Model):
        user_type_choice = (
            (1,"普通用户"),
            (2,"会员"),
        )
        user_type = models.IntegerField(choices=user_type_choice)
        username = models.CharField(max_length=32,unique=True)
        password = models.CharField(max_length=64)
    
    
    class UserToken(models.Model):
        user = models.OneToOneField(to=UserInfo)
        token = models.CharField(max_length=64)

    urls.py

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
    
        url(r'^api/v1/auth', views.AuthView.as_view()),
        url(r'^api/v1/order', views.OrderView.as_view()),
    ]

    views.py

    from django.shortcuts import  HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from rest_framework.authentication import BaseAuthentication
    from . import models
    from rest_framework import exceptions
    import hashlib
    import time
    
    
    class Authentication(BaseAuthentication):
        """
        认证类
        """
    
        def authenticate(self, request):
            token = request._request.GET.get("token")
            toke_obj = models.UserToken.objects.filter(token=token).first()
            if not toke_obj:
                raise exceptions.AuthenticationFailed("用户认证失败")
            return (toke_obj.user, toke_obj)  # 这里返回值一次给request.user,request.auth
    
        def authenticate_header(self, val):
            pass
    
    
    def md5(user):
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding="utf-8"))
        m.update(bytes(ctime,encoding="utf-8"))
        return m.hexdigest()
    
    class AuthView(APIView):
        """登陆认证"""
        def dispatch(self, request, *args, **kwargs):
            return super(AuthView, self).dispatch(request, *args, **kwargs)
    
        def get(self, request, *args, **kwargs):
            return HttpResponse('get')
    
        def post(self, request, *args, **kwargs):
    
            ret = {'code': 1000, 'msg': "登录成功"}
            try:
                user = request._request.POST.get("username")
                pwd = request._request.POST.get("password")
                obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = "用户名或密码错误"
                else:
                    token = md5(user)
                    models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
                    ret['token'] = token
    
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = "请求异常"
    
            return JsonResponse(ret)
    
    
    
    class OrderView(APIView):
        '''查看订单'''
        from utils.permissions import MyPremission
        from utils.version import Myversion
        authentication_classes = [Authentication,]    #添加认证
        permission_classes = [MyPremission,]           #添加权限控制
        versioning_class = Myversion
        def get(self,request,*args,**kwargs):
            print(request.version)
    
            ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}
            return JsonResponse(ret,safe=True)

    使用postman发送请求:http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v1,后台可获取版本。

    当然上面获取版本方式还有更为简单的获取版本方法,使用QueryParameterVersioning,其就是封装的以上过程。

    class OrderView(APIView):
        '''查看订单'''
        from utils.permissions import MyPremission
        from utils.version import Myversion
        from rest_framework.versioning import QueryParameterVersioning
        authentication_classes = [Authentication,]    #添加认证
        permission_classes = [MyPremission,]           #添加权限控制
        versioning_class = QueryParameterVersioning #该方法获取参数的key为version
        def get(self,request,*args,**kwargs):
            print(request.version)
    
            ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}
            return JsonResponse(ret,safe=True)

    当然,DRF也提供了可配置的版本,并且还能控制版本使用

    settings.py

    REST_FRAMEWORK = {#版本配置
         "DEFAULT_VERSION":'v1',               #默认的版本
         "ALLOWED_VERSIONS":['v1','v2'],       #允许的版本,这里只允许V1和v2
         "VERSION_PARAM":'version' ,            #get方式url中参数的名字 如?version=v1
    
    
    }

    使用postman验证,发送带token和版本http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v3

    结果:

    可见版本配置生效。

    2.使用url路径传递版本,如http://www.example.com/api/v1,django rest framework 当然也为我们提供了类:URLPathVersioning

    为了区分,这里新建url和view,如下:

    urls.py

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
    
        url(r'^api/v1/auth', views.AuthView.as_view()),
        url(r'^api/v1/order', views.OrderView.as_view()),
        url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view()), # 新建的url
    ]

    UserView

    class UserView(APIView):
        '''查看用户信息'''
    
        from rest_framework.versioning import URLPathVersioning
    
        versioning_class =URLPathVersioning
        def get(self,request,*args,**kwargs):
            print(request.version)  #获取版本
    
            res={"name":"wd","age":22}
            return JsonResponse(res,safe=True)

    使用postman请求:http://127.0.0.1:8000/api/v1/user,同样后台能拿到版本结果。

    三、源码剖析

    认证流程一样,请求进来,同样走APIview的dispatch的方法,请阅读注解部分:

    1.APIView类的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(
            #     request,
            #     parsers=self.get_parsers(),
            #     authenticators=self.get_authenticators(),
            #     negotiator=self.get_content_negotiator(),
            #     parser_context=parser_context
            # )
            #request(原始request,[BasicAuthentications对象,])
            #获取原生request,request._request
            #获取认证类的对象,request.authticators
            #1.封装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

    2.接着执行self.inital方法:

    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
            #2.实现认证
            self.perform_authentication(request)
            #3.权限判断
            self.check_permissions(request)
            #4.频率限制
            self.check_throttles(request)  

    3.可以看到版本控制是在认证之前,首先下执行version, scheme = self.determine_version(request, *args, **kwargs),以下是self.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)
            """
            if self.versioning_class is None:  #先判断版本类是否存在(self.versioning_class 是否为存在),不存在返回tuple,(none,none)
                return (None, None)            
            scheme = self.versioning_class()   #存在返回版本类对象
            return (scheme.determine_version(request, *args, **kwargs), scheme) #版本类存在,最后返回版本类对象的determine_version方法结果(也就是返回的版本号),和类对象,
    这也就是每个版本类必须要有的方法,用来获取版本。

    4.承接 self.determine_version方法执行完成以后,接着执行request.version, request.versioning_scheme = version, scheme,这个不用多说,无非将版本号赋值给request.version属性,版本类对象赋值给request.versioning_scheme,这也就是我们为什么能通过request.version获取版本号的原因。

    5.同认证源码一样,self.determine_version方法中使用的版本类self.versioning_class(),在全局中也有配置

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS  #版本处理类配置

    6.基于以上源码分析完成以后,下面我们来剖析下,我们示例中所使用的两个版本处理类,具体分析请看注解:

    QueryParameterVersioning(BaseVersioning)

    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.')  ## 当setting.py配置了允许的版本时候,不匹配版本返回的错误信息,可以自己定义
    
        def determine_version(self, request, *args, **kwargs):           ## 获取版本方法
            version = request.query_params.get(self.version_param, self.default_version) # 通过request.query_paras方法获取(本质request.MATE.get)
    default_version默认是version,是在settings中配置的
    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 反解析,可以通过该方法生成请求的url,后面会有示例 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

    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):    ## 同样实现determine_version方法获取版本
            version = kwargs.get(self.version_param, self.default_version) # 由于传递的版本在url的正则中,所以从kwargs中获取,self.version_param默认是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反解析,后面会有示例
            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

    这个版本类都继承了BaseVersioning:

    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION            #默默人版本配置
        allowed_versions = api_settings.ALLOWED_VERSIONS      #允许版本配置
        version_param = api_settings.VERSION_PARAM                #版本key配置
    
        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))
    四、利用版本反向生成URL

    以URLPathVersioning为例,其本质也是用的django的url反向解析方法,实现过程这里就不用过多说明,有兴趣可以自己看源码。

    1.配置url,为view取别名

    urlpatterns = [
    
        url(r'^api/v1/auth', views.AuthView.as_view()),
        url(r'^api/v1/order', views.OrderView.as_view()),
        url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"),
    ]

    2.利用reverse方法反向生成请求的url,UserView视图。

    class UserView(APIView):
        '''查看用户信息'''
    
        from rest_framework.versioning import URLPathVersioning
    
        versioning_class =URLPathVersioning
        def get(self,request,*args,**kwargs):
            print(request.version)
    
            url = request.versioning_scheme.reverse(viewname='user_view', request=request)
            #versioning_scheme已经在源码中分析过了,就是版本类实例化的对象
            print(url)
            res={"name":"wd","age":22}
            return JsonResponse(res,safe=True)

    使用postman发请求:http://127.0.0.1:8000/api/v1/user查看结果如下:

    五、总结

    对于版本控制来说,其实没必要自己去定义或自己写版本处理的类,推荐使用全局配置,以及URLPathVersioning类。

    具体配置:

    # 全局配置
     REST_FRAMEWORK = {
         "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",  #类的路径
        "DEFAULT_VERSION":'v1',               #默认的版本
        "ALLOWED_VERSIONS":['v1','v2'],       #允许的版本
       #  "VERSION_PARAM":'version'             #使用QueryParameterVersioning时候进行的配置,get请求时候传递的参数的key  
    }
    
    #单一视图
    versioning_class =URLPathVersioning
  • 相关阅读:
    【转】VS2010中 C++创建DLL图解
    [转]error: 'retainCount' is unavailable: not available in automatic reference counting mode
    [转]关于NSAutoreleasePool' is unavailable: not available in automatic reference counting mode的解决方法
    【转】 Tomcat v7.0 Server at localhost was unable to start within 45
    【转】Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If
    【转】SVN管理多个项目版本库
    【转】eclipse安装SVN插件的两种方法
    【转】MYSQL启用日志,和查看日志
    【转】Repository has not been enabled to accept revision propchanges
    【转】SVN库的迁移
  • 原文地址:https://www.cnblogs.com/wdliu/p/9119874.html
Copyright © 2011-2022 走看看