DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

  • 第二次学drf

    • drfCBV
    • 认证
    • 权限
    • 限流
    • 版本
      • 准备工作
      • 快速使用
        • URL中传递参数
        • 路由参数中传递(*)
        • Accept请求头
      • 反向解析
      • 源码分析
    • 解析器
    • 元类
    • 序列化使用
    • 序列化源码
    • 验证&源码
    • 序列化+验证
    • Serializer案例
    • Serializer总结
    • 分页
    • 视图+路由+过滤
    • 练习+跨域问题
    • 博客练习
    • 源码分析汇总
  • 温故知新

  • flask

  • 后端
  • 第二次学drf
DC
2023-11-06
目录

版本

通过路由中的name进行反向解析,drf跟Django中的使用并无不同!
但加上版本后,drf的版本类里也有个reverse反向解析的函数,它对Django的reverse进行了一点加工.
根据传递版本的三种方式,跟以往的方式进行反向解析相比,结果有些许的改变.
  首先一点,就是结果中不仅有路径,还有协议、主机名等.
  - 版本号在URL中传递参数 运用版本类的reverse 版本号会自动通过 ?version=v1 的方式拼接到反向解析的结果后面
  - 版本号在路由参数中传递 运用版本类的reverse进行反向解析时 路由中关于版本的动态参数可以不传,会自动帮忙写
  - 版本号在请求头中传递 暂时没发现啥不同,后续发现了再补充.
1
2
3
4
5
6
7

image-20231106135115829


前言

在请求中携带版本号, 便于后续API的更新迭代!
后端跟前端(app‘ios/安卓’、小程序、网页)配合开发,前端向后端api发送请求,后端向前端返回json格式的数据.
同一个api会随着更新迭代有版本的不同,在开发过程中,版本不是一下子全部切换过去的,肯定会有两个版本同时存在的情况.
比如:小程序开发的快,网页开发的慢.. 同一个api都要对其进行支持,所以会有版本区分.

# 准备工作

准备工作: 创建指定版本的Django项目 - 安装djangorestframework - drf纯净版
解决认证组件匿名用户导致的报错,不用翻笔记的小技巧: 
  1> - APIView类里有很多默认的配置,点击跳转api_settings,可以看到配置名‘REST_FRAMEWORK’
     - 匿名用户的key叫啥?回忆下,认证组件是放到Request对象里的.
       APIView - dispatch - initialize_request - Request 或者 from rest_framework.request import Request
  2> 认证权限限流的全局和局部配置的配置名可以去APIView类里找
     比如: authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
          (等号左边是局部的,右边是全局的!!!)
     另外,authentication_classes结尾是es,表示是复数,那么其值是多个,局部和全局都用[];
         若结尾没有es,局部和全局的其值就是一个字符串!
1
2
3
4
5
6
7
8
9
10

# 快速使用

传递版本的三种方式!
1> url的GET参数中传递 2>路由中传递参数 3>请求头中传递参数.

# URL中传递参数

from rest_framework.versioning import QueryParameterVersioning 此案例使用的是局部配置.

比如: http://127.0.0.1:8000/home/?version=2
后端会读取url中GET参数version的值,并将其赋值给 request.version!

url.py 根路由

from django.urls import path
from api import views

urlpatterns = [
    path('home/', views.HomeView.as_view(), name='hh'),
]
1
2
3
4
5
6

settings.py

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    "VERSION_PARAM": "xx",     # 版本参数名
    # "DEFAULT_VERSION": 0.1,  # 默认版本号
    # "ALLOWED_VERSIONS": ["2", "3", "4"],,  # 允许的版本号
    # "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.QueryParameterVersioning"
}

# 已配置DEFAULT_VERSION和ALLOWED_VERSIONS
# url中没有GET参数xx,其值就是DEFAULT_VERSION;url中有GET参数xx,其值只能是ALLOWED_VERSIONS中的项.
# eg: 但凡url中有get参数xx 127.0.0.1:8000/home/?xx=.. xx的值只能是2、3、4
#     - 127.0.0.1:8000/home/?xx=5 127.0.0.1:8000/home/?xx=0.1 
#       报错 {"detail": "Invalid version in query parameter."}
#     - 127.0.0.1:8000/home/ 不会报错, request.version值为0.1
#       127.0.0.1:8000/home/?xx=2 不会报错,request.version值为2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意: 读取url中GET参数version是默认的.. 可在全局进行配置读取url中的哪个参数, 但读取的值依旧会赋值给request.version..

