drf-视图组件
一 视图之两个视图基类
两个视图基类有:
# 视图类:
-APIView:之前用过
-GenericAPIView:GenericAPIView继承了APIView
1.1 APIView
rest_framework.views.APIView
APIView是REST framework提供的所有视图的基类,继承自Django的View父类。
APIView与View的不同之处在于:
- 去除了csrf的校验
- 包装了新的request
- 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
- 处理了全局异常
支持定义的类属性
- authentication_classes 列表或元祖,身份认证类
- permissoin_classes 列表或元祖,权限检查类
- throttle_classes 列表或元祖,流量控制类
在APIView中仍以常规的类视图定义方法来实现get() 、post() 、put() 、 get() 、 delete()。
使用APIView+序列化类+Response写接口
from rest_framework.views import APIView
from .serializer import BookSerialzier
from rest_framework.response import Response
from .models import Book
class BookView(APIView):
def get(self, request):
qs = Book.objects.all()
ser = BookSerialzier(qs, many=True)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def post(self, request):
ser = BookSerialzier(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class BookDetailView(APIView):
def get(self, request, pk):
book = Book.objects.all().get(pk=pk)
ser = BookSerialzier(book)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def put(self, request, pk):
book = Book.objects.get(pk=pk)
ser = BookSerialzier(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
1.2 GenericAPIView[通用视图类]
rest_framework.generics.GenericAPIView
继承自APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
1.2.1 主要的属性或方法
以后,如果涉及到数据库操作,尽量用GenericAPIView。
# GenericAPIView
-类属性:
queryset:要序列化的所有数据
serializer_class:序列化类
lookup_field = 'pk' :获取单条数据的查询条件
filter_backends:过滤类
pagination_class:分页类,任意一个查询所有的条件都要带分页类,一条一条查询。
-方法:
-get_queryset:获取所有要序列化的数据【后期可以重写】
-get_object :根据pk,获取单个对象,没有返回404。路由必须:path('/<int:pk>/', PublishDetailView.as_view()),
-get_serializer : 真正的实例化得到序列化类【后期可以重写,指定要序列化的类是哪一个】
-get_serializer_class:仅仅是返回序列化类,不能传任何参数
拓展:
get_queryset():其实就是把queryset返回出去了。但为什么要写这个方法呢?
# 写成方法的目的:增加扩展性。
可以在自己的类中重写这个方法。可以根据某个条件返回不同的值,这样能够序列化的数据就变多了,是动态的,而不是写死的。
eg:
def get_queryset(self):
if 某种条件:
return self.queryset
elif 另一个条件:
return Book.objects.all()
1.2.2 推导GenericAPIView的封装
# 咱们写的,伪代码
# 定义一个GenericAPIView类继承APIView
class GenericAPIView(APIView):
query_set = None
serialzier_class = None
def get_queryset(self):
return self.query_set
def get_serializer(self):
return self.serialzier_class
def get_object(self):
return self.query_set.filter(pk=pk)
class BookView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request):
qs = self.get_queryset()
ser = self.get_serializer(qs, many=True)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class BookDetailView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request, pk):
book = self.get_object()
ser = self.get_serializer(book)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def put(self, request, pk):
book = self.get_object()
ser = self.get_serializer(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
1.2.3 写5个接口
使用GenericAPIView+序列化类+Response写5个接口
# 总结:以后继承GenericAPIView写接口
1 必须配置类属性
queryset
serializer_class
2 想获取要序列化的所有数据
get_queryset()
3 想使用序列化类:
get_serializer
4 想拿单条
get_object
代码:
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
# queryset = Book.objects # 可以不用加all(),源码中有,但最好自己加上
queryset = Book.objects.all() ##
serializer_class = BookSerialzier ##
# lookup_field = 'id'
def get(self, request):
# qs = self.queryset # 这个函数中也可以这么写
qs = self.get_queryset() ##
ser = self.get_serializer(qs, many=True) ##
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def post(self, request):
ser = self.get_serializer(data=request.data) ##
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class BookDetailView(GenericAPIView):
queryset = Book.objects.all() ##
serializer_class = BookSerialzier ##
def get(self, request, pk):
book = self.get_object() ##
ser = self.get_serializer(book) ##
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def put(self, request, pk):
book = self.get_object() ##
ser = self.get_serializer(data=request.data, instance=book) ##
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
# 再写publish有关的,就可以复制上面的,改动两个类的名字,改动两个类属性就行
queryset = Publish.objects.all()
serializer_class = PublishSerialzier
"""如果涉及到数据库操作,尽量用GenericAPIView"""
二 5个视图扩展类
2.1 方法
5个类(不叫视图类,视图扩展类,需要配合GenericAPIView一起用),每个类有一个方法,以后想写哪个接口,就继承哪个类即可。
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin
'''
-查询所有 :ListModelMixin ---> list方法
-新增一条:CreateModelMixin ---> create方法
-查询单条:RetrieveModelMixin ---> retrieve方法
-修改一条:UpdateModelMixin ---> update方法
-删除一条:DestroyModelMixin ---> destroy方法
'''
2.2 自己封装
# 不需要继承类
class ListModelMixin: # 查询所有
def list(self, request, *args, **kwargs):
qs = self.get_queryset()
ser = self.get_serializer(qs, many=True)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
class CreateModelMixin: # 新增数据
def create(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class RetrieveModelMixin: # 查询单条
def retrieve(self, request, *args, **kwargs):
book = self.get_object()
ser = self.get_serializer(book)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
class UpdateModelMixin: # 修改一条
def update(self, request, *args, **kwargs):
book = self.get_object()
ser = self.get_serializer(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class DestroyModelMixin: # 删除一条
def destroy(self, request, *args, **kwargs):
self.get_object().delete()
return Response({'code': 100, 'msg': '删除成功'})
2.3 源码
'''
-查询所有 :ListModelMixin ---> list方法
-新增一条:CreateModelMixin ---> create方法
-查询单条:RetrieveModelMixin ---> retrieve方法
-修改一条:UpdateModelMixin ---> update方法
-删除一条:DestroyModelMixin ---> destroy方法
'''
ListModelMixin的源码:
class ListModelMixin:
"""
List a queryset.查询所有
快速实现列表视图,返回200状态码。
"""
def list(self, request, *args, **kwargs):
# 过滤
queryset = self.filter_queryset(self.get_queryset())
# 分页
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# 序列化
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
CreateModelMixin的源码:
class CreateModelMixin:
"""
新增一条
Create a model instance.
成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证,不正确主动抛异常
serializer.is_valid(raise_exception=True)
# 保存, 也可以自己写perform_create方法,提高了扩展性
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
RetrieveModelMixin的源码:
class RetrieveModelMixin:
"""
查询一条
Retrieve a model instance.
如果存在,返回200, 否则返回404。
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
UpdateModelMixin的源码:
class UpdateModelMixin:
"""
修改一条
Update a model instance.
成功返回200,序列化器校验数据失败时,返回400错误
"""
def update(self, request, *args, **kwargs):
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):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
DestroyModelMixin的源码:
class DestroyModelMixin:
"""
删除一条
Destroy a model instance.
成功返回204,不存在返回404。
"""
def destroy(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 删除数据
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
2.4 具体使用,写5个接口
# 5 个视图扩展类--->不是视图类---> 必须配合GenericAPIView及其子类使用---> 不能配合APIView使用
# 5个视图扩展类,每一个类中只有一个方法,完成5个接口中的其中一个,想写多个接口,就要继承多个。这种思维方法叫mixin,混入
# 3 继承GenericAPIView+5个视图扩展类+序列化类+Response写接口
from .models import Book
from .serializer import BookSerializer
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
三 9个视图子类
3.1 介绍
# 9个视图子类--->视图类
from rest_framework.generics import ListAPIView, CreateAPIView, UpdateAPIView, DestroyAPIView, RetrieveAPIView
from rest_framework.generics import ListCreateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import RetrieveDestroyAPIView
# 没有UpdateDestroyAPIView,写接口是为了配合前端使用,前端只有查到这个数据才能够修改,如果都不能查到,就不会有修改和删除方法了。
1)CreateAPIView
提供 post 方法
继承自: GenericAPIView、CreateModelMixin
2)ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
3)RetrieveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
4)DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
5)UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
6)RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
7)RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
3.2 具体使用
# 4 基于视图子类写5 个接口:9个视图子类--->视图类
from .models import Book
from .serializer import BookSerializer
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
class BookView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
四 视图集
4.1 ModelViewSet写5个接口
总结
# ModelViewSet:
-继承它后,只需要在视图类中写两行
queryset = Book.objects.all()
serializer_class = BookSerialzier
-配置路由,5个接口都有了
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
视图函数
### 5 视图集 ---> 写5个接口
from .models import Book
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerialzier
路由
urlpatterns = [
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
4.3 ModelViewSet 源码分析
# ModelViewSet是个视图类,必须继承GenericAPIView或子类
# ModelViewSet 源码分析
-继承了:
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin # 5个视图扩展类
GenericViewSet
-ViewSetMixin :没有见过, 重写了 as_view
-GenericAPIView
-只要继承了ModelViewSet,路由写法变成映射的关系了,谁控制它变的:ViewSetMixin
# ViewSetMixin 如何控制路由写法变了?
-BookView.as_view 是在执行,其实是ViewSetMixin的as_view
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
#我们传入的 actions={'get': 'list', 'post': 'create'}
def view(request, *args, **kwargs):
self = cls(**initkwargs)
for method, action in actions.items():
# method:get
# action:list
# 通过反射,self是BookView的对象,取BookView对象中反射list
handler = getattr(self, action)
# 通过反射:setattr(BookView的对象,'get',list方法)
setattr(self, method, handler)
# APIViwe的dispatch
return self.dispatch(request, *args, **kwargs)
return csrf_exempt(view)
# 总结:
'''
1 只要继承了ViewSetMixin及其子类,路由写法就变了,必须传actions参数,没有传入action参数,会报错,主动抛异常
2 变成映射关系了:
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
3 以后,只要是books路由匹配成功,的get请求,就会执行视图类BookView的list方法
4 以后视图类中的方法名,可以随意命名
5 ViewSetMixin这个类,必须配合视图类使用(APIView,GenericAPIView,9个视图子类),必须放在视图类之前(as_view()要先执行)
'''
4.4 视图集中的所有类
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet,ViewSetMixin,ViewSet,GenericViewSet
ViewSetMixin:魔法,不能单独使用,必须配合视图类用,重写了as_view,路由写法变了
ViewSet =ViewSetMixin+APIView
GenericViewSet = ViewSetMixin+GenericAPIView
ModelViewSet:继承了5个视图扩展类和GenericViewSet
ReadOnlyModelViewSet:只读视图类 ---> 只有两个,继承查询所有和查询单条+GenericViewSet
0) ViewSetMixin
只要继承了ViewSetMixin及其子类,路由写法就变了,变成映射关系。必须传actions参数,没有传入action参数,会报错,主动抛异常。
但它不能单独使用,必须配合视图类用
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
1) ViewSet
继承自APIView与ViewSetMixin,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{‘get’:’list’})的映射处理工作。
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
2)GenericViewSet
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView。
GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView与ViewSetMixin,在实现了调用as_view()时传入字典(如{'get':'list'})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用。
3)ModelViewSet
继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
4)ReadOnlyModelViewSet
继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin。
4.5 视图中自定义的方法名
视图函数
from rest_framework.viewsets import ViewSetMixin
from rest_framework.views import APIView
class SmsView(ViewSetMixin, APIView):
# 视图类中的方法可以随意命名
def lqz(self, request):
return Response('你好')
路由
path('lqz/', SmsView.as_view({'get':'lqz'})),
# http://127.0.0.1:8000/lqz/地址的get请求,就会执行SmsView视图类中自定义的lqz方法。
4.5 ModelViewSet的拓展
4.5.1 重写perform_create
- 继承了ModelViewSet,每增加一条记录,就干某个事,重写perform_create
# 具体使用时,就可以自己重写内部的方法,扩展性很高
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 自己重写list方法
def list(self, request, *args, **kwargs):
# 继承父类的list方法,也就是真正的list方法
res = super().list(request, *args, **kwargs)
return Response({'code': 100, 'msg': '成功', 'data': res.data})
# 重写create方法
def create(self, request, *args, **kwargs):
# 真正的新增数据方法
res = super().create(request, *args, **kwargs)
print('发送邮件。。。')
return Response({'code': 100, 'msg': '新增成功'})
# # 或者也可以重写perform_create方法,两个重写一个就行
# def perform_create(self, serializer):
# # 在新增之前,加入逻辑
# print('发送邮件。。。。')
# super(BookView, self).perform_create(serializer)
4.5.2 重写序列化类--get_serializer_class
- 序列化使用一个序列化类,反序列化使用配置的那个序列化类
class BookView(ModelViewSet):
queryset = None
serializer_class = None
def get_serializer_class(self):
# 判断,请求是get,还是其它
# self中保存有请求对象
if self.request.method=='get':
return 某个序列化类
else:
return self.serializer_class
4.5.3 动态变序列化类和动态获取不同对象
- 自定义了一个方法:login,使用了action装饰器,让它取到的qs对象和序列化类跟配置的都不一样
##### 视图类 :self 内部有request对象,还有个action,就是视图类中方法的字符串名
class BookView(ModelViewSet):
queryset = None
serializer_class = None
# 动态变序列化类
def get_serializer_class(self):
# self的action就是路径的名字
if self.action =='login':
return '某个序列化类'
elif self.action=='create':
return '另一个序列化类'
else:
return super().get_serializer_class()
def get_queryset(self):
# self.action=='login':
if 'login' in self.request.path:
return 单独的qs
else:
return super().get_queryset()
def create(self, request, *args, **kwargs):
self.get_serializer()
# /books/login/
# /books/ ---->post--->create self.get_serializer
@action(methods=['POST'], detail=False)
def login(self, request):
self.get_queryset() # 获取所有数据
self.get_serializer() # 获取序列化类
六 总结
# 两个视图基类
-APIView
-GenericAPIView:有关数据库操作,queryset 和serializer_class
# 5 个视图扩展类(rest_framework.mixins)
不是视图类,需要配合GenericAPIView及其子类使用
-CreateModelMixin:create方法创建一条
-DestroyModelMixin:destory方法删除一条
-ListModelMixin:list方法获取所有
-RetrieveModelMixin:retrieve获取一条
-UpdateModelMixin:update修改一条
# 9个视图子类(rest_framework.generics)
-CreateAPIView:有post方法,新增数据
-DestroyAPIView:有delete方法,删除数据
-ListAPIView:有get方法获取所有
-UpdateAPIView:有put方法,修改数据
-RetrieveAPIView:有get方法,获取一条
-ListCreateAPIView:有get获取所有,post方法新增
-RetrieveDestroyAPIView:有get方法获取一条,delete方法删除
-RetrieveUpdateAPIView:有get获取一条,put修改
-RetrieveUpdateDestroyAPIView:有get获取一条,put修改,delete删除
# 视图集:
-ModelViewSet:5个接口的
-ReadOnlyModelViewSet:两个接口,list和retrieve
-ViewSetMixin:魔法,不能单独使用,必须配合视图类用,路由写法变了,映射。
-ViewSet:ViewSetMixin+APIView,以后想继承APIView,但是路由写法变化,视图类中方法可以任意命名
-GenericViewSet:ViewSetMixin+GenericAPIView,以后想继承GenericAPIView,但是路由写法变化,视图类中方法可以任意命名