一、简介
本篇文章主要用于介绍drf的版本组件(使用URLPathVersioning)的快速使用以及源码分析
二、快速使用
下面介绍def自带的版本控制组件如何使用
①在settings.py中设置默认组件为URLPathVersioning以及相关配置
# /day14/settings.py
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": None,
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
# 将VERSION_PARAM设置为version,如果不设置,drf也默认为version
"VERSION_PARAM": "version",
# 默认的版本设置为v4
"DEFAULT_VERSION": "v4",
# 允许的版本设置为v1或v2,其中默认的v4也被允许
"ALLOWED_VERSIONS": ["v1", "v2"]
}
②在views.py中编写一个简单的视图类
# api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class HomeView(APIView):
def get(self, request, *args, **kwargs):
print(request.version)
return Response("...")
③在urls.py中编写url
# day14/urls.py
from django.urls import path
from api import views
urlpatterns = [
# 这里的<str:version>中的version是根据settings中的VERSION_PARAM来确定
# 如果VERSION_PARAM不是给的默认version,则需要进行调整
path('home/<str:version>/', views.HomeView.as_view(), name="home-list"),
]
④测试接口
访问默认版本http://127.0.0.1:8000/home/v4/
访问允许版本http://127.0.0.1:8000/home/v1/
访问非法版本 http://127.0.0.1:8000/home/v3/
三、源码分析
下面从源码角度分析drf实现版本控制的流程
①找到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 = 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
与版本控制有关的代码为:
self.initial(request, *args, **kwargs)
调用了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)
与版本控制有关的代码为:
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
将determine_version的返回值分别赋给version和scheme,最后在赋值给request.version和request.versioning_scheme
其中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:
return (None, None)
scheme = self.versioning_class()
return (scheme.determine_version(request, *args, **kwargs), scheme)
首先是判断当前实例化对象的versioning_class是否为空,如果为空则返回两个None,即不做版本控制,如果有versioning_class,则将versioning_class实例化对象赋给scheme,返回versioning_class实例化对象的determine_version返回值和versioning_class实例化对象
那么versioning_class从哪里读取?其在APIView中的定义如下:
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
即从settings文件中读取,前面我们在settings文件中定义的versioning_class是URLPathVersioning,因此此时需要查看URLPathVersioning的determine_version做了什么,其源码如下:
class URLPathVersioning(BaseVersioning):
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 version is None:
version = self.default_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):
if request.version is not None:
kwargs = {} if (kwargs is None) else kwargs
kwargs[self.version_param] = request.version
return super().reverse(
viewname, args, kwargs, request, format, **extra
)
找到其中的determine_version
def determine_version(self, request, *args, **kwargs):
version = kwargs.get(self.version_param, self.default_version)
if version is None:
version = self.default_version
if not self.is_allowed_version(version):
raise exceptions.NotFound(self.invalid_version_message)
return version
将从kwargs中获取version_param和default_version,这俩值在最开始的时候定义在了settings中,分别是"version"和"v4",这里在提一下kwargs是urls传过来的参数即<str:version>,因此此时的version等于前面测试接口时的v1,v3和v4
然后如果判断此时的version是否为空,为空就把v4赋值给它
接下来将version作为参数传入is_allowed_version方法中,其源码为
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))
首先是判断allowed_versions是否为空,即有没有对版本进行限制,如果为空没有做限制即返回True,因此需要先知道allowed_versions是什么,其定义在APIView中为:
allowed_versions = api_settings.ALLOWED_VERSIONS
即去settings文件里读取ALLOWED_VERSIONS,我们定义的是v1和v2,于是返回的时候,判断此时的version是否是default_version即v4或者是否在allowed_versions中即是否是v1和v2其中一个,满足其中一个即返回True,否则返回Flase,如何回到determine_version,如果返回的是Flase,则报错,返回的是True则将此时从url获得的version返回,赋值给request.version