zoukankan      html  css  js  c++  java
  • Django REST Framework API Guide 02

    本节大纲

      1、Generic Views

      2、ViewSets 

    1、Generic Views

    CBV的主要的一个优点就是极大的允许了对于代码的从用。自然,rest framework取其优势,提供了很多可以重构的视图。rest framework 提供的 Generic Views可以让你很快速的构建跟数据库模型映射紧密的API视图。

    如果 generic view不满足你的API需求,很简单,你可以放弃它去使用正常的APIView类,或者将generic view内部包含的mixins和基础类重构成自己需要的,完成对于generic view的重用

    样例

    class StudentListView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentListSerializer
    
    ###########################################serilizers,单独建立一个serializers文件
    from rest_framework.serializers import ModelSerializer
    from app01.models import Student
    
    class StudentListSerializer(ModelSerializer):
    
        class Meta:
            model = Student
            fields = ('name', 'age', 'gender', 'uuid')
    
    
    ###########################################url
    
        path('student/', StudentListView.as_view(), name='list-student'),

    对于更复杂的需求,你可能需要重写各种各样的方法在你的视图类上,例如

    class StudentListView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentListSerializer
    
        def list(self, request, *args, **kwargs):
            queryset = self.get_queryset()
            serializer = StudentListSerializer(queryset, many=True)
            return Response(serializer.data)

    如上、我们重写了list方法,这是查看一下源码会发现这是ListAPIView的get方法调用的方法。

    对于一些简单的例子,你可以通过as_view()方法传入各种属性,例如

    path('student/', StudentListView.as_view(queryset = Student.objects.all(), serializer_class = StudentListSerializer), name='list-student'),

    API Reference

    GenericAPIView继承并扩展了APIView类,添加了很多对于标准的列表跟详细视图的常用的行为,其中每一个具体的generic views都是由GenericAPIView和其他一个或者多个mixin类组合而成。

    Attributes

    Basic settings

    下面的属性控制这基础视图的行为。

    1、queryset

    用来返回一个视图的对象。你必须设置这个属性,或者是重写get_queryset()方法。如果你在重写一个视图方法,调用get_queryset()比直接用这个属性更好,因为queryset会一次获取整体的范围,这些数据将被缓存给各个随后的请求

    2、serializer_class

    序列化的类,用来验证,反序列化输入和序列化输出。同样的可以设置这个属性,或者是重写get_serializer_class()方法

    3、lookup_field

    这个模型字段应该被用来给对象查找单独的模型实例,默认是pk,如果url上的匹配也是pk,视图会自动筛选出主键值等于pk值的单独模型实例。

    4、lookup_url_kwarg

    URL关键字参数,可以被用来查找对象,url设置里面用该包含一个关键字对应的值,如果没有设置,默认使用lookup_field.

    Pagination

    接下来的方法主要是和list view相关的分页。

    1、pagination_class

    用来对list结果进行分页。可以在setting里面配置用'rest_framework.pagination.PageNumberPagination'来配置一个'DEFAULT_PAGINATION_CLASS'参数

    Filtering

    1、filter_backends

    有很多可用的filter backend类可以用来对queryset进行筛选。默认可以再settings文件中设置DEFAULT_FILTER_BACKENDS

    Methods

    Base methods

    1、get_query(self)

    返回一个可以被list视图使用的queryset,并且作为后面查询具体视图的基础,默认返回queryset由queryset属性控制

    def get_queryset(self):
        user = self.request.user
        return user.accounts.all()

    2、get_object(self)

    返回一个给具体视图(detail view)使用的对象实例。默认用lookup_field参数来筛选基础的queryset,可以被重写来提供更复杂的行为。

    def get_object(self):
        queryset = self.get_queryset()
        filter = {}
        for field in self.multiple_lookup_fields:
            filter[field] = self.kwargs[field]
    
        obj = get_object_or_404(queryset, **filter)
        self.check_object_permissions(self.request, obj)
        return obj

    如果你的api没有权限认证,那可以直接丢弃掉最后一句self.check_object_permissions(self.request, obj)

    3、filter_queryset

    传入一个queryset,通过在使用的filter backends来筛选它,返回一个新的queryset

    def filter_queryset(self, queryset):
        filter_backends = (CategoryFilter,)
    
        if 'geo_route' in self.request.query_params:
            filter_backends = (GeoRouteFilter, CategoryFilter)
        elif 'geo_point' in self.request.query_params:
            filter_backends = (GeoPointFilter, CategoryFilter)
    
        for backend in list(filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, view=self)
    
        return queryset

    4、get_serializer_class(self)

    返回一个序列化的类,默认返回serializer_class属性也可以重写成动态行为

    def get_serializer_class(self):
        if self.request.user.is_staff:
            return FullAccountSerializer
        return BasicAccountSerializer

    Save and deletion hooks(保存和删除的钩子)

    下面这些方法是mixin类提供的,提供了对对象的保存和删除行为简易重写

    .perform_create(self, serializer)       ==> 当保存一个新的实例对象时由CreateModelMixin发起

    .perform_update(self, serializer)   ==> 当更新一个已有的实例对象时由UpdateModelmixin发起

    .perform_destory(self, instance)   ==> 当删除一个实例对象时由DestoryModelMixin发起

    当你需要的数据在request里面,但是不是request.data的一部分的时候,这些钩子就会变得特别有用,

    比如你想记录request.user到实例里面

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    比如你想在保存操作的同时发送一封邮件出去

    def perform_update(self, serializer):
        instance = serializer.save()
        send_email_confirmation(user=self.request.user, modified=instance)

    比如利用这些钩子提供额外的验证在数据库存储数据之前,并可以引发ValidationError()

    def perform_create(self, serializer):
        queryset = SignupRequest.objects.filter(user=self.request.user)
        if queryset.exists():
            raise ValidationError('You have already signed up')
        serializer.save(user=self.request.user)

    注意,这些方法代替了2.X版本里面的pre_save, post_save, pre_delete post_delete方法,它们不再可用

    在GenericAPIView里面,顺便跟下面的方法有个眼缘。。

    get_serializer_context(self)  #返回一个字典包含任何额外的数据,需要添加到serializer里面的
    
    get_serializer(self, instance=None, data=None, many=False, partial=False)  # 返回一个serializer实例
    
    get_paginated_response(self, data)  # 返回一个分页格式的Response对象
    
    paginate_queryset(self, queryset)  # 对有需求的queryset进行分页,返回一个page对象或者空None,如果没有配置

    Mixins

    mixin类提供了一些是用来提供基础视图行为的措施。注意是mixin类提供的行为方法,而不是自定义处理方法,比如.get()或者.post(). 它允许更加灵活的组合行为

    导入

    rest_framework.mixins

    ListModelMixin

    提供.list(request, *args, **kwargs)方法,正常会返回200 ok的返回和序列化的queryset作为body,返回的数据可以被分页。

    CreateModelMixin

    提供了.create(request, *args, **kwargs)方法,正常返回201 created返回和序列化的对象作为body. 如果返回的数据不合法,返回400和错误的详细作为body

    RetrieveModelMixin

    提供.Retrieve(request, *args, **kwargs)方法,正常返回200 ok和创建好的序列化实例. 否则返回404 Not Found.

    UpdateModelMixin

    提供.update(request, *args, **kwargs)方法,更新现有的实例。也提供了.partial_update(request, *args, **kwargs)方法,跟update很像,除了所有的更新字段都是可选的。允许支持patch请求。正常返回200 ok返回,序列化的对象。否则400 bad request.

    DestoryModelMixin

    .destory(request, *args, **kwargs)方法,正常204 No Content返回,否则404 Not Found.

    Concrete View Classes

    CreateAPIView

    只创建;支持post方法处理;扩展:GenericAPIView, CreateModelMixin

    ListAPIView

    只读,展示一组模型实例;支持get方法处理;扩展:GenericAPIView, ListModelMixin

    RetrieveAPIView

    只读,展示一个单独的模型实例,支持get方法处理;扩展:GenericAPIView, RetrieveModelMixin

    DestoryAPIView

    只删除,针对单独的模型实例. 支持put和patch方法处理;扩展:GenericAPIView, UpdateModelMixin

    UpdateAPIView

    只更新,针对单独模型实例. 支持put和patch方法处理;扩展:GenericAPIView, UpdateModelMixin

    ListCreateAPIView

    读写,展示一组模型实例. get和post方法处理;扩展:GenericAPIView,ListModelMIxin,CreateModelMixin

    RetrieveUpdateAPIView

    读或者更新,展示一个独立的模型实例. get, put和patch方法处理;扩展:GenericAPIView, RetrieveModelMixin,UpdateModelMixin

    RetrieveDestoryAPIView

    读或删除,展示一个独立的模型实例. get和delete方法处理;扩展:GenericAPIView,RetrieveModelMixin, DestoryModelMIxin

    RetrieveUpdateDestoryAPIView

    读或者删除,展示一个独立的模型实现. get和delete方法处理;扩展:GenericAPIView,RetrieveModelMixin,DestoryModelMixin

    RetrieveUpdateDestoryAPIView

    读写删除,展示一个独立的模型实例. get, put, patch和delete方法处理;扩展:GenericAPIView,RetrieveMinxin,UpdateModelMixin,DestoryModelMixin

    Customizing the generic views

    Creating custom mixins

    比如,需要查找对象基于多个url字段,你可以创建一个mixin类如下:

    class MultipleFieldLookupMixin(object):
        """
        Apply this mixin to any view or viewset to get multiple field filtering
        based on a `lookup_fields` attribute, instead of the default single field filtering.
        """
        def get_object(self):
            queryset = self.get_queryset()             # Get the base queryset
            queryset = self.filter_queryset(queryset)  # Apply any filter backends
            filter = {}
            for field in self.lookup_fields:
                if self.kwargs[field]: # Ignore empty fields.
                    filter[field] = self.kwargs[field]
            obj = get_object_or_404(queryset, **filter)  # Lookup the object
            self.check_object_permissions(self.request, obj)
            return obj

    你可以简单得将这个mixin应用在view或者viewset,当你想需要应用这个客制化行为的任何时候

    class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer
        lookup_fields = ('account', 'username')

    Creating Custom Base Classes

    如果你使用mixin链接多个视图,可以用下面的这种更进一步创建你自己的基础视图,贯穿你的项目

    class BaseRetrieveView(MultipleFieldLookupMixin,
                           generics.RetrieveAPIView):
        pass
    
    class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin,
                                        generics.RetrieveUpdateDestroyAPIView):
        pass

    Put As Create

    3.0版本的rest framework mixins 对待put即是更新也是创建,主要依赖于你的项目是不是已经存在

    2、ViewSets

    首先需要记住的一点是viewset是一种简单的CBV,不提供任何处理方法比如.get()或者.post(),而是用.list().create()来替代,这种东西翻一下源码就知道了,当然熟悉了也就不需要特别记忆了。

    还有一点值得注意的是相比于直接注册带有视图集在url设置里,使用路由类注册视图集将会是更好的选择,他将自动的为你决定url的设置,例如

    from rest_framework.generics import get_object_or_404
    from app01.serializers import PersonModelSerializer
    from rest_framework.viewsets import ModelViewSet, ViewSet
    from rest_framework.response import Response
    from app01.models import PersonResource
    
    
    class StudentViewSet(ViewSet):
        def list(self, request):
            queryset = PersonResource.objects.filter(job=1)
            serializer = PersonModelSerializer(queryset, many=True)
            return Response(serializer.data)
    
        def retrieve(self, request, pk=None):
            queryset = PersonResource.objects.filter(job=1)
            student = get_object_or_404(queryset, pk=pk)
            serializer = PersonModelSerializer(student)
            return Response(serializer.data)

    如果需要的话,可以丙丁这个视图集到两个分开的视图,如下

    student_list = StudentViewSet.as_view({'get': 'list'})
    student_detail = StudentViewSet.as_view({'get': 'retrieve'})

    但实际上不会这么做,而是注册一个路由绑定视图集来替代,这允许了url自动生成

    from rest_framework.routers import DefaultRouter
    from app01.customviewset import StudentViewSet
    
    
    router = DefaultRouter()
    router.register(r'student', StudentViewSet, base_name='student-viewset')
    
    
    urlpatterns = [
        ......
    ]
    
    urlpatterns += router.urls

    此时就已经借助ViewSet完成了2类API

    http://127.0.0.1:8001/api/student/
    http://127.0.0.1:8001/api/student/1/

     下面截图可以看到具体提供的URL格式,带format的就不说了,看了应该懂,只看对目前有用的就行

    在使用viewset来完成此API工作的时候,可以发现还是有很多重复的变量申明的,对于追求极致的API来说显然是不允许的,所以接下来修改成ModelViewSet

    class StudentViewSet(ModelViewSet):
        queryset = PersonResource.objects.filter(job=1)
        serializer_class = PersonModelSerializer

    其实这个定义跟上面讲的封装的高级视图很相似。

    ViewSet Actions

    class UserViewSet(viewsets.ViewSet):
        """
        Example empty viewset demonstrating the standard
        actions that will be handled by a router class.
    
        If you're using format suffixes, make sure to also include
        the `format=None` keyword argument for each action.
        """
    
        def list(self, request):
            pass
    
        def create(self, request):
            pass
    
        def retrieve(self, request, pk=None):
            pass
    
        def update(self, request, pk=None):
            pass
    
        def partial_update(self, request, pk=None):
            pass
    
        def destroy(self, request, pk=None):
            pass

    Introspecting ViewSet Actions

    在dispatch的时候,下面的属性在视图集内是可用的

        basename - the base to use for the URL names that are created.
        action - the name of the current action (e.g., list, create).
        detail - boolean indicating if the current action is configured for a list or detail view.
        suffix - the display suffix for the viewset type - mirrors the detail attribute.

    你可以注入下面的属性来调整当前操作的行为,比如限制权限

    def get_permissions(self):
        """
        Instantiates and returns the list of permissions that this view requires.
        """
        if self.action == 'list':
            permission_classes = [IsAuthenticated]
        else:
            permission_classes = [IsAdmin]
        return [permission() for permission in permission_classes]

    Marking extra actions format routinng

    如果你有特殊的方法应该是可路由的,你可以用@action装饰器来标记它们。与常规操作一样,额外的操作可以用于对象列表或单个实例。通过detail参数的True、False标示这个。通过DefaultRouter来配置action,包含pk在URL里面。

    class StudentViewSet(ModelViewSet):
        queryset = PersonResource.objects.filter(job=1)
        serializer_class = PersonModelSerializer
    
        @action(methods=['post'], detail=True)
        def change_age(self, request, pk=None):
            student = self.get_object()
            student.age += 1
            student.save()
            return Response({'status': '%s age changed.' % student.name})
    
        @action(detail=False)
        def ordering(self, request):
            student = PersonResource.objects.filter(job=1).order_by('modify_time')
    
            page = self.paginate_queryset(student)
            if page is not None:
                serializer = self.get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            serializer = self.get_serializer(student, many=True)
            return Response(serializer.data)

    注意,我们一直使用的是DefaultRouter,而不是url里面自己定义。。当然上面的这种写法有点烦。。因为Detail=True的是只允许了post方法,可以改成get方法

    可以随便打一个错误的url来看一下链接

    id=1指向的是dandy,此时

    也可以在装饰器里面添加额外的参数,这边就直接copy官方文档的了,随便看看

      @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
        def set_password(self, request, pk=None):
           ...

    装饰器默认是get请求们也可以自己添加

        @action(methods=['post', 'delete'], detail=True)
        def unset_password(self, request, pk=None):
           ...


    Reversing action URLS

    本身正常情况下,在django里面,我们通过url里面的name进行url反转,再特殊一点,如果遇到两个一样的name,会根据命名空间appname来进行区别。而如果使用了viewset视图集,可以抛弃.reverse()方法,使用再次封装的.reverse_action()进行url反转。

        @action(methods=['get', 'put'], detail=True)
        def change_age(self, request, pk=None):
            student = self.get_object()
            student.age += 1
            student.save()
            return Response({
                'status': '%s age changed.' % student.name,
                'url': self.reverse_action('change-age', args=[pk])
            })

    注意 反转的basename其实就是action装饰器的函数名,尤其是当函数名跟上面的实例一样,有下划线_的时候,需要转换成-,别掉进坑。。

    或者有一种更简单的方法

        @action(methods=['get', 'put'], detail=True)
        def change_age(self, request, pk=None):
            student = self.get_object()
            student.age += 1
            student.save()
            return Response({
                'status': '%s age changed.' % student.name,
                'url': self.reverse_action('change-age', args=[pk]),
                'url1': self.reverse_action(self.change_age.url_name, args=[pk])
            })

    API Reference

    ViewSet

    ViewSet继承了APIView,你可以使用APIView的提供的任何标准的属性,比如permission_classesauthentication_classes...

    视图集类不能提供任何执行操作,为了使用视图集类,你可以直白地重写类或者定义执行操作。

    GenericViewSet

    GenericViewSet继承自GenericAPIView,包含get_object(), get_queryset()方法和其他generic view基础行为,默认不包含操作。同样地,为了使用GenericViewSet类,你需要直白的重写类无论是mixin还是自定义执行操作。

    ModelViewSet

    继承自GenericAPIView,包含各种执行操作,源自各种各样的mixin类。

    ModelViewSet提供:.list(), .retrieve(), .create(), .update(), .partial_update()和.destory()

    因为是扩展了GenericAPIView,所以有很多属性可以使用,下面的几个示例都不是很重要,直接copy的官方文档

    class AccountViewSet(viewsets.ModelViewSet):
        """
        A simple ViewSet for viewing and editing accounts.
        """
        queryset = Account.objects.all()
        serializer_class = AccountSerializer
        permission_classes = [IsAccountAdminOrReadOnly]

    或者同样的跟GenericAPIView的其他扩展类一样。

    class AccountViewSet(viewsets.ModelViewSet):
        """
        A simple ViewSet for viewing and editing the accounts
        associated with the user.
        """
        serializer_class = AccountSerializer
        permission_classes = [IsAccountAdminOrReadOnly]
    
        def get_queryset(self):
            return self.request.user.accounts.all()

    ReadOnlyModelViewSet

    继承自GenericAPIView,但是从名字就可以猜到read-only,所以支持.list(), .retrieve()方法

    class AccountViewSet(viewsets.ReadOnlyModelViewSet):
        """
        A simple ViewSet for viewing accounts.
        """
        queryset = Account.objects.all()
        serializer_class = AccountSerializer

    Custome ViewSet base classes

    有时你需要提供客制化的视图集类,而ModelViewSet并没有满足,就需要自定制。

    from rest_framework import mixins
    
    class CreateListRetrieveViewSet(mixins.CreateModelMixin,
                                    mixins.ListModelMixin,
                                    mixins.RetrieveModelMixin,
                                    viewsets.GenericViewSet):
        """
        A viewset that provides `retrieve`, `create`, and `list` actions.
    
        To use it, override the class and set the `.queryset` and
        `.serializer_class` attributes.
        """
        pass

    这里,我们讲GenericView跟ViewSet放在了一起,因为这其中有很多很多相似的地方,如果每一个都看示例,翻源码就很好理解了,如果只是一笔看过,可能会混淆掉其中的内容,当然,如果真正做到看rest framework这一块,相信在这个领域已经不算是小白了,源码随便看看应该是可以的。

  • 相关阅读:
    P4315 月下“毛景树”
    P1505 [国家集训队]旅游
    P3258 [JLOI2014]松鼠的新家
    P4116 Qtree3
    P2580 于是他错误的点名开始了
    P3038 [USACO11DEC]牧草种植Grass Planting
    P3128 [USACO15DEC]最大流Max Flow
    P2146 [NOI2015]软件包管理器
    P2590 [ZJOI2008]树的统计
    P3384 【模板】树链剖分
  • 原文地址:https://www.cnblogs.com/wuzdandz/p/9615806.html
Copyright © 2011-2022 走看看