一、简介

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

二、基本使用

版本使用方式:

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

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

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

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

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

models.py

  1. from django.db import models
  2.  
  3. class UserInfo(models.Model):
  4. user_type_choice = (
  5. (1,"普通用户"),
  6. (2,"会员"),
  7. )
  8. user_type = models.IntegerField(choices=user_type_choice)
  9. username = models.CharField(max_length=32,unique=True)
  10. password = models.CharField(max_length=64)
  11.  
  12. class UserToken(models.Model):
  13. user = models.OneToOneField(to=UserInfo)
  14. token = models.CharField(max_length=64)

urls.py

  1. from django.conf.urls import url
  2. from django.contrib import admin
  3. from app01 import views
  4.  
  5. urlpatterns = [
  6.  
  7. url(r'^api/v1/auth', views.AuthView.as_view()),
  8. url(r'^api/v1/order', views.OrderView.as_view()),
  9. ]

views.py

  1. from django.shortcuts import HttpResponse
  2. from django.http import JsonResponse
  3. from rest_framework.views import APIView
  4. from rest_framework.authentication import BaseAuthentication
  5. from . import models
  6. from rest_framework import exceptions
  7. import hashlib
  8. import time
  9.  
  10. class Authentication(BaseAuthentication):
  11. """
  12. 认证类
  13. """
  14.  
  15. def authenticate(self, request):
  16. token = request._request.GET.get("token")
  17. toke_obj = models.UserToken.objects.filter(token=token).first()
  18. if not toke_obj:
  19. raise exceptions.AuthenticationFailed("用户认证失败")
  20. return (toke_obj.user, toke_obj) # 这里返回值一次给request.user,request.auth
  21.  
  22. def authenticate_header(self, val):
  23. pass
  24.  
  25. def md5(user):
  26. ctime = str(time.time())
  27. m = hashlib.md5(bytes(user,encoding="utf-8"))
  28. m.update(bytes(ctime,encoding="utf-8"))
  29. return m.hexdigest()
  30.  
  31. class AuthView(APIView):
  32. """登陆认证"""
  33. def dispatch(self, request, *args, **kwargs):
  34. return super(AuthView, self).dispatch(request, *args, **kwargs)
  35.  
  36. def get(self, request, *args, **kwargs):
  37. return HttpResponse('get')
  38.  
  39. def post(self, request, *args, **kwargs):
  40.  
  41. ret = {'code': 1000, 'msg': "登录成功"}
  42. try:
  43. user = request._request.POST.get("username")
  44. pwd = request._request.POST.get("password")
  45. obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
  46. if not obj:
  47. ret['code'] = 1001
  48. ret['msg'] = "用户名或密码错误"
  49. else:
  50. token = md5(user)
  51. models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
  52. ret['token'] = token
  53.  
  54. except Exception as e:
  55. ret['code'] = 1002
  56. ret['msg'] = "请求异常"
  57.  
  58. return JsonResponse(ret)
  59.  
  60. class OrderView(APIView):
  61. '''查看订单'''
  62. from utils.permissions import MyPremission
  63. from utils.version import Myversion
  64. authentication_classes = [Authentication,] #添加认证
  65. permission_classes = [MyPremission,] #添加权限控制
  66. versioning_class = Myversion
  67. def get(self,request,*args,**kwargs):
  68. print(request.version)
  69.  
  70. ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}
  71. return JsonResponse(ret,safe=True)

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

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

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

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

settings.py

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

使用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

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

UserView

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

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

三、源码剖析

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

1.APIView类的dispatch源码:

  1. def dispatch(self, request, *args, **kwargs):
  2. """
  3. `.dispatch()` is pretty much the same as Django's regular dispatch,
  4. but with extra hooks for startup, finalize, and exception handling.
  5. """
  6. self.args = args
  7. self.kwargs = kwargs
  8. #对原始request进行加工,丰富了一些功能
  9. #Request(
  10. # request,
  11. # parsers=self.get_parsers(),
  12. # authenticators=self.get_authenticators(),
  13. # negotiator=self.get_content_negotiator(),
  14. # parser_context=parser_context
  15. # )
  16. #request(原始request,[BasicAuthentications对象,])
  17. #获取原生request,request._request
  18. #获取认证类的对象,request.authticators
  19. #1.封装request
  20. request = self.initialize_request(request, *args, **kwargs)
  21. self.request = request
  22. self.headers = self.default_response_headers # deprecate?
  23.  
  24. try:
  25. self.initial(request, *args, **kwargs)
  26.  
  27. # Get the appropriate handler method
  28. if request.method.lower() in self.http_method_names:
  29. handler = getattr(self, request.method.lower(),
  30. self.http_method_not_allowed)
  31. else:
  32. handler = self.http_method_not_allowed
  33.  
  34. response = handler(request, *args, **kwargs)
  35.  
  36. except Exception as exc:
  37. response = self.handle_exception(exc)
  38.  
  39. self.response = self.finalize_response(request, response, *args, **kwargs)
  40. return self.response

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

  1. def initial(self, request, *args, **kwargs):
  2. """
  3. Runs anything that needs to occur prior to calling the method handler.
  4. """
  5. self.format_kwarg = self.get_format_suffix(**kwargs)
  6.  
  7. # Perform content negotiation and store the accepted info on the request
  8. neg = self.perform_content_negotiation(request)
  9. request.accepted_renderer, request.accepted_media_type = neg
  10.  
  11. # Determine the API version, if versioning is in use.
  12. ####版本控制
  13. version, scheme = self.determine_version(request, *args, **kwargs)
  14. request.version, request.versioning_scheme = version, scheme
  15.  
  16. # Ensure that the incoming request is permitted
  17. #2.实现认证
  18. self.perform_authentication(request)
  19. #3.权限判断
  20. self.check_permissions(request)
  21. #4.频率限制
  22. self.check_throttles(request)

