zoukankan      html  css  js  c++  java
  • 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 请求中获取版本信息:

    1561868254061

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

    1561868346781

    二、通过 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/

    1561868877799

    是不是比之前简洁多了,也美观多了,如果你想全局配置每个视图,也可以再 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('版本信息')
    
    

    1561869673743

    四、源码分析

    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
    
    

    源码流程图

    1561871599685


    总结

    • get 请求参数中获取版本信息:QueryParameterVersioning(不推荐)
    • URLPATH 中获取版本信息(推荐),可全局配置
  • 相关阅读:
    递归 例子 c
    Static和extern关键字 c
    typedef的作用
    预编译指令包括:宏定义;条件编译;文件包含(就是include)
    枚举 c
    结构体 可以由多个不同类型的数据构成
    变量类型 c
    指针类型:非常重要 c
    设计模式学习--原型模式
    设计模式学习--工厂方法模式
  • 原文地址:https://www.cnblogs.com/midworld/p/11380117.html
Copyright © 2011-2022 走看看