DRF视图组件
一、视图
Django REST framwork 提供视图的主要作用:
- 控制序列化器的执行(检验、保存、转换数据)
- 控制数据库查询的执行
视图继承关系
REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。
二、2个视图基类
1.APIView
from rest_framework.views import APIView
APIView与View
-
APIView
是REST framework 提供的所有视图的基类, 继承自Django的View
父类 -
APIView
相比较与View
, 它对Django原生的request
对象进行了处理转成了 DRF 的request
对象 -
并重写了
dispatch
方法, 在路由分发之前会对请求进行身份认证, 权限检查, 频率控制 -
重写了View中通过本次请求的方式动态的反射到自定义继承APIView类实例化的对象中定义的请求方法
-
设置全局异常处理
-
处理完毕异常以后使用的是 drf 的 response 对象对请求响应
示例(基于APIView写接口)
- 在
APIView
中仍以常规的类视图定义方法来实现get( ) 、post( ) 或者其他请求方式的方法 - 导入方式 :
from rest_framework.views import APIView
- models.py
from django.db import models # Create your models here. class Book(models.Model): name = models.CharField(max_length=32) price = models.CharField(max_length=32) publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE) authors = models.ManyToManyField(to='Author') def publish_detail(self): return {'publish':self.publish.name,'addr':self.publish.addr} def author_list(self): l = [] for author in self.authors.all(): l.append({'name':author.name,'phone':author.phone}) return l class Publish(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) phone = models.CharField(max_length=11)
- serializers.py
from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ['name','price','publish','authors','publish_detail','author_list'] extra_kwargs = {'name':{'max_length':8}, 'publish':{'write_only':True}, 'authors':{'write_only':True}, 'publish_detail': {'read_only': True}, 'author_list': {'read_only': True} }
- views.py
from rest_framework.views import APIView from .models import Book,Publish,Author from rest_framework.response import Response from .serializer import BookSerializer # Create your views here. class BookView(APIView): # 查询所有 def get(self,request): books = Book.objects.all() ser = BookSerializer(instance=books,many=True) print(ser.data) return Response(ser.data) #新增一条 def post(self,request): ser = BookSerializer(data=request.data) if ser.is_valid(): ser.save() return Response({'code':'100','msg':'新增成功','result':ser.data}) else: return Response({'code':101,'msg':ser.errors}) class BookDetail(APIView): # 查询一条 def get(self,request,pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer(instance=book) print(ser.data) return Response(ser.data) #修改一条 def put(self,request,pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer(data=request.data,instance=book) if ser.is_valid(): ser.save() return Response({'code': 100, 'msg': '修改成功','result':ser.data}) else: return Response({'code': 101, 'msg': ser.errors}) # 删除一条 def delete(self,request,pk): Book.objects.filter(pk=pk).delete() return Response({'code':100,'msg':'删除成功'})
- urls.py
urlpatterns = [ path('admin/', admin.site.urls), path('books/', views.BookView.as_view()), path('books/<int:pk>/', views.BookDetail.as_view()), ]
小结
-
继承关系:APIView继承View
-
APIView基于View的拓展:
APIView重写了View的dispatch方法,在该方法中实现了以下一些功能:
-
对来的原生请求对象request进行了封装.
-
提供了对包装过后的请求对象的三段认证:认证,权限控制、频率控制
-
重写了
View
中通过本次请求的方式动态的反射到自定义继承APIView类实例化的对象中定义的请求方法 -
使用异常处理处理2,3步骤中的异常
-
处理完毕异常以后使用drf的Response对象响应请求
-
-
针对路由配置:
- 路由中的有名分组必须指定pk, 视图中使用必须使用相同的关键字参数接受
2.GenericAPIView (通用视图类)
1. GenericAPIView介绍
- 继承自
APIVIew
, 主要增加了操作序列化器和数据库查询的方法, 作用是为下面Mixin扩展类的执行提供方法支持; 通常在使用时, 可搭配一个或多个Mixin扩展类 - 导入方式 :
from rest_framework.generics import GenericAPIView
2.提供关于序列化器的属性和方法
- 属性
属性 | 作用 |
---|---|
queryset (常用) | 需要序列化的查询集 |
serializer_class (常用) | 指定使用的序列化器 |
pagination_class | 分页控制类 |
filter_backends | 过滤控制后端 |
lookup_field | 查询单一数据库对象时使用的条件字段,默认为**’pk’** |
- 方法
方法 | 作用 |
---|---|
get_queryset( ) (常用) | 获取QuerySet查询集 |
get_object( ) (常用) | 获取一条数据对象 |
get_serializer( ) | 返回序列化器对象,可以其他视图或扩展类使用 |
get_serializer_class | 返回序列化器类,默认返回serializer_class,可以重写 |
3.示例(基于GenericAPIView 写接口)
- models.py 文件不变
- serializers.py 文件不变
- urls.py不变
- views.py
from .models import Book,Publish,Author from rest_framework.response import Response from .serializer import BookSerializer from rest_framework.generics import GenericAPIView class BookView(GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request): # objs = self.queryset #这里可以拿到,但是不要这么用,GenericAPIView提供了一个方法(.get_queryset()) objs = self.get_queryset() # 好处,可以重写该方法,后期可扩展性高 # 取序列化类,不要使用self.serializer_class,而要使用方法(.get_serializer()) ser = self.get_serializer(instance=objs, many=True) # 后期可以重写get_serializer_class,指定使用哪个序列化类来序列化 return Response(ser.data) def post(self, request): ser = self.get_serializer(data=request.data) if ser.is_valid(): ser.save() # 咱们现在只有ser序列化类的对象,但是咱们想要,新增的对象---》序列化成字典---》大前提,序列化类中的create方法一定要返回新增的对象 return Response({'code': 100, 'msg': '新增成功', 'result': ser.data}) else: return Response({'code': 101, 'msg': ser.errors}) class BookDetailView(GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request, pk): obj = self.get_object() # 获取单条 ser = self.get_serializer(instance=obj) return Response(ser.data) def put(self, request, pk): obj = self.get_object() ser = self.get_serializer(instance=obj, data=request.data) if ser.is_valid(): ser.save() return Response({'code': 100, 'msg': '修改成功', 'result': ser.data}) else: return Response({'code': 101, 'msg': ser.errors}) def delete(self, request, pk): self.get_object().delete() return Response({'code': 100, 'msg': '删除成功'})
查询条件使用的条件字段是有
lookup_field
属性来控制的, 默认是pk
, 可以修改
三、基于GenericAPIView+5个视图扩展类写接口
1.五个视图扩展类
扩展类 | 提供方法 | 用途 |
---|---|---|
ListModelMixin | list() | 获得多条数据对象 |
CreateModelMixin | create() | 新建单一数据对象 |
RetrieveModelMixin | retrieve() | 获得单一数据对象 |
UpdateModelMixin | update() | 更新一条数据 |
DestroyModelMixin | destroy() | 删除单⼀一对象 |
2.作用
- 提供了几种后端视图 (对数据资源进行增删改查) 处理流程的实现
- 如果需要编写的视图属于这五种, 则视图可以通过继承相应的扩展类来复用代码, 减少自己编写的代码
- 这五个扩展类需要搭配
GenericAPIView
父类,因为五个扩展类的实现需要调用GenericAPIView
提供的序列化器与数据库查询的方法
3.示例
- models.py 文件不变
- serializers.py 文件不变
- urls.py不变
- views.py
from .models import Book,Publish,Author from .serializer import BookSerializer from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin 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个视图子类
1.九个视图子类
子类 | 提供方法 | 继承自 |
---|---|---|
CreateAPIView | 提供 post 方法 | GenericAPIView、CreateModelMixin |
ListAPIView | 提供 get 方法 | GenericAPIView、ListModelMixin |
RetireveAPIView | 提供 get 方法 | GenericAPIView、RetrieveModelMixin |
DestoryAPIView | 提供 delete 方法 | GenericAPIView、DestoryModelMixin |
UpdateAPIView | 提供 put 和 patch 方法 | GenericAPIView、UpdateModelMixin |
ListCreateAPIView | 提供 get、post方法 | GenericAPIView、ListModelMixin、CreateModelMixin |
RetrieveUpdateAPIView | 提供 get、put、patch方法 | GenericAPIView、RetrieveModelMixin、UpdateModelMixin |
RetrieveDestroyAPIView | 提供 get、put、patch方法 | GenericAPIView、RetrieveModelMixin、DestoryModelMixin |
RetrieveUpdateDestoryAPIView | 提供 get、put、patch、delete方法 | GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin |
2.示例
基于视图子类写5个接口
- models.py 文件不变
- serializers.py 文件不变
- urls.py文件不变
- views.py
from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView class BookView(ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookSerializer class BookDetail(RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookSerializer
基于视图子类写任意个数接口
五、视图集(ViewSet)
1.为什么要有视图集?
-
不同的视图集继承了不同的视图基类、视图子类或者视图扩展类, 我们使用视图集可以更加方便的操作数据
-
在没有视图集之前, DRF五大接口帮我们实现数据操作 : get_one, get_list, post, put, delete
-
我们可以写一个类, 然后继承GenericAPIView的九个视图子类来实现五个接口
-
但是出现的问题是 : 查询所有使用 get 方法, 查询一条也是使用 get 方法, 但是在Python中, 同一个类中不能有相同的方法名
-
于是DRF的作者在这个类中不再实现get( )、post( )等方法, 而是将它们映射成对应的 action 行为
-
查询所有的
get( )
方法映射成list( )
, 查询一条的get( )
方法映射成retrive( )
, 这就是视图集的由来
2.视图集的作用
- 使用视图集可以将一系列逻辑相关的动作放到一个类中
list() # 提供一组数据
retrieve() # 提供单个数据
create() # 创建数据
update() # 保存数据
destory() # 删除数据
- ViewSet视图集类不再实现get( )、post( )等方法,而是实现动作 action 如 : list( ) 、create( ) 等
- 视图集只在使用
as_view( )
方法的时候,才会将action动作与具体请求方式相互映射
3.常用的视图集父类
ViewSet
-
继承自:
APIView
与ViewSetMixin
-
作用 : 与APIView基本类似,提供了身份认证、权限校验、流量管理等
-
ViewSet主要通过继承ViewSetMixin来实现在调用
as_view( )
时传入字典的映射处理工作 -
缺点 : 在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法
GenericViewSet
- 使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写
- 而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写
- 但是Mixin扩展类依赖于
GenericAPIView
,所以还需要继承GenericAPIView
- GenericViewSet就帮助我们完成了这样的继承工作
- 继承自 :
GenericAPIView
与ViewSetMixin
- 在实现了调用
as_view()
时传入字典(如{'get':'list'}
)的映射处理工作的同时,还提供了GenericAPIView
提供的基础方法,可以直接搭配Mixin扩展类使用
ModelViewSet
- 继承自 :
GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin
上面根据视图子类写5个接口,需要写两个视图类,而两个视图类的代码,又几乎一样,ModelViewSet可以将两个视图类合并为一个,但是需要配制两个路由
例:
views.py from rest_framework.viewsets import ModelViewSet # 继承GenericViewSet + 五个接口都有 from rest_framework.viewsets import ReadOnlyModelViewSet # 继承GenericViewSet + 只有读两个接口 from rest_framework.viewsets import GenericViewSet # 继承GenericAPIView与ViewSetMixin from rest_framework.viewsets import ViewSet # 继承的APIView与ViewSetMixin from rest_framework.viewsets import ViewSetMixin # 提供修改路由中as_view()类方法的actions参数 class BookView4(ModelViewSet): queryset = models.Book.objects.all() serializer_class = BookSerializer urls.py # 需要书写方法映射关系 path('books/', views.BookView.as_view(actions={'get':'list','post':'create'})), path('books/<int:pk>', views.BookView.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
ReadOnlyModelViewSet
- 继承自 :
GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin - 只能写两个只读接口
4.视图集中定义附加的action动作
在视图集中,除了上述默认的方法动作外,还可以添加自定义动作
- views.py
class PublishView5(GenericViewSet):
queryset = models.Publish.objects.all()
serializer_class = PublishSerializer
# 自定义的 action
def get_xxx(self,request):
query_set = self.get_queryset()
serializer = self.get_serializer(query_set,many=True)
return Response(serializer.data)
- urls.py
path('publish5/', views.PublishView5.as_view(actions={'get': 'get_xxx'})),
5.action 属性
在视图集中,我们可以通过action对象属性来获取当前请求视图集时的action动作是哪个
class PublishView5(GenericViewSet):
queryset = models.Publish.objects.all()
serializer_class = PublishSerializer
# 自定义的 action
def get_xxx(self,request):
query_set = self.get_queryset()
serializer = self.get_serializer(query_set,many=True)
print(self.action) # get_xxx
return Response(serializer.data)
6.
# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行 views.BookView.as_view({'get': 'list', 'post': 'create'})()------>读as_view【这个as_view是ViewSetMixin的as_view】 @classonlymethod def as_view(cls, actions=None, **initkwargs): # 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错 if not actions: raise TypeError("The `actions` argument must be provided when " "calling `.as_view()` on a ViewSet. For example " "`.as_view({'get': 'list'})`") # 。。。。其他代码不用看 def view(request, *args, **kwargs): self = cls(**initkwargs) if 'get' in actions and 'head' not in actions: actions['head'] = actions['get'] self.action_map = actions for method, action in actions.items(): handler = getattr(self, action) setattr(self, method, handler) return self.dispatch(request, *args, **kwargs) # 去除了csrf校验 return csrf_exempt(view) # 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执 行ViewSetMixin----》as_view----》内的view()---》代码贴过来 def view(request, *args, **kwargs): #actions 是传入的字典--->{'get': 'list', 'post': 'create'} self.action_map = actions # 第一次循环:method:get,action:list # 第一次循环:method:post,action:create for method, action in actions.items(): # 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法 # handler就是视图类中的list方法 handler = getattr(self, action) # 反射修改:把method:get请求方法,handler:list # 视图类的对象的get方法,变成了list setattr(self, method, handler) return self.dispatch(request, *args, **kwargs) #dispatch是APIView的
总结
- 在继承
ViewSetMixin
的时候一定要将其放在最左边, 这样在调用as_view()
方法的时候才会先调用ViewSetMixin
中的as_view()
方法 - 视图集在使用
as_view()
方法的时候需要书写方法与action
的映射关系,{'get': 'list', 'post': 'create'}
——只要传入actions,以后访问get就是访问list,访问post,就是访问create,因为ViewSetMixin
重写了父类的as_view()
方法。
- 上面的四种常用视图集都直接或间接的继承了
ViewSetMixin
- 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】,比如:{'get': 'login', 'post': 'register'}
7.
- ModelViewSet:5个试图扩展类+ViewSetMixin+GenericAPIView
- ReadOnlyModelViewSet:2个试图扩展类 + ViewSetMixin+GenericAPIView 只读的两个
- ViewSetMixin:魔法,重写了as_view,只要继承它,以后路由写法变成了映射方法
-
ViewSet:ViewSetMixin+ APIView
-
GenericViewSet:ViewSetMixin+ GenericAPIView
重点:
- 以后,你想继承APIView,但是想变路由写法【视图类中方法名任意命名】,要继承ViewSet
- 以后,你想继承GenericAPIView,但是想变路由写法【视图类中方法名任意命名】,要继承GenericViewSet
8. 视图层大总结
- 两个试图基类
-APIView,GenericAPIView
- 5个试图扩展类,不是视图类,必须配合GenericAPIView
- 9个视图子类,是视图类,只需要继承其中某一个即可
- 视图集
-ModelViewSet:路由写法变了,只需要写两行,5个接口都有了
-ReadOnlyModelViewSet:路由写法变了,只需要写两行,2个只读接口都有了
-ViewSetMixin:不是视图类,魔法,重写了as_view,路由写法变了,变成映射了views.BookView.as_view({'get': 'list', 'post': 'create'})
-ViewSet:ViewSetMixin+ APIView
-GenericViewSet:ViewSetMixin+ GenericAPIView
六.DRF视图关系继承图
ps : 在 Pycharm 中可以鼠标右击一个视图类------>Diagrams------>Show Diagrams 来查看这个类的继承关系
- 视图提供的相应方法图