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

    • Postman和restful
    • APIView
      • cbv的View类源码**
      • APIView执行流程***
        • 写个简单接口
        • 项目中模块的导入
        • 查询所有图书
        • 源码分析
        • 回顾装饰器原理
        • APIView三件事
        • 自定义异常
      • Request类分析***
        • 拦截点号运算
        • 源码分析
        • 验证request.data!!
    • Serializer
    • ModelSerializer
    • drf的请求与响应
    • drf的视图组件
    • drf的路由组件
    • 认证权限频率
    • 过滤排序分页异常
    • 源码分析
    • jwt
    • 大回顾
    • 零散的知识点
  • 第二次学drf

  • 温故知新

  • flask

  • 后端
  • 第一次学drf
DC
2023-10-19
目录

APIView

# cbv的View类源码**

在 Django/路由和视图层.md/CBV使用及源码 的地方进行过详细的阐述!!

此处简单进行回顾:

Ps: 上图为定位到源码所在位置的技巧!!

path('test/', views.test),
path('index/', IndexView.as_view()),  # -- 该处是类调用方法的典型案例 

1> 当请求来了,会在路由层匹配路由,匹配成功后会执行对应的路由函数!! 
   详细点说,会将request当作参数传进去. request -- <WSGIRequest: GET '/index/'>
       FBV -- views.test(request); CBV -- IndexView.as_view()(request).
2> views.test指向的是视图层test函数的内存地址;同理,IndexView.as_view()也应该指向某一函数的内存地址!
3> 分析源码,执行IndexView.as_view()后,返回的是View类中类方法as_view的内层函数view的内存地址,
   So,路由匹配成功后, CBV -- view(request,*args,**kwargs)
   !!!强调 匹配成功后,会将路由中的参数给到 def view(request,*args,**kwargs)里
4> view(request,*args,**kwargs)的内部执行了self.dispatch(request,*args,**kwargs) 
   即通过调度(dispatch)分发请求
   根据查找规则,给dispatch是类View的dispatch!
5> 在dispatch函数内部使用了反射!!通过反射去拿视图类中对应请求方法的内存地址!
   若是get请求,相当于执行了IndexView视图类中的get方法! post请求同理..
   >> 代码执行流程: dispatch根据用户不同的请求方式去视图类中找到对应的方法,然后执行!
    def dispatch(self, request, *args, **kwargs):
        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
        return handler(request, *args, **kwargs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# APIView执行流程***

# 写个简单接口

利用apiview+Response写个查询所有图书的接口

# 项目中模块的导入

补充: 浅谈项目中模块的导入

-- 导模块就从两个位置开始导
1> 内置的和第三方的都在python解释器里, import rest_framework
   (python解释器的路径已经加到环境变量里啦!)
2> 自己写的
    绝对导入(从环境变量sys.path里有的路径开始导) from app01.models import Book
    相对导入(相对当前文件所在的路径) from .models import Book
Ps: Django好像不允许将app加到sys.path环境变量中..一般我们也不会这样做.
1
2
3
4
5
6
7
# 查询所有图书

JsonResponse的源码分析 详看 Django/路由和视图层.md/JsonResponse对象

使用Response
1>若用浏览器访问,展示好看的页面
需要注册rest_framework,否则报错缺少rest_framework/api.html,这个页面是app-drf提供的
2> 若用postman访问,直接返回Json串! 无需注册rest_framework

from django.urls import path, re_path
from django.contrib import admin
from app01.views import BookView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', BookView.as_view()),
]


# --- # --- # --- #


from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()


# --- # --- # --- #


from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book


# -- drf的APIView继承了Django的view
class BookView(APIView):
    # -- 查询所有图书
    def get(self, request):
        book_list = Book.objects.all()
        l = []
        for book in book_list:
            l.append({'name': book.name, 'price': book.price})
        # -- JsonResponse默认是传字典的,要传列表的话,得设置参数safe=False
        # return JsonResponse(l, safe=False)
        return Response(l)
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

# 源码分析

