视图+路由+过滤
# GenericAPIView
GenericAPIView 里提供了5个功能:
1> 从数据库获取列表数据并返回;
2> 对列表数据分页并返回分页后的数据;
3> 对列表数据 序列化;4> 获取列表数据进行筛选;
5> 列表数据进行筛选后, 对该queryset使用get获取单条数据, 在返回该单条数据之前还会对其做权限校验;
相关类变量 | 方法 | 功能 |
---|---|---|
queryset | get_queryset() | 从数据库获取列表数据 |
pagination_class | paginate_queryset() | 拿到分页对象, 对列表数据分页, 返回分页后的数据 |
serializer_class | get_serializer() | 获得序列化器类, 并对其进行实例化(实例化时传入了context参数), 返回实例 |
filter_backends | filter_queryset() | 获取列表数据进行筛选 |
queryset filter_backends | get_object() | 1.列表数据进行筛选 2.对该queryset使用get获取单条数据 3.在返回该单条数据之前还可对其做权限校验; |
实际在开发中不会直接继承它, 他更多的是担任 中间人 的角色, 为其他视图子类提供公共功能..
# 前3个功能
数据库获取列表数据、并对其进行 分页 和 序列化.
# 分页后序列化
若想给前端展示图书列表, 数据库获取数据 - 分页 - 获得序列化器类的类实例化对象 - 序列化并返回.
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": None,
"PAGE_SIZE": 2,
}
2
3
4
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from api import models
class DemoModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class DemoView(GenericAPIView):
queryset = models.Book.objects.all()
pagination_class = PageNumberPagination
serializer_class = DemoModelSerializer
def get(self, request, *args, **kwargs):
# 1.数据库获取数据
# queryset = models.Book.objects.all()
queryset = self.get_queryset()
# 2.分页
# pager = PageNumberPagination()
# p_list = pager.paginate_queryset(queryset, request, self)
p_list = self.paginate_queryset(queryset)
# 3.获得序列化器类的类实例化对象
# ser = DemoModelSerializer(instance=p_list, many=True)
ser = self.get_serializer(instance=p_list, many=True)
# 4.序列化并返回
# return Response(ser.data)
# 看源码可知,本质上是调用的是PageNumberPagination里的get_paginated_response方法
return self.get_paginated_response(ser.data)
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
# 源码分析
利用源码来分析, GenericAPIView 是如何进行封装的.
看了后 感觉就是在 “脱了裤子放屁”, 哈哈哈哈! 怪不得开发中不会直接用它.
from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from api import models
class DemoModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
field = "__all__"
class DemoView(GenericAPIView):
queryset = models.Book.objects.all()
pagination_class = PageNumberPagination
serializer_class = DemoModelSerializer
def get(self, request, *args, **kwargs):
# 1.数据库获取数据
# queryset = models.Book.objects.all()
queryset = self.get_queryset()
""" 绕一圈,有点脱了裤子放屁的感觉. (´▽`)
class GenericAPIView(views.APIView):
queryset = None
def get_queryset(self):
# self.queryset默认为空,若继承GenericAPIView的视图类不重写queryset类变量,会断言报错
assert self.queryset is not None, ("error Message")
queryset = self.queryset
if isinstance(queryset, QuerySet):
# 意味这,类变量queryset = models.Book.objects,会自动帮我们加上all()
queryset = queryset.all()
return queryset
"""
# 2.分页
# pager = PageNumberPagination()
# p_list = pager.paginate_queryset(queryset, request, self)
p_list = self.paginate_queryset(queryset)
""" 绕一圈,有点脱了裤子放屁的感觉. (⁎⁍̴̛ᴗ⁍̴̛⁎)
class GenericAPIView(views.APIView):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
@property
def paginator(self): # -- 该方法就是为了得到一个分页对象.
# ★★★
# 若继承GenericAPIView的视图类重写了pagination_class类变量,那么读它
# 若没有,读自己的,自己的会去配置文件里读
# - drf的配置文件默认设置其为空;
# - 我们也可在Django的配置文件中进行DEFAULT_PAGINATION_CLASS的配置!
# REST_FRAMEWORK = {
# "UNAUTHENTICATED_USER": None,
# "PAGE_SIZE": 2,
# "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination"
# }
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
if self.paginator is None: # -- self.paginator拿到了一个分页对象
return None
# 拿到分页后的数据
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data): # 返回
assert self.paginator is not None
return self.paginator.get_paginated_response(data) # get_paginated_response是分页类里的方法
"""
# 3.获得序列化器类的类实例化对象
# ser = DemoModelSerializer(instance=p_list, many=True)
# print(ser.data)
ser = self.get_serializer(instance=p_list, many=True)
""" 绕一圈,有点脱了裤子放屁的感觉. (つД`)ノ
class GenericAPIView(views.APIView):
serializer_class = None
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class() # 找到序列化器类
# 其实就类似于这样.
# DemoModelSerializer(instance=p_list, many=True, context={'xx':'yy'}) # ★!!上下文
kwargs.setdefault('context', self.get_serializer_context())
return serializer_class(*args, **kwargs) # 序列化器类进行类实例化
def get_serializer_class(self):
# 该方法就是取一个类变量,同样的 self.serializer_class默认为空.
# 若继承GenericAPIView的视图类不重写serializer_class类变量,会断言报错
assert self.serializer_class is not None, ("error Message")
return self.serializer_class
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
"""
# 4.序列化返回
# return Response(ser.data)
# 调用的是GenericAPIView里的get_paginated_response方法
"""
class GenericAPIView(views.APIView):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_paginated_response(self, data): # 返回
assert self.paginator is not None
return self.paginator.get_paginated_response(data) # get_paginated_response是分页类里的方法
"""
return self.get_paginated_response(ser.data)
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
112
113
114
115
116
# 后2个功能
filter_queryset方法 以及 get_object 方法. (get_object中使用了filter_queryset的.)
首先, 说一个知识点: (无需有一丁点怀疑,就是这样的.)
/api/demo/ GET请求 得到数据列表!
/api/demo/<int:pk>/ GET请求 得到单条数据!
2
回顾下,关于ORM取一条数据,有两种方式:
1> models.Book.objects.filter(pk=pk).first()
2> models.Book.objects.get(pk=pk)
- 当queryset对象为空,第一种方式 queryset.first() 返回 None
- 第二种方式, 必须满足 pk=pk的数据必须找到<且>满足的数据只有一条. 不然就报错.
So,我们在使用get的方式取数据时,往往用try..except..进行异常捕获!!
GenericAPIView源码中使用的方式2获取单条数据!
2
3
4
5
6
7
回顾下,权限组件里的两个函数:
from rest_framework.permissions import BasePermission
可以查看BasePermission的源码,里面有两个方法
- has_permission 是在disptch里做的权限校验.当请求来,执行视图函数之前进行的权限校验!校验不通过,是到不了视图函数那的.
- has_object_permission 更加细粒度的权限校验.
GenericAPIView的get_object方法是获取单条数据
其源码里就有语句self.check_object_permissions(self.request, obj)
So,可设置返回这条数据给用户之前需要满足什么条件!
2
3
4
5
6
7
8
# 获取单条数据
单条数据就无需分页了.
from rest_framework import serializers
from rest_framework.filters import BaseFilterBackend
from rest_framework.generics import GenericAPIView
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from api import models
class MyFilterBackend1(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
xx = request.query_params.get("xx")
queryset = queryset.filter(status=xx)
return queryset
class MyFilterBackend2(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset
class DemoModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class DemoDetailView(GenericAPIView):
queryset = models.Book.objects.all()
filter_backends = [MyFilterBackend1, MyFilterBackend2] # 过滤类
def get(self, request, pk):
# status = request.query_params.get("xx")
# instance = models.Books.objects.filter(status=status).get(pk=pk)
instance = self.get_object()
# ser = DemoModelSerializer(instance=instance)
ser = self.get_serializer(instance=instance)
return Response(ser.data)
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
# 源码分析
GenericAPIView里的get_object方法:
1> 获取列表数据 - get_queryset
2> 对列表数据进行筛选 - filter_queryset
3> 对经过筛选后得到的queryset对象使用get获取单条数据
4> 在返回该单条数据之前还会对其做权限校验.
class GenericAPIView(views.APIView):
queryset = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
def get_queryset(self):
assert self.queryset is not None, ("error message")
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
def get_object(self):
# 调用了self.get_queryset(),表明我们的视图类中必须得定义类变量queryset
queryset = self.filter_queryset(self.get_queryset())
# lookup_url_kwarg就是路由的动态参数pk, 拿到url传递给它的值!
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, ("error message")
# 若url是/api/demo/2/ 那么 filter_kwargs = {"pk":2}
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
# get_object_or_404 跳转过去,其关键源码就是 (get取单条数据且做了异常捕获)
# ==> 有对象就返回,没对象就返回404!!
# try:
# queryset.get(*args, **kwargs)
# except queryset.model.DoesNotExist:
# raise Http404("error message.")
obj = get_object_or_404(queryset, **filter_kwargs)
# ★ 做了权限校验!!会调用权限组件中的has_object_permission.
# 去看源码APIView中的check_object_permissions方法就知道咋回事了.
self.check_object_permissions(self.request, obj)
return obj
# - 就是做一个筛选的功能.
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
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
# 概览
不想举例说明了,就那么个意思,将就看吧. 一切从简. 请结合 第一次学drf时,总结的视图相关的笔记进行理解. 特别是总结部分!!
“5_drf的视图组件.md” 中没有涉及到分页和过滤 但问题不大
- ○!! GenericAPIView
将数据库查询、序列化类的定义提取到类变量中,便于后期处理.
后期再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。
看下 第一次学的drf中“5_drf的视图组件.md” 有完整的5个接口 APIView、GenericAPIView的一对比,这两个视图类齐活
(后面学的drf中 自己总结分析的GenericAPIView源码也蛮不错的,慢慢读就好.一会就读完了.)
GenericAPIView继承APIView
- ○!! ViewSetMixin
将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中
两个路由两个视图类变成两个路由一个视图类.
它不能单独使用!!要与有dispatch方法的视图类配合一起使用!
urlpatterns = [
path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve",
"put":"update",
"patch":"partial_update",
"delete":"destory"})),]
- ViewSet = ViewSetMixin + APIView
ViewSetMixin主要是重写as_view 看源码 第一次学的drf中“5_drf的视图组件.md”中有阐述,写的很明白
经历了前面一步 将继承APIView时的5个接口改写成 继承ViewSet来实现5个接口 很容易实现,也好理解
- ○!! GenericViewSet类中没有定义任何代码
他就是继承ViewSetMixin和GenericAPIView,也就说他的功能就是将继承的两个类的功能继承到一起.
GenericViewSet = ViewSetMixin + GenericAPIView
经历了前面几步 将继承GenericAPIView时的5个接口改写成 继承GenericViewSet来实现5个接口 很容易实现,也好理解
- 发现想操作其他表,只需要改那些类变量即可,list、create、..等方法动都不用动
所以有了5个视图扩展类!! “5_drf的视图组件.md”中有这个5个扩展类的源码讲解!!
但该博文里搭配的是GenericAPIView+5个视图类 无伤大雅,着重看源码分析即可.
△ 其实 这5个视图扩展类往往会跟GenericViewSet一起用!!
○!! drf为我们提供好了5个用于做 增、删、改(含局部修改)、查列表、查单个数据的5个类(需结合 `GenericViewSet` 使用)
在这个5个类中已帮我们写好了 `list`、`create`、`retrieve`、`update`、`partial_update`、`destory` 方法
我们只需要在根据写 类变量:queryset、serializer_class即可
- class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):pass
★ 还有其他的...
"""关于如何选择
1. APIView 自己写的业务逻辑跟mysql数据库不打交道,比如业务需求需要读取redis、文件等,就继承APIView自己写
2. GenericViewSet + 5个视图扩展类 路由的as_view()是需要写字典参数,表明对应关系的.
因为GenericViewSet继承了ViewSetMixin ViewSetMixin使得不再需要两个视图类.两个路由两个视图类变成两个路由一个视图类.
3. GenericAPIView + 5个视图扩展类 没有用到ViewSetMixin,所以路由的as_view()不需要参数. 但依旧是两个路由两个视图类.
根据请求方式的不同执行get、post、put、delete等方法.
"""
★★★
DRF 的API设计思路:
api/demo/ GET 数据列表
api/demo/ POST 新建数据
api/demo/10/ GET 获取指定一条数据详细
api/demo/10/ PUT 修改数据
api/demo/10/ DELETE 删除数据
★★★
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
# GenericAPIView
【0】看GenericAPIView的源码,可以看到它 脱了裤子放屁的改写.
# ViewSetMixin
【1】视图继承 GenericAPIView GenericAPIView继承的是APIView
路由:
from api.views import demo # views原本是py文件,我们改成了文件夹并在里面创建了名为demo的py文件
path('api/demo/', demo.DemoView.as_view()),
path('api/demo/<int:pk>/', demo.DemoDetailView.as_view()),
视图:
class DemoView(GenericAPIView):
def get(self, request):
pass
def post(self, request):
pass
class DemoDetailView(GenericAPIView):
def get(self, request,pk):
pass
def put(self, request,pk):
pass
def delete(self, request,pk):
pass
===
Q:两个路由写了两个视图类DemoView和DemoDetailView,能否两个路由只写一个视图类呢?
A:可以,只写视图类DemoView,在里面写了5个接口,其中get方法通过if判断写了两个接口.
===
【2】视图继承 GenericAPIView GenericAPIView继承的是APIView
路由:
path('api/demo/', demo.DemoView.as_view()),
path('api/demo/<int:pk>/', demo.DemoView.as_view()),
两个路由,一个视图类:
class DemoView(GenericAPIView):
def get(self, request,pk=None):
if pk:
获取单条数据
else:
获取数据列表数据
def post(self, request):
pass
def put(self, request,pk):
pass
def delete(self, request,pk):
pass
===
Q: 但drf说,你在get方法里判断,这样写比较容易混淆. 有何办法呢?
A: drf提供了ViewSetMixin - 请求的拆分处理 最主要的是重写了as_view方法!!如何重写的,看源码,很容易理解.
路由写法就变了,视图类中的方法可以随便起名,只需要在路由中映射即可.但约定俗成的写成list、create、...、等
===
【3】视图继承ViewSet ViewSet = ViewSetMixin + APIView
路由,与前面的相比,得在路由里多加个参数
path('api/demo/', demo.GvsView.as_view({"get":"list","post":"create"})),
path('api/demo/<int:pk>/', demo.GvsView.as_view({"get":"retrive","delete":"destroy","put":"update"})),
# 向api/demo/该地址发送get请求、post请求,分别执行GvsView类中的list和create方法..
# 潜规则:方法命名为 list、create、...、等
视图:
同样的两个路由一个视图类,视图类里写5个方法映射5个接口: (容易混淆的 获取数据列表和获取数据详细被拆分到了两个方法中)
# ViewSet继承的是ViewSetMixin和APIView, 若这里的GvsView只继承ViewSetMixin,那肯定不行
# 为啥? 很简单,ViewSetMixin重写的as_view方法里是会调用dispatch方法的,ViewSetMixin可没有dispatch方法,APIView里是有dispatch方法的!
class GvsView(ViewSet):
def list(self,request):
pass # 获取数据列表
def create(self,request):
pass
def retrive(self,request,pk):
pass # 获取数据详细
def destroy(self,request,pk):
pass # 删除
def update(self,request,pk):
pass # 修改
===
很容易过渡到继承GenericViewSet
===
【4】视图继承GenericViewSet GenericViewSet = ViewSetMixin + GenericAPIView
以list方法为例
路由:
path('api/user/', demo.UserView.as_view({"get":"list"})),
视图:
class UserView(GenericViewSet):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
def list(self, request):
queryset = self.get_queryset() # 获取数据 queryset = models.UserInfo.objects.all().order_by("-id")
# 分页 pager = PageNumberPagination()
# p_list = pager.paginate_queryset(queryset, request, self)
p_list = self.paginate_queryset(queryset)
# 序列化 ser = DemoModelSerializer(instance=p_list, many=True)
ser = self.get_serializer(intance=p_list, many=True)
return Response(ser.data)
===
你可以清楚的发现,若我想操作Blog表,而不是UserInfo表,只需改变类变量的值即可,list方法动都不用动,一点都不用改.
类变量改变后,list方法的返回就不是用户列表的数据了,而是博客列表的数据
我们发现了,drf也发现了! 所以它提供了5个视图扩展类!!
===
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
# 5个视图扩展类
【5】 5个视图扩展类!!
ListModelMixin 里面写了 list() -- 获取所有表记录
CreateModelMixin 里面写了 create() -- 创建一条表记录
RetrieveModelMixin 里面写了 retrieve() -- 获取一条表记录
UpdateModelMixin 里面写了 update()&partial_update -- 修改一条表记录
DestroyModelMixin 里面写了 destroy() -- 删除一条表记录
- 扩展类里重写的这些方法,方法里执行的那些方法都可以在GenericAPIView中找到!!
查看时,不明白的地方就看 第二次学drf关于GenericAPIView源码的讲解,肯定能看懂的,不用担心.
- 整理下新增、全部更新、和局部更新的区别
ser = UserSerializer(data=request.data)
if is_valid():
ser.save() # 新增 list方法
# 全局更新和局部更新的关键在于实例化时是否传入了partial参数!!
# 全局更新和局部更新都对应的是视图扩展类UpdateModelMixin!
ser = UserSerializer(instance=instance,data=request.data)
if is_valid():
ser.save() # 全局更新 put方法 - 更新时,instance中所有的字段都得有
ser = UserSerializer(instance=instance,data=request.data,partial=True)
if is_valid():
ser.save() # 局部更新 patch方法 - 需要更新的字段存在即可
【5.1】ListModelMixin 查看用户列表数据
# 上面那个 class UserView(GenericViewSet): 就等同于下面这
路由:
path('api/user/', demo.UserView.as_view({"get":"list"})),
视图:
class UserView(GenericViewSet,ListModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
源码: 看下ListModelMixin的源码,很容易理解,不做过多赘述.
【5.2】RetrieveModelMixin 查看某个用户的详情
路由:
path('api/user/<int:pk>/', demo.UserView.as_view({"get":"retrieve"})),
视图:
class UserView(GenericViewSet,RetrieveModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
源码:看下RetrieveModelMixin的源码,很容易理解,不做过多赘述.
【5.1】+【5.2】
path('api/user/', demo.UserView.as_view({"get":"list"})),
path('api/user/<int:pk>/', demo.UserView.as_view({"get":"retrieve"})),
class UserView(GenericViewSet,ListModelMixin,RetrieveModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
===
Q:可以发现,查看用户列表数据和查看某个用户的详情用这几行代码就实现了,但它两用的是同一个序列化器!
如何让这两个功能不用同一个序列化器,而是分别对应一个序列化器呢?
A:需要切换序列化器
查看源码,发现都会调用GenericAPIView中的get_serializer,get_serializer里会调用get_serializer_class
所以我们可以重写get_serializer_class !!
===
path('api/user/', demo.UserView.as_view({"get":"list"})),
path('api/user/<int:pk>/', demo.UserView.as_view({"get":"retrieve"})),
class UserView(GenericViewSet,ListModelMixin,RetrieveModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
def get_serializer_class(self,*args,**kwargs):
pk = self.kwargs.get('pk')
if pk:
return self.serializer_class
return 其他的序列化器类
【5.3】CreateModelMixin 新增用户数据
路由:
path('api/user/', demo.UserView.as_view({"post":"create"})),
视图:
class UserView(GenericViewSet,CreateModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
serializer_class = UserInfoSerializer
源码:看下CreateModelMixin的源码!
"""
class CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # raise_exception=True
self.perform_create(serializer) # 扩展点
headers = self.get_success_headers(serializer.data)
# 返回状态码 200成功 201添加成功
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer): # 可以重写这个方法!!save时多了少了.
serializer.save()
def get_success_headers(self, data): # 响应头
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
"""
【5.4】UpdateModelMixin 修改用户数据 --> 全部更新 & 局部更新 !!
Ps:我试图找过partial,只能定位到BaseSerilazer的init里,再进一步可能就涉及到Django ORM的源码啦,没有深究.
路由:
path('api/user/<int:pk>/', demo.UserView.as_view({"put":"update"})),
视图:
class UserView(GenericViewSet,UpdateModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
serializer_class = UserInfoSerializer
源码:看下UpdateModelMixin的源码!
"""
# partial值为False,全部更新,save更新时,全部字段都得有! partial值为True,局部更新,save更新时,保证更改的字段存在即可!
class UpdateModelMixin:
def update(self, request, *args, **kwargs): # 全部更新 - 对应视图函数put
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer) # 扩展点!
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer): # 可以重写这个方法!
serializer.save()
def partial_update(self, request, *args, **kwargs): # 局部更新 - 对应视图函数patch
kwargs['partial'] = True # !!!
return self.update(request, *args, **kwargs)
"""
【5.1】+【5.2】+ 【5.3】+ 【5.4】
===
Q:到这里,我们已经学会了5个视图函数了!!如何让他们分别对应一个序列化器呢?
Q:同样的,通过切换序列化器.
===
path('api/user/', demo.UserView.as_view({"get":"list","post":"create"})),
path('api/user/<int:pk>/', demo.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update"})),
class UserView(GenericViewSet,ListModelMixin,RetrieveModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
pagination_class = PageNumberPagination
serializer_class = UserInfoSerializer
def get_serializer_class(self,*args,**kwargs):
# 判断依据 1> self.request.method 2>self.kwargs 通过他俩就可区别这5个视图函数!!
return self.serializer_class
【5.6】DestroyModelMixin
路由:
path('api/user/<int:pk>/', demo.UserView.as_view({"delete":"destory"})),
视图:
class UserView(GenericViewSet,UpdateModelMixin):
queryset = models.UserInfo.objects.all().order_by("-id")
serializer_class = UserInfoSerializer
源码:看下DestroyModelMixin的源码!
"""
class DestroyModelMixin:
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # 根据pk找到一条记录
self.perform_destroy(instance) # 扩展点!
# 204表明删除成功
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance): # 源码里是物理删除,我们可重写它实现逻辑删除!
instance.delete()
"""
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# ModelViewSet
【6】
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
2
3
4
5
6
7
# 路由
详看 第一次学drf时总结的博文里关于 路由的文章. 写的很详细了,不再赘述!!
注!! action的url_path是支持写成正则表达式的!!
path('api/shipper/', include("apps.shipper.urls")),
router.register('wallet', wallet.WalletView)
class WalletView(ListRetrieveModelMixin, GenericViewSet):
@action(methods=['GET'], detail=False, url_path='charge/again/(?P<tid>\d+)', url_name='charge_again')
def charge_again(self, request, tid):
# print(tid)
return Response("ok")
/api/shipper/wallet/charge/again/${row_data.id}/ GET
2
3
4
5
6
7
8
9
# 过滤
固定了类变量queryset里就弄了,动态的再使用过滤类. 五个视图扩展类的源码中全员加入了过滤的逻辑.
详看 第一次学drf时总结的博文里关于 过滤的文章. 写的很详细了,不再赘述!!