3.可以看到版本控制是在认证之前,首先下执行version, scheme = self.determine_version(request, *args, **kwargs),以下是self.determine_version源码:

  1. def determine_version(self, request, *args, **kwargs):
  2. """
  3. If versioning is being used, then determine any API version for the
  4. incoming request. Returns a two-tuple of (version, versioning_scheme)
  5. """
  6. if self.versioning_class is None: #先判断版本类是否存在(self.versioning_class 是否为存在),不存在返回tuple,(none,none)
  7. return (None, None)
  8. scheme = self.versioning_class() #存在返回版本类对象
  9. 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(),在全局中也有配置

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

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

QueryParameterVersioning(BaseVersioning)

  1. class QueryParameterVersioning(BaseVersioning):
  2. """
  3. GET /something/?version=0.1 HTTP/1.1
  4. Host: example.com
  5. Accept: application/json
  6. """
  7. invalid_version_message = _('Invalid version in query parameter.') ## 当setting.py配置了允许的版本时候,不匹配版本返回的错误信息,可以自己定义
  8.  
  9. def determine_version(self, request, *args, **kwargs): ## 获取版本方法
  10. version = request.query_params.get(self.version_param, self.default_version) # 通过request.query_paras方法获取(本质request.MATE.get),
    default_version默认是version,是在settings中配置的
  11. if not self.is_allowed_version(version): #不允许的版本抛出异常
  12. raise exceptions.NotFound(self.invalid_version_message)
  13. return version #无异常则返回版本号
  14.  
  15. def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): #url 反解析,可以通过该方法生成请求的url,后面会有示例
  16. url = super(QueryParameterVersioning, self).reverse(
  17. viewname, args, kwargs, request, format, **extra
  18. )
  19. if request.version is not None:
  20. return replace_query_param(url, self.version_param, request.version)
  21. return url

