• DRF 之view


    DRF view

    View

    DRF中的view分成三个等级,最基本的APIView, 到GenericAPIView,再到GenericViewSet.

    Django用“视图”这个概念封装处理用户请求并返回响应的逻辑。视图是一个可调用对象,它不仅可以是基于函数,也可以是基于类的。函数是通过判断request.method来区分不同的请求,而基于类的视图则对它进行了封装,只需要实现对应的方法即可,如get,post. 不管django还是restframework中的视图类都是基于这一点进行的封装。

    APIView

    APIView是drf中所有view的父类,本身继承于Django的View. 。最直接封装的是对request,response都进行了封装,response里面做了一些认证,权限,限流之类处理。而response返回的结果是经过系列化的json.

    与django中的view类似的是,APIView中只需要实现对应的方法如 get, post等。

    class ArticleView(APIView):
        def get(self, request, *args, **kwargs):
            # 取数据
            pass
        def post(self, request, *args, **kwargs):
            # 增加数据
            pass
        def put(self, request, *args, **kwargs):
            # 全局更新
            pass
        def patch(self, request, *args, **kwargs):
            # 局部更新
            pass
        def delete(self, request, *args, **kwargs):
            # 删除
            pass
    

    GenricAPIView

    这里都是通用的APIView,所谓通用就是常用的增删改查,也就是restframework已经帮你封装好了。比如django的GenericView封装了ListView, DetailView,CreateView, UpdateView, DeleteView等通用视图类。drf中则封装得更多。

    下面 分别看一下源码:

    CreateAPIView

    实现了post方法

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

    ListAPIView

    实现get方法

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

    RetrieveAPIView

    实现get方法,它与ListAPIView的不同是它获取单个对象,类似于django中的DetailView

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

    DestroyAPIView

    实现delete方法

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

    UpdateAPIView

    实现了put,patch两个方法,分别对应全局和局部更新。

    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)
    

    下面则是一些组合的视图类

    ListCreateAPIView

    既实现了get方法,又实现了post方法,即可以获取所有对象,也可以添加对象。

    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)
    

    RetrieveUpdateAPIView

    实现了get, put, patch三个方法,这里的get是获取单个对象,put,patch则对应的更新

    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
    

    RetrieveDestroyAPIView

    实现了get,delete方法,能获取,删除单个对象。

    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)
    
    

    RetrieveUpdateDestroyAPIView

    这个是实现方法最多的视图类,get(获取单个对象),put,patch更新单个对象,delete则是删除单个对象,也就是这个类实现了对单个对象的查改删三种操作

    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来实现的。事实上GenericAPIView中并没有实现增删改查中的任何一种操作,这些操作都是通过Mixin来完成的。

    Mixins

    知道了这些,当我们需要对通用视图类的行为作定制时,只需要对这些Mixin实现的方法进行重写即可。这一点后面再说,我们先看看restframework实现了哪些Mixin,是不是和上面的视图类能对应上呢?

    CreateModelMixin

    CreateModelMixin中实现了三个方法,其中与增加对应的是前两个,create,perform_create.也就是说当我们继承CreateAPIView时,实际上是通过这两个方法来完成增加操作的。

    class CreateModelMixin:
        """
        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 {}
    
    

    ListModelMixin

    只实现了一个list方法,即ListAPIView通过它来返回对象列表。

    class ListModelMixin:
        """
        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)
    

    RetrieveModelMixin

    实现了retrieve方法,获取单个对象。

    class RetrieveModelMixin:
        """
        Retrieve a model instance.
        """
        def retrieve(self, request, *args, **kwargs):
            instance = self.get_object()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
    

    UpdateModelMixin

    实现了update,perform_update,partial_update方法,update,partial_update分别对应的全局与局部更新,在这里如果我们需要对保存的数据作一定的定制可以重写perform_update方法。

    class UpdateModelMixin:
        """
        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)
    

    DestroyModelMixin

    最后一个Mixin是DestoryModelMixin,实现了destory,perform_destory方法,即删除。

    class DestroyModelMixin:
        """
        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()
    

    ListAPIView应用举例:

    很显然,Mixin的个数并没有上面的View那么多,这是因为View可能对Mixin进行组合来达到实现不同功能的目的。那我们在写自己的视图类时是不是也可以随意组合通用视图类呢?如果仔细看一下上面那些通用视图类中实现的方法,就会发现也有些是不能组合到一起的,比如ListAPIView,RetrieveAPIView,它们都实现了get方法,使用时就会出问题。

    前面说到“当我们需要对通用视图类的行为作定制时,只需要对这些Mixin实现的方法进行重写即可”指的就是这些Mixin中的方法,比如在使用ListAPIView时我们要获取 额外的信息,就可以重写list方法:

    这里拿最近写的一个订单视图类举例,我重写list方法,将订单信息进行了数据结构的重组,返回一个嵌套的字典

    class Order(ListAPIView):
        authentication_classes = [UserAuthentication, ]
        serializer_class = ser_order.OrderModelSerializer
        queryset = models.Order.objects.all().order_by('-id')
        # filter_backends = [ser_order.OrderFilterBackends, ]
    
        def list(self, *args, **kwargs):
            response = super(Order, self).list(*args, **kwargs)
            if response.status_code != status.HTTP_200_OK:
                return response
    
            order_dict = OrderedDict()
    
            # 将订单的状态码,状态描述文字组成字典
            for item in models.Order.status_choices:
                order_dict[item[0]] = {'text': item[1], 'child': []}
    
            # 将每个状态对应的订单信息放到child列表中,即根据状态码进行了分类
            for row in response.data:
                order_dict[row['status']]['child'].append(row)
    
            response.data = order_dict
            return response
    

    ViewSet

    ViewSet其实是对前面内容的更高层的封装,但我们可以看到在ViewSet类中并没有实现任何特殊的内容,它只是继承了两个类ViewSetMixin, APIView.

    ViewSetMixin

    ViewSetMixin是viewset的基础,由于代码太多我们就只看看它的最重要的方法:as_view()它将请求方法与对应的Mixin中的方法关联起来,比如‘get' -->'list', 'post'-->'create', 'put'-->'update'

    class ViewSetMixin:
        """
        This is the magic.
    
        Overrides `.as_view()` so that it takes an `actions` keyword that performs
        the binding of HTTP methods to actions on the Resource.
    
        For example, to create a concrete view binding the 'GET' and 'POST' methods
        to the 'list' and 'create' actions...
    
        view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
        """
    
        @classonlymethod
        def as_view(cls, actions=None, **initkwargs):
            """
            Because of the way class based views create a closure around the
            instantiated view, we need to totally reimplement `.as_view`,
            and slightly modify the view function that is created and returned.
            """
            # The name and description initkwargs may be explicitly overridden for
            # certain route configurations. eg, names of extra actions.
            cls.name = None
            cls.description = None
    
            # The suffix initkwarg is reserved for displaying the viewset type.
            # This initkwarg should have no effect if the name is provided.
            # eg. 'List' or 'Instance'.
            cls.suffix = None
    
            # The detail initkwarg is reserved for introspecting the viewset type.
            cls.detail = None
    
            # Setting a basename allows a view to reverse its action urls. This
            # value is provided by the router through the initkwargs.
            cls.basename = None
    
            # actions must not be empty
            if not actions:
                raise TypeError("The `actions` argument must be provided when "
                                "calling `.as_view()` on a ViewSet. For example "
                                "`.as_view({'get': 'list'})`")
    
            # sanitize keyword arguments
            for key in initkwargs:
                if key in cls.http_method_names:
                    raise TypeError("You tried to pass in the %s method name as a "
                                    "keyword argument to %s(). Don't do that."
                                    % (key, cls.__name__))
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r" % (
                        cls.__name__, key))
    
            # name and suffix are mutually exclusive
            if 'name' in initkwargs and 'suffix' in initkwargs:
                raise TypeError("%s() received both `name` and `suffix`, which are "
                                "mutually exclusive arguments." % (cls.__name__))
    
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                # We also store the mapping of request methods to actions,
                # so that we can later set the action attribute.
                # eg. `self.action = 'list'` on an incoming GET request.
                self.action_map = actions
    
                # Bind methods to actions
                # This is the bit that's different to a standard view
                for method, action in actions.items():
                    handler = getattr(self, action)
                    setattr(self, method, handler)
    
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
    
                self.request = request
                self.args = args
                self.kwargs = kwargs
    
                # And continue as usual
                return self.dispatch(request, *args, **kwargs)
    
            # take name and docstring from class
            update_wrapper(view, cls, updated=())
    
            # and possible attributes set by decorators
            # like csrf_exempt from dispatch
            update_wrapper(view, cls.dispatch, assigned=())
    
            # We need to set these on the view function, so that breadcrumb
            # generation can pick out these bits of information from a
            # resolved URL.
            view.cls = cls
            view.initkwargs = initkwargs
            view.actions = actions
            return csrf_exempt(view)
    
        def initialize_request(self, request, *args, **kwargs):
            """
            Set the `.action` attribute on the view, depending on the request method.
            """
            request = super().initialize_request(request, *args, **kwargs)
            method = request.method.lower()
            if method == 'options':
                # This is a special case as we always provide handling for the
                # options method in the base `View` class.
                # Unlike the other explicitly defined actions, 'metadata' is implicit.
                self.action = 'metadata'
            else:
                self.action = self.action_map.get(method)
            return request
    
        def reverse_action(self, url_name, *args, **kwargs):
            """
            Reverse the action for the given `url_name`.
            """
            url_name = '%s-%s' % (self.basename, url_name)
            kwargs.setdefault('request', self.request)
    
            return reverse(url_name, *args, **kwargs)
    
        @classmethod
        def get_extra_actions(cls):
            """
            Get the methods that are marked as an extra ViewSet `@action`.
            """
            return [method for _, method in getmembers(cls, _is_extra_action)]
    
        def get_extra_action_url_map(self):
            """
            Build a map of {names: urls} for the extra actions.
    
            This method will noop if `detail` was not provided as a view initkwarg.
            """
            action_urls = OrderedDict()
    
            # exit early if `detail` has not been provided
            if self.detail is None:
                return action_urls
    
            # filter for the relevant extra actions
            actions = [
                action for action in self.get_extra_actions()
                if action.detail == self.detail
            ]
    
            for action in actions:
                try:
                    url_name = '%s-%s' % (self.basename, action.url_name)
                    url = reverse(url_name, self.args, self.kwargs, request=self.request)
                    view = self.__class__(**action.kwargs)
                    action_urls[view.get_view_name()] = url
                except NoReverseMatch:
                    pass  # URL requires additional arguments, ignore
    
            return action_url
    
    

    ViewSet

    class ViewSet(ViewSetMixin, views.APIView):
        """
        The base ViewSet class does not provide any actions by default.
        """
        pass
    

    GenericViewSet

    GenericViewSet不提供任何方法,但由于它继承了GenericAPIView,所以也具体了如get_object,get_queryset,get_serializer等方法。另外它还继承 了ViewSetMixin,所以也具体了对应的方法如view方法等。

    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
    

    ReadOnlyModelViewSet

    ReadOnlyModelViewSet 主要达到read only的作用,即它不提供增删改的功能,而只获取对象,因为它只实现了list, retrieve功能。

    class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                               mixins.ListModelMixin,
                               GenericViewSet):
        """
        A viewset that provides default `list()` and `retrieve()` actions.
        """
        pass
    

    ModelViewSet

    ModelViewSet继承GenericViewSet,及一个Mixin. 但GenericViewSet又继承了ViewSetMixin,GenericAPIView,这样看ModelViewSet继承了所有的Mixin和通用视图类的父类。

    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
    
    
    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
    

    那么ViewSet到底高级在哪呢?

    ViewSet应用举例:

    class UserListViewSet(GenericViewSet, ListModelMixin):
        """
        API endpoint that allows users to be viewed or edited.
        """
        queryset = User.objects.all().order_by('-date_joined')
        serializer_class = UserSerializer
    

    如果我们使用上面的视图类:那么在as_view中我们就该指定get 与list的关系:

     UserListViewSet.as_view({'get': 'list'})
    

    但通常有更简单的用法:先看代码:

    from rest_framework import viewsets
    from rest_framework import serializers
    
    
    class UserSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = models.User
            fields = ('url', 'username', 'email', 'groups')
    
    
    class GroupSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = models.Group
            fields = ('url', 'name')
    
    class UserViewSet(viewsets.ModelViewSet):
        """
        API endpoint that allows users to be viewed or edited.
        """
        queryset = User.objects.all().order_by('-date_joined')
        serializer_class = UserSerializer
    
    
    class GroupViewSet(viewsets.ModelViewSet):
        """
        API endpoint that allows groups to be viewed or edited.
        """
        queryset = Group.objects.all()
        serializer_class = GroupSerializer
    
    views.py
    
    from django.conf.urls import url, include
    from rest_framework import routers
    from auth import views
    
    router = routers.DefaultRouter()
    router.register(r'users', views.UserViewSet)
    router.register(r'groups', views.GroupViewSet)
    
    # Wire up our API using automatic URL routing.
    # Additionally, we include login URLs for the browsable API.
    urlpatterns = [
        url(r'^', include(router.urls)),
    ]
    
    

    VIewSet常常配合router使用,router可以自动将常用的 get绑定list,post绑定create这些操作完成,而不需要你在as_view中指定对应的关系了,连这个都省去了。

  • 相关阅读:
    wordpress 常用 数据库脚本
    Centos6.3安装locate
    WordPress路径相关函数总结
    关于电子杂志翻页的资源
    CentOS下MongoDB的升级
    WordPress数据库研究
    HOWTO:如何通过ServiceAddService修改已经存在的服务启动参数
    FAQ:Component的属性是否可以运行时修改?
    INFO:AdminStudio Debug
    HOWTO:如何修改InstallShield的运行环境
  • 原文地址:https://www.cnblogs.com/Andy963/p/12357927.html
Copyright © 2020-2023  润新知