APIView通过super()使用的是View里的as_view; APIView重写了View里的dispatch方法!!

# 回顾装饰器原理
# -- 回顾下装饰器的原理. @auth语法糖的本质就是 test=auth(test)
def auth(func):
    def inner(*args, **kwargs):
        print('装饰器开始啦!')
        res = func(*args, **kwargs)
        return res
    return inner

@auth
def test():
    print('hello')

def test_1():
    print('hello')

test()
print('-' * 10)
test_1 = auth(test_1)
test_1()

"""
装饰器开始啦!
hello
----------
装饰器开始啦!
hello
"""
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
# APIView三件事

看源码不是说每行代码都要看懂,看懂其中关键的就可以啦!!!
包装新的request、三大认证、处理异常.

Ps: 图中为源码查看前进后退的技巧. 其实有快捷键 option + command + ⬅️/➡️

★ 狠狠的记住APIView干了三件事!
     1> 把老的request对象包装成了新的request对象;
老的 request、type(request)
<WSGIRequest: GET '/index/'> <class 'django.core.handlers.wsgi.WSGIRequest'>
drf的 request、type(request)
<rest_framework.request.Request: GET '/books/'> <class 'rest_framework.request.Request'>
     2> 在执行视图类中的方法之前,执行了三大认证; 别急,后面会进行详细阐述.
     3> 处理了全局异常 (包括三大认证和视图类中的方法)! 若有异常会捕获并处理.

path('books/', BookView.as_view()),

1> 请求来啦,路由匹配成功, BookView.as_view()(request) -- APIView.as_view()(request)
   @classmethod
   def as_view(cls, **initkwargs):
       ... ...
       # -- 调用了Django里View的as_view,它return返回了一个view
       #    核心走的是父类as_view So,此处的view就是Django里view的内存地址!
       view = super().as_view(**initkwargs)
       view.cls = cls  
       view.initkwargs = initkwargs
       
       # -- csrf_exempt(view) 去掉了csrf的认证!!后面自己写认证方式!
       #    返回的是局部禁用csrf认证的view视图函数
       #    为什么免除?因为前后端分离用不到啦!!前后端分离,页面和数据是分开的!
       #    Ps:在前面Django那去除csrf的认证不是加装饰器吗??这就得回顾下装饰器的核心原理.
       return csrf_exempt(view)
2> APIView.as_view()(request) -- view(request) # -- 此处的view是去除了csrf认证的view.
3> 我们知道执行Django里view函数里的核心语句是调用 dispatch方法!! 
   self.dispatch(request,*args,**kwargs)
   查找顺序 self - 视图类BookView - drf里的APIView - django里的View
   !!So,这里使用的不是Django View里的dispatch,使用的是APIView里的dispatch方法!
   但跟Django View里的dispatch一样,核心本质还是根据请求方式的不同调用不同的方法!
4> 查看APIView里dispatch的源码
    def dispatch(self, request, *args, **kwargs):
        # ★★★ 意味着,drf的视图函数里 可以通过self.args、self.kwargs获取路由传递参数的值!!
        self.args = args
        self.kwargs = kwargs
        # -- 二次封装request对象
        #    把原来的request对象包装成了 新的request对象/drf的request对象 !
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request  # -- 所以视图类中的self.request就是request.. Hhh.但我们一般都只会用request.
        self.headers = self.default_response_headers  # deprecate?

        try:
            # -- 设置了后端能够解析的编码 执行了三大认证: 认证、权限、频率
            self.initial(request, *args, **kwargs)
            # -- 往下的5行代码跟django的View类里的dispatch源码一模一样!
            #    So,这5行代码也许可以简单缩写成 response = super().dispatch(request,*args,**kwargs)
            #    ( ̄O ̄;) 没有经过仔细推敲的!别真这样改!开发者不这样简写,肯定是这5行语句有的调用是不一样的.
            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
            # -- 真正执行视图类BookView里的方法.
            response = handler(request, *args, **kwargs)
        except Exception as exc:
            # -- 三大认证不通过,抛出异常!
            # -- 执行视图类BookView里的方法,若出错,会捕获异常!!
            response = self.handle_exception(exc)

        # -- 此处处理了响应,即返回json格式还是浏览器格式的数据
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
      

