drf—视图类
1、视图基类
1.1 APIView
rest_framework.views.APIView
1、drf提供的所有视图的基类,继承于Django的View类
2、APIView与View的不同在于:
1 二次封装了View的httpRequest对象:重写as_view和dispatch方法
2 封装了响应模块,设置(render)符合前端要求的格式
1.2 GenericAPIView
1、继承APIView,拥有APIView所有的功能
2、get_queryset方法,配置queryset类属性
3、get_object方法,配置lookup_url_kwargs类属性
4、get_serializer方法,配置serializer_class类属性
总结:
1、在APIView基础上额外提供了三个方法,三个类属性
2、目的:将视图中的对资源的增删改查封装成方法,提供更简单的使用方式
封装基础:
1 增删改查的逻辑相似
2 操作的资源不同,但是可以形成配置
3 逻辑+配置的资源即可实现对接口的封装
3、使用方式:
1 视图类继承GenericAPIView
2 定义共有属性,配置封装资源
3 定义请求函数,通过get_queryset、get_object、get_serializer来获取配置
class CarGenericAPIView(GenericAPIView):
# 不能直接写到objects结束,因为objects结束时,不是QuerySet对象,而是Manager对象,但 .all() 和 .filter() 后一定是QuerySet对象
queryset = models.Car.objects.filter(is_delete=False).all()
serializer_class = serializers.CarModelSerializer
lookup_url_kwarg = 'pk'
def get(self, request, *args, **kwargs):
car_obj = self.get_object()
car_ser = self.get_serializer(car_obj)
return Response(data={'status':0, 'msg':'ok', 'results':car_ser.data})
2、mixins视图扩展类
1、5个类,实现了单群查、单增删改等6个接口的封装
2、5个类中的实现体,都调用了GenericAPIView的配置和方法,所以需要一起使用
3、可以使用多个mixins类和GenericAPIView配合使用,形成多个操作的封装
4、完成初步封装
1 继承GenericAPIView和本章讲述的视图扩展类之后,就能在请求函数中,调用相应的请求函数
2 需要自己写请求函数,看起来有点累赘,这是为了后面更好的封装
示例:
class CarGenericAPIView(GenericAPIView,ListModelMixin):
# 不能直接写到objects结束,因为objects结束时,不是QuerySet对象,而是Manager对象,但 .all() 和 .filter() 后一定是QuerySet对象
queryset = models.Car.objects.filter(is_delete=False).all()
serializer_class = serializers.CarModelSerializer
lookup_url_kwarg = 'pk'
def get(self, request, *args, **kwargs):
return list(request, *args, **kwargs)
2.1 ListModelMixin
列表视图扩展类,提供list(request,*args,**kwargs)
方法快速实现列表视图,返回200状态码
该Mixin的list方法会对数据进行过滤和分页
源代码:
class ListModelMixin(object):
"""
List a queryset.
"""
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)
示例:
from rest_framework.mixins import ListModelMixin
class BookListView(ListModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request):
return self.list(request)
2.2 CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回200状态码
源代码:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
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 {}
示例:
2.3 RetrieveModelMinxin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象
如果存在,返回200,否则返回404
源代码:
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
示例:
class BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.retrieve(request)
2.4 UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象
同时也提供partial_update(request, *args, **kwargs)
方法,实现局部更新
成功返回200,序列化校验数据失败,返回400
源代码:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
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)
2.5 DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象
成功返回200,否则返回404
源代码:
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
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()
3、功能视图子类
1、由上可知,Mixin类与GenericAPIView类的组合使用,可以实现不同的资源操作的封装
2、官方提供了几种组合形式,进一步封装得到如下几个功能视图子类
3、完成了初步封装:
1 都继承了GenericAPIView,封装了获取资源的方法
2 分别继承了若干个mixin扩展类,封装了资源的6个操作方法
3 两者结合,实现基本的封装
4、使用方式:
1、定义视图类后,定义类共有属性
serializer_class 该视图类的序列化类
queryset 该视图类相关的所有资源
lookup_url_kwargs = 'pk' # url种单操作接口的有名分组名,如pk,id等,一般需要具有唯一性
2、本章讲述的功能视图子类中,重写了相应的请求函数,所以不用再写请求函数
3.1 CreateAPIView
提供post方法,继承GenericAPIView、CreateModelMixin
3.2 ListAPIView
提供get方法,继承GenericAPIView、ListModelMixin
3.3 RetrieveAPIView
提供get方法,继承GenericAPIView、RetrieveModelMixin
3.4 DestroyAPIView
提供delete方法,继承GenericAPIView、DestroyModelMixin
3.5 UpdateAPIView
提供put、patch方法,继承GenericAPIView、UpdateModelMixin
3.6 RetrieveUpdateAPIView
提供get、put、patch方法,继承GenericAPIView、RetrieveModelMixin、UpdateModelMixin
3.7 RetrieveUpdateDestroyAPIView
提供get、put、patch、delete方法,继承GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
4、视图集
4.1 视图集的作用
1、实现群增、删、改
2、实现序列化数据,不只是显示数据,而是显示完整的response响应,包括状态码,状态信息等
3、重写as_view方法,使得能够在路由层通过参数actions指定路由层请求方式与视图函数的映射绑定,不再默认get请求绑定get方法
4、继承扩展类的视图,可以将一系列逻辑相关的动作放到一个类中:
list() 提供一组数据,群查
retrieve() 提供单个数据,单查
create() 创建数据
update() 更新整体数据
partial_update() 更新局部数据
destroy() 删除数据
4.2 常用视图集
ViewSet
1、继承APIView与ViewSetMixin,作用与APIView类似,提供了身份认证、权限校验、流量管理等
2、ViewSetMixin,重写了as_view方法,通过参数字典参数actions实现请求方法与视图函数的映射绑定,其他的与APIView的as_view没有区别
3、继承APIView,除了as_view方法外,其他的方法如dispatch还是用APIView的实现
4、ViewSetMixin重写as_view,APIView也没有提供任何封装的视图函数方法,因此仍然需要自己编写视图函数
GenericViewSet
1、继承GenericAPIView与ViewSetMixin
2、由于继承的是GenericAPIView,因此可以使用GenericAPIView提供的基础方法
3、由于继承了GenericAPIView,就可以继承依赖于GenericAPIView的Mixin扩展类,进而使用封装好的方法操作资源
ModelViewSet
继承GenericViewSet,并且继承了依赖于GenericViewSet的Mixin5大扩展类
因此,在此视图集下,可以使用扩展类提供的方法,包括list()、retrieve()、create()、update()、partial_update()、destroy()
ReadOnlyViewSet
只读视图,即只能使用查相关的扩展类方法
继承GenericViewSet,并继承了ListModelMixin和RetrieveModelMixin
可以复用扩展类提供的list()、retrieve()方法
4.3 如何使用视图集
4.3.1 视图层
定义视图类,继承需要的视图集
# 常用ModelViewSet视图集
class CarViewSet(ModelViewSet):
serializer_class = serializers.CarModelSrealizer
queryset = models.Car.objects.filter(is_delete=False).all()
lookup_url_kwargs = 'pk' # url种单操作接口的有名分组名,如pk,id等,一般需要具有唯一性
# 若是需要群增、群改、群删,就需要再手动重写这些方法,并在Url中添加映射
4.3.2 路由层
示例
通过给as_view()方法传参,指定请求方法与视图函数的映射绑定
urlpatterns = [
url(r'^api/books/$',view.BookViewSet.as_view({'get':'list'})), # 群查
url(r'^api/books/(?P<pk>)$',view.BookViewSet.as_view({'get':'retrieve'})), # 单查
]
as_view源码
ViewSetMixin重写的as_view源码
# 伪代码,只写actions部分相关代码
class ViewSetMixin: # 除了object,不继承其他类
def as_view(cls, actions=None, **initkwargs):
if not actions: # 只有传了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.action_map = actions # 获取actions添加为属性action_map
for method, action in actions.items():
handler = getattr(self, action) # 反射获取action对应的视图函数对象
setattr(self, method, handler) # 将请求方法与视图函数对象绑定
return self.dispatch(request, *args, **kwargs)
return csrf_exempt(view)
5、示例ModelViewSet
5.1 序列化类
from rest_framework import serializers,exceptions
from api import models
class CarListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
return [self.child.update(instance[index], attrs) for index, attrs in enumerate(validated_data)]
class CarModelSrealizer(serializers.ModelSerializer):
class Meta:
list_serializer_class = CarListSerializer
model = models.Car
fields = ['id','name', 'price', 'brand', 'sponsors', ]
5.2 路由层
from . import views
from django.conf.urls import url
urlpatterns = [
url(r'^v1/Cars/$', views.CarViewSet.as_view(
{'get': 'list', 'post': 'create', 'put': 'update_many', 'patch': 'partial_update_many', 'delete': 'destroy_many'})),
url(r'^v1/Cars/(?P<pk>\d+)/$', views.CarViewSet.as_view(
{'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]
5.3 视图层
from api import models
from api import serializers
from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin, ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
class CarViewSet(ModelViewSet):
serializer_class = serializers.CarModelSrealizer
queryset = models.Car.objects.filter(is_delete=False).all()
lookup_url_kwargs = 'pk' # url种单操作接口的有名分组名,如pk,id等,一般需要具有唯一性
def create(self, request, *args, **kwargs):
request_data = request.data
if isinstance(request_data, list):
car_ser = self.get_serializer(data=request_data, many=True)
car_ser.is_valid(raise_exception=True)
car_obj = car_ser.save()
return Response(data={'status': 0, 'msg': '增加成功', 'results': self.get_serializer(car_obj, many=True).data},
status=200)
return super().create(request, *args, **kwargs)
def update_many(self, request, *args, **kwargs):
request_data = request.data
if not isinstance(request_data, list):
return Response(data={"status": 1, 'msg': '数据格式不对,应该是列表套字典'})
pks = []
try:
for dic in request_data:
pks.append(dic.pop('pk'))
instance_list = models.Car.objects.filter(is_delete=False, pk__in=pks).all()
if len(pks) != len(instance_list):
raise Exception('每个字典必须带有pk')
except Exception as e:
return Response(data={'status': 1, 'msg': str(e)}, status=404)
car_ser = self.get_serializer(instance=instance_list, data=request_data, many=True)
car_ser.is_valid(raise_exception=True)
car_obj_list = car_ser.save()
return Response(data={'status': 0, 'msg': '更新成功', 'results': self.get_serializer(car_obj_list, many=True).data})
def partial_update_many(self, request, *args, **kwargs):
request_data = request.data
if not isinstance(request_data, list):
return Response(data={"status": 1, 'msg': '数据格式不对,应该是列表套字典'})
pks = []
try:
for dic in request_data:
pks.append(dic.pop('pk'))
instance_list = models.Car.objects.filter(is_delete=False, pk__in=pks).all()
if len(pks) != len(instance_list):
raise Exception('每个字典必须带有pk')
except Exception as e:
return Response(data={'status': 1, 'msg': str(e)})
car_ser = self.get_serializer(instance=instance_list, data=request_data, many=True, partial=True)
car_ser.is_valid(raise_exception=True)
car_obj_list = car_ser.save()
return Response(data={'status': 0, 'msg': '更新成功', 'results': self.get_serializer(car_obj_list, many=True).data})
def destroy_many(self):
pass
5.4 注意点
1、群增、单增,公用一个接口,需要在函数体内,对数据类型做判断
2、群改接口中,需要手动编写数据格式的判断过程:列表套字典,字典中要带主键
3、try捕捉异常后,except Exception as e得到的是一个对象,需要转换后才能用于响应数据
4、群改的操作,需要在序列化类中,定义CarListSerializer,重写群update方法,并在CarModelSrealizer中配置list_serializer_class=CarListSerializer