热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

DjangoRestFramework源码剖析(四)API版本

一、简介在我们给外部提供的API中,可会存在多个版本,不同的版本可能对应的功能不同,所以这时候版本使用就显得尤为重要,djangorestframework也为我们提供了多种版本使






一、简介

在我们给外部提供的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[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[v1|v2]+)/users/$', users_list, name='users-list'),
url(r'^(?P[v1|v2]+)/users/(?P[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[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

 



推荐阅读
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • 深入解析 UIImageView 与 UIImage 的关键细节与应用技巧
    本文深入探讨了 UIImageView 和 UIImage 的核心特性及应用技巧。首先,详细介绍了如何在 UIImageView 中实现动画效果,包括创建和配置 UIImageView 实例的具体步骤。此外,还探讨了 UIImage 的加载方式及其对性能的影响,提供了优化图像显示和内存管理的有效方法。通过实例代码和实际应用场景,帮助开发者更好地理解和掌握这两个重要类的使用技巧。 ... [详细]
  • 本文介绍了Android动画的基本概念及其主要类型。Android动画主要包括三种形式:视图动画(也称为补间动画或Tween动画),主要通过改变视图的属性来实现动态效果;帧动画,通过顺序播放一系列预定义的图像来模拟动画效果;以及属性动画,通过对对象的属性进行平滑过渡来创建更加复杂的动画效果。每种类型的动画都有其独特的应用场景和实现方式,开发者可以根据具体需求选择合适的动画类型。 ... [详细]
  • 通过优化模板消息机制,本研究提出了一种高效的信息化推送方案。该方案利用获取的访问令牌(access token)和指定的模板ID,实现了精准且快速的信息推送,显著提升了用户体验和信息传递效率。具体实现中,通过调用相关API接口,确保了消息的准确性和及时性,为用户提供更加便捷的服务。 ... [详细]
  • Spring框架入门指南:专为新手打造的详细学习笔记
    Spring框架是Java Web开发中广泛应用的轻量级应用框架,以其卓越的功能和出色的性能赢得了广大开发者的青睐。本文为初学者提供了详尽的学习指南,涵盖基础概念、核心组件及实际应用案例,帮助新手快速掌握Spring框架的核心技术与实践技巧。 ... [详细]
  • 本课程详细介绍了如何使用Python Flask框架从零开始构建鱼书应用,涵盖高级编程技巧和实战项目。通过视频教学,学员将学习到Flask的高效用法,包括数据库事务处理和书籍交易模型的实现。特别感谢AI资源网提供的课程下载支持。 ... [详细]
  • Django框架下的对象关系映射(ORM)详解
    在Django框架中,对象关系映射(ORM)技术是解决面向对象编程与关系型数据库之间不兼容问题的关键工具。通过将数据库表结构映射到Python类,ORM使得开发者能够以面向对象的方式操作数据库,从而简化了数据访问和管理的复杂性。这种技术不仅提高了代码的可读性和可维护性,还增强了应用程序的灵活性和扩展性。 ... [详细]
  • 本文深入探讨了 Python Watchdog 库的使用方法和应用场景。通过详细的代码示例,展示了如何利用 Watchdog 监控文件系统的变化,包括文件的创建、修改和删除等操作。文章不仅介绍了 Watchdog 的基本功能,还探讨了其在实际项目中的高级应用,如日志监控和自动化任务触发。读者将能够全面了解 Watchdog 的工作原理及其在不同场景下的应用技巧。 ... [详细]
  • 本文通过具体示例详细介绍了如何使用Python的wxPython库来处理鼠标拖动事件。通过一个简单的案例,展示了如何在wxPython中设置和响应鼠标拖动操作,为开发者提供了一种直观的学习方法。代码示例清晰地说明了事件绑定和处理过程,有助于读者快速掌握相关技术。 ... [详细]
  • 计算 n 叉树中各节点子树的叶节点数量分析 ... [详细]
  • Python数学模块详解与应用 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • 深入解析Python中的循环双向链表数据结构
    本文详细探讨了Python中循环双向链表的数据结构,包括其定义、特点及应用场景。文章首先介绍了循环双向链表的基本概念,随后深入分析了其核心操作,如节点的插入、删除和遍历等。最后,通过具体的Python代码示例,展示了如何高效地实现这些操作,帮助读者全面理解并掌握这一重要数据结构。 ... [详细]
  • 本文深入探讨了Python线程池的内部实现机制,作为对Apshceduler调度器研究的延伸。在先前关于Apshceduler源码分析的文章中,我们提到调度器通过`def_do_submit_`函数将任务提交到线程池。本文将进一步解析线程池的工作原理,包括任务分配、线程管理及性能优化等方面,为读者提供更全面的技术理解。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有