Pagination in Django-Rest-Framework using API-View

apatel picture apatel · Mar 16, 2015 · Viewed 18.7k times · Source

I currently have an API view setup as follows:

class CartView(APIView):
    authentication_classes = [SessionAuthentication, TokenAuthentication]
    permission_classes = [IsAuthenticated, ]
    api_view = ['GET', 'POST']

    def get(self, request, format=None):
        try:
            cart = request.user.cart
        except Cart.DoesNotExist:
            cart = Cart.objects.create(user=request.user)
        cart_details = cart.cart_details.all()
        serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
        return Response(serializer.data)

Here CartDetailSerializer is a normal ModelSerializer.

I want to paginate this API. However, in the docs of DRF, I found this:

If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response.

There is no example provided on how to paginate a regular APIView API.

Can anyone post an example which I can use in above scenario.

Thanks.

Answer

prawg picture prawg · Jul 14, 2015

While the way rayy mentions is a possibility, django-rest-framework can handle this internally with some additional features that make working with your API much easier. (*note django-rest-framework's pagination is built from the Django paginator from django.core.paginator)

Right after what you quoted is the key information to solving this problem:

Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the mixins.ListMixin and generics.GenericAPIView classes for an example.

Slight correction to what is stated there: look at the ListModelMixin.

If you go to these two links you can see the source code for the above files: generics.py mixins.py

What you need to do is include something like the following to get pagination to work in the APIView (**note: this code is untested but the idea is correct. There is also a better way of writing this rather than having to include the code in every view but I will leave that up to you to keep my answer short and understandable):

from __future__ import absolute_import
# if this is where you store your django-rest-framework settings
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response

from .models import Cart 

class CartView(APIView):
    pagination_class = settings.DEFAULT_PAGINATION_CLASS

    def get(self, request, format=None):
        #assuming every other field in the model has a default value    
        cart = Cart.objects.get_or_create(user=request.user)

        #for a clear example
        cart_details = Cart.objects.all()

        page = self.paginate_queryset(cart_details)
        if page is not None:
            serializer = CartDetailSerializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = CartDetailSerializer(cart_details, many=True)
        return Response(serializer.data)

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

I hope this was of more help to you and others who come across this post.