Django rest framework 之版本

Posted midworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django rest framework 之版本相关的知识,希望对你有一定的参考价值。

一、通过 QueryParameterVersioning 获取版本

通过 QueryParameterVersioning 从 get 请求中获取版本信息:

1、新建 app,名为 api,Project/urls.py

from django.contrib import admin
from django.urls import path, include
from app.views import IndexView, OrderView, UserInfo

urlpatterns = [
    path('admin/', admin.site.urls),

    path('api/', include('api.urls')),

    path('api/v1/index/', IndexView.as_view()),
    path('api/v1/order/', OrderView.as_view()),
    path('api/v1/info/', UserInfo.as_view()),
]

2、api/urls.py

from django.urls import path, re_path
from api.views import UserView

urlpatterns = [
    path('users/', UserView.as_view()),
]

3、api/views.py

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.versioning import QueryParameterVersioning

class UserView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)      # 获取版本信息

        return HttpResponse('版本信息')

4、settings.py

# 设置全局认证
REST_FRAMEWORK = 
    "DEFAULT_VERSION": 'v1',               # 默认的版本
    "ALLOWED_VERSIONS": ['v1', 'v2'],       # 允许的版本
    "VERSION_PARAM": 'version'             # GET方式url中参数的名字  ?version=xxx

5、访问:<http://127.0.0.1:8000/api/users/?version=v2>,通过 QueryParameterVersioning 从 get 请求中获取版本信息:

技术图片

当访问不存在的版本时(v3 不在 settings 中设置的允许版本范围内):

技术图片

二、通过 URLPATH 获取

在上面我们通过 QueryParameterVersioning 可以获取版本信息,但是每次以 <http://127.0.0.1:8000/api/users/?version=v3> 这种方式传参,未免太过麻烦,也不简洁美观。rest framework 推荐使用 URLPATH 来获取版本信息。

1、api/urls.py

from django.urls import path, re_path
from api.views import UserView

urlpatterns = [
    # path('users/', UserView.as_view()),
    
    # 修改如下,通过正则来匹配版本信息
    re_path('(?P<version>[v1|v2]+)/users/', UserView.as_view())
]

2、api/views.py

URLPathVersioning 可以从 URL 路径中获取版本信息,而不是获取 URL 中的参数:

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.versioning import QueryParameterVersioning
from rest_framework.versioning import URLPathVersioning


class UserView(APIView):
    # versioning_class = QueryParameterVersioning
    versioning_class = URLPathVersioning        # 添加这个

    def get(self, request, *args, **kwargs):
        print(request.version)

        return HttpResponse('版本信息')

3、访问:http://127.0.0.1:8000/api/v2/users/

技术图片

是不是比之前简洁多了,也美观多了,如果你想全局配置每个视图,也可以再 settings 中设置:

REST_FRAMEWORK = 
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",

三、反向解析 URL

一直以来我们很少用到 URL 中的 name 参数(别名),但是有时候我们与 reverse() 方法用来作反向解析,从而推断出访问的 URL,这在有些场合还是很有作用的:

1、api/uris.py

from django.urls import path, re_path
from api.views import UserView

urlpatterns = [
    # path('users/', UserView.as_view()),
    re_path('(?P<version>[v1|v2]+)/users/', UserView.as_view(), name='api_user')
]

2、api/views.py

# viewname: URL name 参数,request 对象
reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra)
class UserView(APIView):
    # versioning_class = QueryParameterVersioning
    versioning_class = URLPathVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)
        
        print(request.versioning_scheme)
        url_path = request.versioning_scheme.reverse(viewname='api_user', request=request)
        print(url_path)

        return HttpResponse('版本信息')

技术图片

四、源码分析

1、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(request,parsers=self.get_parsers(),authenticators=self.get_authenticators(),negotiator=self.get_content_negotiator(),parser_context=parser_context)
        # 第一个参数为原生的 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、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.
        # 获取版本信息以及 scheme
        version, scheme = self.determine_version(request, *args, **kwargs)
        # 将版本信息以及 scheme 封装到 request 中
        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)

3、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)
    """
    # 从 versioning_class 中获取,如果没有配置则返回两个 None
    if self.versioning_class is None:
        return (None, None)
    scheme = self.versioning_class()
    return (scheme.determine_version(request, *args, **kwargs), scheme)

4、api_settings

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    ....
    # 从配置文件中加载
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

URLPathVersioning 源码分析

1、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.

    # 提供的 URL 配置示例
    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_param 获取版本信息,如果没有则使用默认的 default_version
        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(URLPathVersioning, self).reverse(
            viewname, args, kwargs, request, format, **extra
        )

2、version_param

class BaseVersioning(object):
    default_version = api_settings.DEFAULT_VERSION      # 默认版本
    allowed_versions = api_settings.ALLOWED_VERSIONS    # 允许版本
    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__
        ))

settings 中的配置:

"DEFAULT_VERSION": 'v1',               # 默认的版本
"ALLOWED_VERSIONS": ['v1', 'v2'],       # 允许的版本
"VERSION_PARAM": 'version'             # GET方式url中参数的名字  ?version=xxx

源码流程图

技术图片


总结

  • get 请求参数中获取版本信息:QueryParameterVersioning(不推荐)
  • URLPATH 中获取版本信息(推荐),可全局配置

以上是关于Django rest framework 之版本的主要内容,如果未能解决你的问题,请参考以下文章

django rest framework之用户登录

Django之REST_FRAMEWORK 认证组件

基于Django的Rest Framework框架的序列化组件

Python之Django rest_Framework

python之Django rest_framework

Python之Django rest_Framework