从零学DRF:源码剖析与实战(四)——视图

两个视图基类介绍
APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

相比View,主要多出来的功能有:

  • 视图方法可以返回rest_framework中的Response对象,APIView视图会为响应的数据渲染一个漂亮的前端页面,而不是仅仅返回字符串。
  • 会捕获APIException异常,基本所以的都会被捕获到,并且会帮我们处理成合适的响应信息;
  • APIView的dispatch()里面实现了像认证,权限,以及频率限制等其他功能。
GenericAPIView

简单使用示例

class BookGenericAPIView(GenericAPIView):
    queryset = models.Book.objects.filter(is_delete=False)
    serializer_class = serializers.BookModelSerializer
    lookup_field = 'pk'  # 默认就是pk,如果路由写正则的时候,用的是
    def get(self, request, *args, **kwargs):
        # 这里后面不用加.all(),因为源码内部帮我们加了
        # 群查
        book_query = self.get_queryset()  # 获取query数据
        book_ser = self.get_serializer(book_query, many=True)
        book_data = book_ser.data
        return APIResponse(results=book_data)

postman发送GET请求测试

GenericAPIView源码

先吃一份GenericAPIView的源码,关键部分已做了注释,

class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.
    """
    # You'll need to either set these attributes,
    # or override `get_queryset()`/`get_serializer_class()`.
    # If you are overriding a view method, it is important that you call
    # `get_queryset()` instead of accessing the `queryset` property directly,
    # as `queryset` will get evaluated only once, and those results are cached
    # for all subsequent requests.
    """
    下面这些是GenericAPIView的类属性
    我们继承GenericAPIView后,可以重写他们
    """
    # 指明视图需要的数据(从model中查询到的queryset对象)
    queryset = None
    # serializer_class指明视图使用的序列化器
    serializer_class = None

    # If you want to use object lookups other than pk, set 'lookup_field'.
    # For more complex lookup requirements override `get_object()`.
    # 自定义主键,默认是pk
    lookup_field = 'pk'
    lookup_url_kwarg = None

    # The filter backend classes to use for queryset filtering
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS

    # The style to use for queryset pagination.
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    def get_queryset(self):
        """
        Get the list of items for this view.
        This must be an iterable, and may be a queryset.
        Defaults to using `self.queryset`.

        This method should always be used rather than accessing `self.queryset`
        directly, as `self.queryset` gets evaluated only once, and those results
        are cached for all subsequent requests.

        You may want to override this if you need to provide different
        querysets depending on the incoming request.

        (Eg. return a list of items that is specific to the user)
        """
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )

        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

    def get_object(self):
        """
        Returns the object the view is displaying.

        You may want to override this if you need to provide non-standard
        queryset lookups.  Eg if objects are referenced using multiple
        keyword arguments in the url conf.
        """
        queryset = self.filter_queryset(self.get_queryset())

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )

        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        # get_serializer_class()返回serializer_class,没有写serializer_class会报错
        serializer_class = self.get_serializer_class()
        # get_serializer_context返回一个字典
        kwargs['context'] = self.get_serializer_context()
        return serializer_class(*args, **kwargs)

    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        # 先断言,如果serializer_class没有重写,就报错
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )
		# 返回serializer_class
        return self.serializer_class

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

    def filter_queryset(self, queryset):
        """
        Given a queryset, filter it with whichever filter backend is in use.

        You are unlikely to want to override this method, although you may need
        to call it either from a list view, or from a custom `get_object`
        method if you want to apply the configured filtering backend to the
        default queryset.
        """
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        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):
        """
        Return a single page of results, or `None` if pagination is disabled.
        """
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)

    def get_paginated_response(self, data):
        """
        Return a paginated style `Response` object for the given output data.
        """
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

源码分析总结

GenericAPIView是继承APIView的,使用完全兼容APIView,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可以配合一个或多个Mixin扩展类
重点:GenericAPIView在APIView基础上完成了哪些事
 1)get_queryset():从类属性queryset中获得model的queryset数据                           群操作就走get_queryset()方法(包括群查,群增等)
 2)get_object():从类属性queryset中获得model的queryset数据,再通过有名分组pk确定唯一操作对象   单操作就走get_object()方法(包括单查,单增等)
 3)get_serializer():从类属性serializer_class中获得serializer的序列化类
五个视图扩展类

1.ListModelMixin(群查)

列表视图扩展类,提供 list 方法快速实现查询视图,返回200状态码。除了查询,该list方法会对数据进行过滤和分页

2.CreateModelMixin(单增) #注意:没有群增的方法,需要自己手动写(******)

创建视图扩展类,提供create方法快速创建资源的视图,成功返回201的状态码

3.RetrieveModelMixin(单查)

详情视图扩展类,提供retrieve方法,可以快速实现返回一个存在的数据对象。

4.UpdateModelMixin(更新,修改) #只有单整体改和单局部改,没有群整体改和群局部改

更新视图扩展类,提供update方法,可以快速实现更新一个存在的数据对象,同时也提供partial_update方法,可以实现局部更新。

5.DestoryModelMixin(删除) 一般不怎么用到

删除视图扩展类,提供destory方法,可以快速实现删除一个存在数据对象。

视图扩展类使用示例

class BookMixinGenericAPIView(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, CreateModelMixin, GenericAPIView):
    # 调用工具类的时候会去找serializer_class等,所以得先定义
    queryset = models.Book.objects.filter(is_delete=False)
    serializer_class = serializers.BookModelSerializer
    lookup_field = 'pk'
    def get(self, request, *args, **kwargs):
        if request.GET.get('pk'):
            response = self.retrieve(request, *args, **kwargs)  # 单查,使用RetrieveModelMixin
        else:
            # 群查
            response = self.list(request, *args, **kwargs)
        return APIResponse(results=response.data)   # 记得加data吧序列化后的数据拿出来
    # 单增
    def post(self, request, *args, **kwargs):
        response =  self.create(request, *args, **kwargs)
        return APIResponse(results=response)
    # 单整体修改
    def put(self, request, *args, **kwargs):
        response = self.update(request, *args, **kwargs)
        return APIResponse(results=response)
    # 单局部修改
    def patch(self, request, *args, **kwargs):
        response = self.partial_update(request, *args, **kwargs)
        return APIResponse(results=response)
常用功能子类视图(工具视图)

工具视图(继承了GenericAPIView和各种Mixins工具类)

  • 1)工具视图都是GenericAPIView的子类,并且不同的子类继承了不同的工具类去完成不同的功能
  • 2)工具视图的功能可以满足需求,只需要继承工具视图,并且提供queryset与serializer_class即可
  • 3 ) 其实这些东西就是想帮我们省代码,因为Django的目标就是为开发者实现快速开发,是一个重量级框架

在这里插入图片描述

使用示例

我们不用再自己写get(),put()等方法,因为DRF源码里面已经帮我们做了。

from rest_framework.generics import ListCreateAPIView, UpdateAPIView
# 基于常用功能子类视图实现群查,新增/更改一条记录
class BookListCreateAPIView(ListCreateAPIView, UpdateAPIView):
    serializer_class = serializers.BookModelSerializer
    queryset = models.Book.objects.filter(is_delete=False)
源码分析

先来一份源码,因为这里很简单,我就不注释了。

class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    """
    Concrete view for creating a model instance.
    """
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)


class RetrieveAPIView(mixins.RetrieveModelMixin,
                      GenericAPIView):
    """
    Concrete view for retrieving a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)