api/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning
from django.urls import reverse

      
class HomeView(APIView):
    # 配置文件 VERSION_PARAM
    # 1> http://127.0.0.1:8000/home/?xx=2 --> request.version 值为 2
    # 2> settings.py中是否配置了"DEFAULT_VERSION": 0.1
    #    已配置 http://127.0.0.1:8000/home/  --> request.version 值为 0.1
    #    未配置 http://127.0.0.1:8000/home/  --> request.version 值为 None
    versioning_class = QueryParameterVersioning

    def get(self, request):
        self.dispatch
        print(request.version, type(request.version))  # 版本号
        print(request.versioning_scheme)               # 版本类的类实例化对象
        drf_url = request.versioning_scheme.reverse('hh', request=request)
        django_url = reverse('hh')
        print("drf中反向生成url:", drf_url)
        print("Django中反向生成url:", django_url)

        return Response("...")

""" 浏览器输入网址: http://127.0.0.1:8000/home/?xx=3
3 <class 'str'>
<rest_framework.versioning.QueryParameterVersioning object at 0x7fd82de49ee0>
drf中反向生成url: http://127.0.0.1:8000/home/?xx=3
Django中反向生成url: /home/
"""

简单说一下,url中传递参数的反向解析,关键源码看这三行就理解了.
  - version, scheme = self.determine_version(request, *args, **kwargs)
  - request.version, request.versioning_scheme = version, scheme 
  - 在QueryParameterVersioning版本类中的reserve函数会
    return replace_query_param(url, self.version_param, request.version)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 路由参数中传递(*)

"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning" 此案例使用的是全局配置.

url.py 根路由

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

urlpatterns = [
    # ★ 等同于 re_path(r'^api/(?P<version>\w+)/home2/$', views.HomeView2.as_view(), name="hh2") 它两效果一样!
    path('api/<str:version>/home/', views.HomeView1.as_view(), name='hh'),
]

# 注意:
# 1> 不建议用无名分组,因为使用无名分组
#    在使用drf的反向解析request.versioning_scheme.reverse('hh3', request=request)时会报错!!找不到version参数.
# 2> 上述的两种路由写法,路由中的参数 都会传递给视图函数的形参 **kwargs !!
#    kwargs中可以取到,在drf中还可以通过self.kwargs取.
# 3> 格外提醒一点,path是完完全全的一模一样才能匹配上!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14

settings.py

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    # "VERSION_PARAM": "version",  # 其实该值不用设置,默认就是version,而且该值应该和路由中设置的参数的名字一样!!
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning"
}
1
2
3
4
5

api/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
# from rest_framework.versioning import URLPathVersioning
from django.urls import reverse


class HomeView1(APIView):
    # versioning_class = URLPathVersioning

    """
    直接写参数version,可以直接拿到路由中传递的版本信息,但通常为了接受路由中的多个参数,我们都会写*args,**kwargs.
    Q: ★那我们直接从kwagrs字典中取键version对应的值就行了啊,是不是不需要版本类啦?
    A: ★使用drf的版本,它会将版本号封装到request中,这样我们在其他视图类中也可以通过request取到!
      而且使用drf的版本后,进行反向解析时,会更方便.
    """
    def get(self, request, *args, **kwargs):
        print(args, kwargs)
        print(request.version, type(request.version))
        print('drf >>:', request.versioning_scheme.reverse('hh', request=request))
        print('django >>:', reverse('hh', kwargs=kwargs))

        return Response("...")

    """ 浏览器输入: http://127.0.0.1:8000/api/2/home/
    () {'version': '2'}
    2 <class 'str'>
    drf >>: http://127.0.0.1:8000/api/2/home/
    django >>: /api/2/home/
    """
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Accept请求头

from rest_framework.versioning import AcceptHeaderVersioning 此案例使用的是局部配置!!

url.py 根路由

from django.urls import path
from api import views

urlpatterns = [
    path('order/', views.OrderView.as_view(), name='yy1'),
]
1
2
3
4
5
6

api/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import AcceptHeaderVersioning
from django.urls import reverse


class OrderView(APIView):
    versioning_class = AcceptHeaderVersioning

    # PostMan中GET请求输入 http://127.0.0.1:8000/order/
    # 并设置请求头, Accept --> application/json;version=v1
    def get(self, request, *args, **kwargs):
        print(args, kwargs)     # () {}
        print(request.version)  # v1

        print(reverse('yy1'))   # /order/
        print(request.versioning_scheme.reverse('yy1', request=request))  # http://127.0.0.1:8000/order/

        return Response('Hello World')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 反向解析

在每个版本处理的类中还定义了reverse方法, 它是用来反向生成URL并携带相关的的版本信息用的!

简单来说, 使用Django的url反向解析, 需传递参数, 那么drf的url反向解析也需要传递..

