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

posted @ 2019-12-30 20:04  W文敏W  阅读(400)  评论(1编辑  收藏  举报