URLPathVersioning

  1. class URLPathVersioning(BaseVersioning):
  2. """
  3. To the client this is the same style as `NamespaceVersioning`.
  4. The difference is in the backend - this implementation uses
  5. Django's URL keyword arguments to determine the version.
  6.  
  7. An example URL conf for two views that accept two different versions.
  8.  
  9. urlpatterns = [
  10. url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
  11. url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
  12. ]
  13.  
  14. GET /1.0/something/ HTTP/1.1
  15. Host: example.com
  16. Accept: application/json
  17. """
  18. invalid_version_message = _('Invalid version in URL path.') # 不允许的版本信息,可定制
  19.  
  20. def determine_version(self, request, *args, **kwargs): ## 同样实现determine_version方法获取版本
  21. version = kwargs.get(self.version_param, self.default_version) # 由于传递的版本在url的正则中,所以从kwargs中获取,self.version_param默认是version
  22. if not self.is_allowed_version(version):
  23. raise exceptions.NotFound(self.invalid_version_message) # 没获取到,抛出异常
  24. return version # 正常获取,返回版本号
  25.  
  26. def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): # url反解析,后面会有示例
  27. if request.version is not None:
  28. kwargs = {} if (kwargs is None) else kwargs
  29. kwargs[self.version_param] = request.version
  30.  
  31. return super(URLPathVersioning, self).reverse(
  32. viewname, args, kwargs, request, format, **extra

这个版本类都继承了BaseVersioning:

  1. class BaseVersioning(object):
  2. default_version = api_settings.DEFAULT_VERSION #默默人版本配置
  3. allowed_versions = api_settings.ALLOWED_VERSIONS      #允许版本配置
  4. version_param = api_settings.VERSION_PARAM #版本key配置
  5.  
  6. def determine_version(self, request, *args, **kwargs):
  7. msg = '{cls}.determine_version() must be implemented.'
  8. raise NotImplementedError(msg.format(
  9. cls=self.__class__.__name__
  10. ))
  11.  
  12. def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
  13. return _reverse(viewname, args, kwargs, request, format, **extra)
  14.  
  15. def is_allowed_version(self, version):
  16. if not self.allowed_versions:
  17. return True
  18. return ((version is not None and version == self.default_version) or
  19. (version in self.allowed_versions))
四、利用版本反向生成URL

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

1.配置url,为view取别名

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

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

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

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

五、总结

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

具体配置:

  1. # 全局配置
  2. REST_FRAMEWORK = {
  3. "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #类的路径
  4. "DEFAULT_VERSION":'v1', #默认的版本
  5. "ALLOWED_VERSIONS":['v1','v2'], #允许的版本
  6. # "VERSION_PARAM":'version' #使用QueryParameterVersioning时候进行的配置,get请求时候传递的参数的key
  7. }
  8.  
  9. #单一视图
  10. versioning_class =URLPathVersioning

Django Rest Framework源码剖析(四)-----API版本的更多相关文章

  1. Django Rest Framework源码剖析(八)-----视图与路由

    一.简介 django rest framework 给我们带来了很多组件,除了认证.权限.序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使 ...

  2. Django Rest Framework源码剖析(三)-----频率控制

    一.简介 承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡.提高服务器配置.通过 ...

  3. Django Rest Framework源码剖析(七)-----分页

    一.简介 分页对于大多数网站来说是必不可少的,那你使用restful架构时候,你可以从后台获取数据,在前端利用利用框架或自定义分页,这是一种解决方案.当然django rest framework提供 ...

  4. Django Rest Framework源码剖析(六)-----序列化(serializers)

    一.简介 django rest framework 中的序列化组件,可以说是其核心组件,也是我们平时使用最多的组件,它不仅仅有序列化功能,更提供了数据验证的功能(与django中的form类似). ...

  5. Django Rest Framework源码剖析(五)-----解析器

    一.简介 解析器顾名思义就是对请求体进行解析.为什么要有解析器?原因很简单,当后台和前端进行交互的时候数据类型不一定都是表单数据或者json,当然也有其他类型的数据格式,比如xml,所以需要解析这类数 ...

  6. Django Rest Framework源码剖析(二)-----权限

    一.简介 在上一篇博客中已经介绍了django rest framework 对于认证的源码流程,以及实现过程,当用户经过认证之后下一步就是涉及到权限的问题.比如订单的业务只能VIP才能查看,所以这时 ...

  7. Django REST framework 源码剖析

    前言 Django REST framework is a powerful and flexible toolkit for building Web APIs. 本文由浅入深的引入Django R ...

  8. Django Rest Framework源码剖析(一)-----认证

    一.简介 Django REST Framework(简称DRF),是一个用于构建Web API的强大且灵活的工具包. 先说说REST:REST是一种Web API设计标准,是目前比较成熟的一套互联网 ...

  9. 跨站请求伪造(csrf),django的settings源码剖析,django的auth模块

    目录 一.跨站请求伪造(csrf) 1. 什么是csrf 2. 钓鱼网站原理 3. 如何解决csrf (1)思路: (2)实现方法 (3)实现的具体代码 3. csrf相关的装饰器 (1)csrf_p ...

随机推荐

  1. npm install、npm init、npm update、npm uninstall和package.json

    npm install 安装本地包 npm install <package_name>:这个命令将在当前目录中创建node_modules目录(如果尚不存在),并将该软件包下载到该目录. ...

  2. 快速了解Vuex

    提要:提起react就会想起其应用最广泛的redux状态管理工具,vue中的官方推荐的状态管理工具就是Vuex. 看到同事在鼓捣Vuex的东西,前面项目完成后也没有好好总结一下Vuex的知识,所有就再 ...

  3. MySQL 性能监控4大指标——第一部分

    [编者按]本文作者为 John Matson,主要介绍 mysql 性能监控应该关注的4大指标. 第一部分将详细介绍前两个指标: 查询吞吐量与查询执行性能.文章系国内 ITOM 管理平台 OneAPM ...

  4. LeetCode题解之Largest Number

    1.题目描述 2. 将整数值转换为string  ,然后排序. 3.代码 string largestNumber(vector<int>& nums) { vector<s ...

  5. linux下搭建hexo环境

    最近对搭建个人博客比较感兴趣,但是刚搭建好next主题基本博客,电脑就坏了,借了一台电脑继续搞,不想在他电脑中弄太多环境,所以我准备在自己电脑的服务器上搭建hexo环境 服务器环境: (1)cento ...

  6. FTP上传下载类

    public class FtpOperation { public static void UploadFile(FileInfo fileinfo, string targetDir, strin ...

  7. IntelliJ IDEA2018激活方法

    前言: IntelliJ IDEA2018请在官网下载:https://www.jetbrains.com/idea/ 一.license server激活 输入http://idea.jialeen ...

  8. sysdate()简单用法

    环境: create table rq (xm varchar2(10),age number,zw varchar(10),rzrq date);insert into rq values ('小崔 ...

  9. Java 中File类的createNewFile()与createTempFile(), delete和deleteOnExit区别

    1. Java 中File类的createNewFile()与createTempFile()的区别 最近,在看代码时看到了一个方法, File.createTempFile() ,由此联想到File ...

  10. WPScan扫描Wordpress漏洞

    一.什么是Wpscan?什么是Wordpres? 1.Wpscan WPScan是一个扫描WordPress漏洞的黑盒子扫描器,可以扫描出wordpress的版本,主题,插件,后台用户以及爆破后台用户 ...