首先明白一点, url的反向解析跟访问的哪个路由没有关系, (意思是我在路由A对应的视图函数中可以url解析B路由!!)
1> QueryParameterVersioning的版本参数是在 url的GET参数中 的 (?/version=v1)
     So,无论是Django还是drf, 路由中有多少个参数反向解析时就传多少个. drf的url反向解析, 版本信息会自动 ?version=v1添加
2> URLPathVersioning的版本参数时在 路由中 的 (path('index/<int:pk>/<str:version>/',..,..)
     So,Django的url反向解析路由中有多少个就传多少个; drf的url反向解析, version参数会自动添加, 可以不传.其余的都需要传.
3> AcceptHeaderVersioning的版本参数在Accept请求头里, So, 情况跟QueryParameterVersioning一样!!

url.py 根路由

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

urlpatterns = [
    # 版本号在url中传递
    # 注意:name为hh1和hh2的路由相应的视图类都是HomeView.
    path('home/', views.HomeView.as_view(), name='hh1'),
    path('home/<int:pk>/', views.HomeView.as_view(), name='hh2'),

    # 版本号在路由参数中传递
    path('index/<int:pk>/<str:version>/', views.IndexView.as_view(), name='ww1'),
  
    # 版本号在请求头中传递
    path('order/<int:pk>/', views.OrderView.as_view(), name='yy1'),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

api/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning, AcceptHeaderVersioning
from django.urls import reverse


class HomeView(APIView):
    versioning_class = QueryParameterVersioning

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

        
        # !注意哦,使用路由反向解析,跟当前访问哪个api是无关的. 
        # 根据下方的实验,可以证明 在当前匹配的路由是可以反向解析其他路由的!!
        # http://127.0.0.1:8000/home/?version=v1 匹配上的是 name为hh1的路由 “request.resolver_match”的值中表明了.
        # http://127.0.0.1:8000/home/2/?version=v1 匹配上的是 name为hh2的路由
        print()
        print("=== 当前访问网址:", request.get_full_path())
        print("django_hh1 >>:", reverse('hh1'))
        print("django_hh2 >>:", reverse('hh2', kwargs={'pk': 11}))

        print("drf_hh1 >>:", request.versioning_scheme.reverse('hh1', request=request))
        print("drf_hh2 >>:", request.versioning_scheme.reverse('hh2', kwargs={'pk': 11}, request=request))
        print("drf_hh2 >>:", request.versioning_scheme.reverse('hh2', args=(11,), request=request))

        return Response('Hello World')
            
""" 浏览器输入: http://127.0.0.1:8000/home/?version=v1
ResolverMatch(func=api.views.HomeView, args=(), kwargs={}, url_name=hh1, app_names=[], namespaces=[], route=home/)
() {}
v1 <class 'str'>

=== 当前访问网址: /home/?version=v1
django_hh1 >>: /home/
django_hh2 >>: /home/11/
drf_hh1 >>: http://127.0.0.1:8000/home/?version=v1
drf_hh2 >>: http://127.0.0.1:8000/home/11/?version=v1
drf_hh2 >>: http://127.0.0.1:8000/home/11/?version=v1
"""

"""浏览器输入: http://127.0.0.1:8000/home/2/?version=v1
ResolverMatch(func=api.views.HomeView, args=(), kwargs={'pk': 2}, url_name=hh2, app_names=[], namespaces=[], route=home/<int:pk>/)
() {'pk': 2}
v1 <class 'str'>

=== 当前访问网址: /home/2/?version=v1
django_hh1 >>: /home/
django_hh2 >>: /home/11/
drf_hh1 >>: http://127.0.0.1:8000/home/?version=v1
drf_hh2 >>: http://127.0.0.1:8000/home/11/?version=v1
drf_hh2 >>: http://127.0.0.1:8000/home/11/?version=v1
"""


class IndexView(APIView):
    versioning_class = URLPathVersioning

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

        print(reverse('ww1', kwargs=kwargs))
        print(request.versioning_scheme.reverse('ww1', kwargs=kwargs, request=request))
        # -- 此处就证明了version参数可以不传. 不传使用的是默认的.
        print(request.versioning_scheme.reverse('ww1', kwargs={'pk': 11}, request=request))

        return Response('Hello World')
      
"""浏览器输入: http://127.0.0.1:8000/index/1/v1/
ResolverMatch(func=api.views.IndexView, args=(), kwargs={'pk': 1, 'version': 'v1'}, url_name=ww1, app_names=[], namespaces=[], route=index/<int:pk>/<str:version>/)
() {'pk': 1, 'version': 'v1'}
v1 <class 'str'>

=== 当前访问网址: /index/1/v1/
django_ww1 >>: /index/1/v1/
drf_ww1 >>: http://127.0.0.1:8000/index/1/v1/
drf_ww1 >>: http://127.0.0.1:8000/index/11/v1/
"""
      
      
class OrderView(APIView):
    versioning_class = AcceptHeaderVersioning

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

        print()
        print("=== 当前访问网址:", request.get_full_path())
        print("django_yy1 >>:", reverse('yy1', kwargs={"pk": 98}))
        print("drf_yy1 >>:", request.versioning_scheme.reverse('yy1', kwargs={"pk": 98}, request=request))

        return Response('Hello World')
      
"""PostMan GET请求,http://127.0.0.1:8000/order/98/,并添加请求头Accept的值为application/json;version=v1
() {'pk': 98}
v1

=== 当前访问网址: /order/98/
django_yy1 >>: /order/98/
drf_yy1 >>: http://127.0.0.1:8000/order/98/
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

# 源码分析

此处源码分析的过程是以版本类 QueryParameterVersioning 为例的!!

版本类URLPathVersioning、AcceptHeaderVersioning 的执行流程都差不多..
最大的不同就是怎么取值的, 看各自类中的 determine_version 函数.

  • QueryParameterVersioning --> version = request.query_params.get(self.version_param, self.default_version)
  • URLPathVersioning --> version = kwargs.get(self.version_param, self.default_version)
  • AcceptHeaderVersioning --> version = media_type.params.get(self.version_param, self.default_version)

image-20230814153012318

# -- 自己项目中的视图类

class HomeView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request):
        print(request.version)
        return Response("...")
      
     

# -- rest_framework/views.py

class APIView(View):
    # api_settings.DEFAULT_VERSIONING_CLASS drf对其设置的默认值是None.
    # 再次敲黑板, versioning_class的查找顺序: 视图类中的局部配置 - 项目配置文件中的全局配置 - drf配置文件中默认的
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    
    def initial(self, request, *args, **kwargs):
        # 版本控制
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme  # 版本号, 《版本类的类实例化对象》

        self.perform_authentication(request)  # 认证
        self.check_permissions(request)       # 权限
        self.check_throttles(request)         # 限流
        
        
    def determine_version(self, request, *args, **kwargs):
        if self.versioning_class is None:
            return (None, None)
        # 版本类的类实例化对象,此处是 QueryParameterVersioning()
        # 条件反射/看到类实例化就要想到,看其__init__和父类的__init__,看源码,没有做任何初始化的操作!
        scheme = self.versioning_class()
        # 提醒: scheme.determine_version(request, *args, **kwargs) *args, **kwargs 是在路由中传递的参数
        # eg:path("user/<int:id>/",views.UserView.as_view())
        return (scheme.determine_version(request, *args, **kwargs), scheme)
      
      
      
# -- rest_framework/versioning.py 

class BaseVersioning:
    default_version = api_settings.DEFAULT_VERSION    # 默认版本号 drf配置文件中DEFAULT_VERSION默认值为None
    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__
        ))

    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
        # 理解下该逻辑. 
        # 当全局配置了VERSION_PARAM='xx'、DEFAULT_VERSION、ALLOWED_VERSIONS
        # url中没有GET参数xx,其值就是DEFAULT_VERSION;url中有GET参数xx,其值只能是ALLOWED_VERSIONS中的项.
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
      
      
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.')

    def determine_version(self, request, *args, **kwargs):
        # request.query_params.get 从url中取值
        # self.version_param url中的GET版本参数,self.default_version默认版本值 看父类BaseVersioning
        # 再次提醒查找顺序: 因为该self不是视图类的实例化对象,所以此处的顺序是先项目配置文件再drf配置文件.
        # 分析结果: version要么是传入的,要么是默认的.
        version = request.query_params.get(self.version_param, self.default_version)  # url中没传就用默认的
        # self.is_allowed_version(version) 判断该版本号是否合法!!
        if not self.is_allowed_version(version):
            # 该异常最终会在类APIView的dispatch中被捕获到.然后返回.
            raise exceptions.NotFound(self.invalid_version_message)
        return version

    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        # 调用父类BaseVersioning中的reverse
        # 本质就是使用 Django中的reverse,根据name反向生成url!
        url = super().reverse(
            viewname, args, kwargs, request, format, **extra
        )
        if request.version is not None:
            # ★不细看replace_query_param啦,简单来说就是在反向生成的url后面加上 ?version=2 即反向生成的url携带版本号!
            return replace_query_param(url, self.version_param, request.version)
        return url
      
  
  
# -- rest_framework/reverse.py 

def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
    if format is not None:
        kwargs = kwargs or {}
        kwargs['format'] = format
    # 本质就是调用Django中的reverse!! from django.urls import reverse as django_reverse
    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
    if request:
        return request.build_absolute_uri(url)
    return url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

限流
解析器

← 限流 解析器→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式