Combine ListModelMixin with APIView to show pagina

2019-09-14 18:48发布

问题:

I want to show the pagination feature in my API and I am using APIView with multiple serializers.
I know it is very easy to show pagination with ListView.
I have seen somewhere that combining ListModelMixin and APIView works but if my code is as follows:

class ListModelMixin(object):
    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(serilaizer.data)

class ItemsAPIView(APIView):
    permission_classes = (permissions.IsAuthenticated,)
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    def get(self, request, format=None):
        """
        Return a list of all devices of this user.
        """
        reply = {}
        try:
            products = BaseItem.objects.owned_items().filter(owner=request.user)
            reply['data'] = OwnedItemSerializer(products, many=True).data

            items = BaseItem.objects.dev_items().filter(owner=request.user)
            reply['data'].extend(ItemSerializer(items, many=True).data)

        except:
            reply['data'] = []
        return Response(reply, status.HTTP_200_OK)

How can I combine them so I can get paginated results?
Thanks in advance!

回答1:

First things first, what you are currently doing is too complex without reason.

In order to achieve a "paginatable" queryset, it is preferred to change your owned_items() and dev_items() in simple filter combinations, rather than model methods. To clarify by example:

products = BaseItem.objects.filter(owner=request.user, owned=True)

instead of

products = BaseItem.objects.owned_items().filter(owner=request.user)

That way, you can produce a single queryset which will be easier to paginate:

user_items = BaseItem.objects.filter(
                 Q(owner=request.user, owned=True) |
                 Q(owner=request.user, dev=True)
             )

Note 1: You can simplify things further if you like, but that gets out of scope of your question. As food for thought, check this out:

user_items =  BaseItem.objects.filter(owner=request.user).distinct()

Note 2: You should use a single serializer for a single model because what you are doing adds complexity without benefit (high risk-low reward situation)


With the above mentioned and assumed:

There are some ways to achieve what you want here:

  1. By utilizing GeneriAPIView and ListModelMixin you can refactor your class in such a way to have a .list() method with auto-pagination:

    from rest_framework import mixins, generics
    
    class ItemsAPIView(mixins.ListModelMixin, generics.GenericAPIView,):
        permission_classes = (permissions.IsAuthenticated,)
        pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
        serializer_class = OwnedItemSerializer
    
        # You can define .get in here if you really need it.
        # You can also override .list to add specific functionality
    
  2. If you don't want to use the above, and you want to keep your APIView, then you can keep your get method and provide pagination for it as mentioned in this Q&A example:

    class ItemsAPIView(APIView):
        permission_classes = (permissions.IsAuthenticated,)
        pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
        serializer_class = MyNewUnifiedSerializerClass
    
        def get(self, request):
            user_items =  BaseItem.objects.filter(
                              owner=request.user
                          ).distinct()
            page = self.paginate_queryset(user_items)
    
            if page is not None:
                serializer = self.serializer_class(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            serializer = self.get_serializer(user_items, many=True)
            return Response(serializer.data)
    
        # Now add the pagination handlers taken from 
        #  django-rest-framework/rest_framework/generics.py
    
        @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)