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)
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环境变量中..一般我们也不会这样做.
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)
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
"""
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) # -- 频率
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)
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("请求成功.")
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
"""
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)
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')
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'>
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意思是 “查询参数”! 更好的方便我们去理解.
2
3
4
5
6
7