class DestroyAPIView(mixins.DestroyModelMixin,
                     GenericAPIView):
    """
    Concrete view for deleting a model instance.
    """
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)


class UpdateAPIView(mixins.UpdateModelMixin,
                    GenericAPIView):
    """
    Concrete view for updating a model instance.
    """
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)


class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
                            mixins.UpdateModelMixin,
                            GenericAPIView):
    """
    Concrete view for retrieving, updating a model instance.
    """
    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 patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)


class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
                             mixins.DestroyModelMixin,
                             GenericAPIView):
    """
    Concrete view for retrieving or deleting a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)


class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    """
    Concrete view for retrieving, updating or deleting a model instance.
    """
    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 patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

源码分析总结:

工具视图在源码里边的实现是:继承GenericAPIView,以及各种Mixin工具类,然后去实现增删改查方法,也就是那几行增删改查的方法都帮我们实现了,如果工具视图可以满足我们的需求的话,我们只需要继承对应的工具视图类就行了。

视图集(重点掌握)

视图集GenericViewSet源码里面只写了个pass,它继承自ViewSetMixingenerics.GenericAPIViewViewSetMixin重写了as_view()方法,使得在路由中使用as_view()可以传参, generics.GenericAPIView前面已经分析过源码,其实就是定义了一些query_set,serialize_class类属性,以及get_queryset,get_serialize等方法,

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass

GenericViewSet我们看看怎么用的
使用方法:
路由:

url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list'})),

视图:

from api.utils.serializsers.pager import PagerSerialiser
from rest_framework.viewsets import GenericViewSet

	class View1View(GenericViewSet):
		queryset = models.Role.objects.all()
		serializer_class = PagerSerialiser
		pagination_class = PageNumberPagination

		def list(self, request, *args, **kwargs):
			# 获取数据
			roles = self.get_queryset()  # models.Role.objects.all()

			# [1, 1000,]     [1,10]
			pager_roles = self.paginate_queryset(roles)

			# 序列化
			ser = self.get_serializer(instance=pager_roles, many=True)

			return Response(ser.data)

可以看到单纯使用GenericViewSet其实作用不大,因为还要去定义我们的queryset,serializer_class,一般我们使用它都是通过多继承来用的,除了继承它,还要继承一些工具集。比如:

class View1View(CreateModelMixin,GenericViewSet):

下面在来介绍一下最牛的ModelViewSet,如果用这个,我们视图只需要写3行代码就行,不过用它就基本不能自定制了,它一共继承了6个类,增,删,改,查,群查,以及GenericViewSet,它把我们的视图工具集全都继承了。
源码:

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

使用
路由系统:

url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list','post':'create'})),
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),

视图:

我们写好分页的类以及序列化的类,然后视图只需要写这三行代码就OK了

from api.utils.serializsers.pager import PagerSerialiser
from rest_framework.viewsets import GenericViewSet,ModelViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin

class View1View(ModelViewSet):
	queryset = models.Role.objects.all()
	serializer_class = PagerSerialiser
	pagination_class = PageNumberPagination
总结:

有了这么多种写法,你一定会问,该写哪种啊?

视图类归纳起来大概有三种写法,分别继承APIView, GenericViewSet以及ModelViewSet,先说ModelViewSet,它一共继承了6个类(面试说出来),帮我们实现了增删改查的功能,如果我们的视图功能是比较简单的,只是基本的增删改查,那么就使用ModelViewSet,因为它帮我们实现了这些基本的功能,我们用起来更高效,但是它的自定制太差了,如果我们要实现的视图的功能是比较复杂的,就要用比较原始一点的APIView或者GenericViewSet,具体而言,如果我们的增删改查,所有的功能都比较复杂,都想自己去写,就使用APIView,如果我们的更新功能比较复杂,其他比较简单,那么我们就可以继承GenericViewSet以及UpdateModelMixinGenericViewSet开发的时候,比APIView有一个优点就是他可以在路由里面根据请求映射到对应的视图函数里面。如果我们的某个类里边只要实现get或者post方法,那么也可以使用工具视图,不过这个应该比较少用

a. 增删改查 ModelViewSet
b. 增删 CreateModelMixin,DestroyModelMixin GenericViewSet
c. 复杂逻辑 GenericViewSet 或 APIView

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值