# -- 再看看 `self.initial(request, *args, **kwargs)`的源码!
def initial(self, request, *args, **kwargs):
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # -- 渲染器,不用管
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # -- 跟版本相关,不用管
    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)  # -- 频率
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
# 自定义异常

app01下创建文件 exception.py

from rest_framework.exceptions import APIException


class MyException(APIException):
    def __init__(self, detail="出错了,请联系管理员!", code=None):
        # 查看源码,可以看到APIException的__init__需要detail和code两个参数
        super().__init__(detail, code)
1
2
3
4
5
6
7

views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .exception import MyException


class TestView(APIView):
    def get(self, request):
        user = None
        if not user:
            # -- 会捕获全局异常!!在前端显示{"detail":"用户不存在"}
            #    这样的话,逻辑出现了问题,就不用return了,直接raise,简化了代码!!
            raise MyException(detail='用户不存在')
        return Response("请求成功.")
1
2
3
4
5
6
7
8
9
10
11
12
13

# Request类分析***

在对APIView执行流程的分析中, 我们知道了, 只要继承了APIView,后续的视图类中用到的request对象, 都是drf的request对象!

# 拦截点号运算

__setattr__ 和 __getattr__ 在前面 网络并发编程/手撸ORM.md/知识储备 里详细阐述过!!

魔法方法之 __getattr__ , 类的实例对象.属性 调用的属性不存在时, 触发它!
魔法方法之 __setattr__ , 类的实例对象.属性 = 值 进行赋值(增加/修改)时, 触发它!

class Person:
    school = 'beida'

    def __getattr__(self, item):
        print(item)
        return 'hello'

    def __setattr__(self, key, value):
        print(key, value)
        # -- 错误的方式.
        #    反射setattr的本质是 self.key = value
        #    所以会递归的调用魔法方法__setattr__
        # setattr(self, key, value)
        # -- 正确的方式一:
        #    通过操作属性字典!
        self.__dict__[key] = value
        # -- 正确的方式二:
        #    在子类派生出来的功能中重用父类功能,下面两个皆可
        #    1> 类来调用对象的绑定方法,有几个值就传几个值
        #    2> super()
        # object.__setattr__(self, key, value)
        # super().__setattr__(key, value)


p = Person()
print(p.school)
print('--' * 3)
print(p.name)
print('--' * 3)
p.name = 'egon'
print(p.name)
"""
beida
------
name
hello
------
name egon
egon
"""
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

扩展:
__getitem__ 和 __setitem__, 与 obj[key] 相关 !! -- 可以写个类不继承字典但可以实现字典的取赋值的方式! __setattr__ 和 __getattr__, 与 obj.key 相关 !! -- 可以写个类继承字典,让字典同时支持 点和中括号 的取值、赋值!

# 源码分析

APIView是将老的request对象包装成了新的request对象,实现的细节跟Request类有关!!

★ 重点!! 记住下面的三个结论!!!
     1> 老的django的request在drf的request._request中
     2> 新的request用起来跟原来的一模一样,没有任何区别! 因为Request类重写了__getattr__方法
     3> 新的request多了一个data属性!! POST请求提交的数据都由它接收!
这解决了Django里没有方法直接接收json数据,需要自己处理json数据的问题..
意味着新的request对formdata, urlencoded, json三个格式参数均能解析!!!
实际上该data是一个方法,用@property装饰器包装成了一个数据属性!!注意被包装成数据属性的方法除了self没有其它参数!

request = self.initialize_request(request, *args, **kwargs)

def initialize_request(self, request, *args, **kwargs):
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        ... ...
    )
  
这里的Request到底是个啥呢?它来自这里!
from rest_framework.request import Request

查看Request类的源码!!
★ 可以发现,在Request类的__init__方法里,进行了`self._request = request`的操作,即将老的request赋值给了_request 
所以我们可以在视图类BookView中打印出老的request的值!
class BookView(APIView):
    def get(self, request):
        # <rest_framework.request.Request: GET '/books/'> <class 'rest_framework.request.Request'>
        print(request, type(request))
        # <WSGIRequest: GET '/books/'> <class 'django.core.handlers.wsgi.WSGIRequest'>
        print(request._request, type(request._request))
        print(request.GET)  # <QueryDict: {}>
        return HttpResponse('ok')
      
★ 我们还发现,新的request用起来跟原来的一模一样,打印的结果也没有差别,为什么呢?
我们能想到的是肯定使用了老的request. 按理来说应该是这样,`request._request.GET`
揭晓谜底,因为Request类重写了__getattr__方法!!源码如下:
    def __getattr__(self, attr):
        try:
            # -- 新的request对象.GET方法/属性 会触发__getattr__方法的执行
            #    通过反射相当于执行了 老的request对象.GET方法!!
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)
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

# 验证request.data!!

post请求提交的三种编码格式的数据都会被request.data接收! 简单的认为是字典就行!
urlencoded格式、formdata格式 -- type(request.data)为<class 'django.http.request.QueryDict'>
json格式 -- type(request.data)为<class 'dict'>

from django.urls import path, re_path
from django.contrib import admin
from app01.views import TrainView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('train/', TrainView.as_view()),
]


# --- # --- # --- #


from rest_framework.views import APIView
from rest_framework.response import Response

class TrainView(APIView):
    def post(self, request):
      
        print(request)
        print(request.data)
        print(type(request.data))
        return Response('ok')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Postman实验结果如下: POST请求地址http://127.0.0.1:8000/train/

# -- 传递urlencoded格式的数据
#    name dc
#    name lqz
request._request >>: <WSGIRequest: POST '/train/'>
request._request.POST >>: <QueryDict: {'name': ['dc', 'lqz']}>
request._request.FILES >>: <MultiValueDict: {}>
------
request >>: <rest_framework.request.Request: POST '/train/'>
request.data >>: <QueryDict: {'name': ['dc', 'lqz']}>
type(request.data) >>: <class 'django.http.request.QueryDict'>


# -- 传递formdata格式的数据
#    name dc
#    name lqz
#    my_file 111.jpg
#    my_file 222.jpg
request._request >>: <WSGIRequest: POST '/train/'>
request._request.POST >>: <QueryDict: {'name': ['dc', 'lqz']}>
request._request.FILES >>: <MultiValueDict: {'my_file': [
                                              <InMemoryUploadedFile: 111.jpg (image/jpeg)>, 
                                              <InMemoryUploadedFile: 222.jpg (image/jpeg)>]}>
------
request >>: <rest_framework.request.Request: POST '/train/'>
request.data >>: <QueryDict: {'name': ['dc', 'lqz'], 'my_file': [
                                              <InMemoryUploadedFile: 111.jpg (image/jpeg)>,  
                                              <InMemoryUploadedFile: 222.jpg (image/jpeg)>]}>
type(request.data) >>: <class 'django.http.request.QueryDict'>
  

# -- 传递Json格式的数据
#    {"msg":"success"}
request._request >>: <WSGIRequest: POST '/train/'>
request._request.POST >>: <QueryDict: {}>
request._request.FILES >>: <MultiValueDict: {}>
------
request >>: <rest_framework.request.Request: POST '/train/'>
request.data >>: {'msg': 'success'}
type(request.data) >>: <class 'dict'>
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

补充: request.query_params 是原来的 request.GET
源码如下:

@property
def query_params(self):
    return self._request.GET
  
为什么要这么做呢?就单纯的返回一个self._request.GET, drf的request.GET也可以啊!何必多此一举?
drf让我们更快速的写接口,更快速的实现restful规范,在url地址里携带的参数,eg,?name=lqz&age=19这些东西称为过滤条件!
所以query_params意思是 “查询参数”! 更好的方便我们去理解.
1
2
3
4
5
6
7

Postman和restful
Serializer

← Postman和restful Serializer→

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