20

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.

Aneesh R S
  • 3,807
  • 4
  • 23
  • 35
apatel
  • 611
  • 1
  • 6
  • 16

4 Answers4

24

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.

prawg
  • 511
  • 4
  • 11
  • 2
    While you are right so is the accepted answer. The DRF pagination merely implements django's built-in pagination. Check out the source code at rest_framework.pagination. Look for the `paginate_queryset` definition – ryechus Jul 14 '15 at 16:31
  • 1
    Excellent addition ryechus. I hadn't looked at that part of the source code but the backbone of django-rest-framework's pagination is definitely built on the standard Django paginator from django.core.paginator. I edited my previous response. – prawg Jul 14 '15 at 18:50
  • 2
    Everyting is okay, but it should return page not cart_details, easy fix: `if page is not None: serializer = CartDetailSerializer(page, many=True)` and pagination class coming from DRF with `from rest_framework.settings import api_settings` and `pagination_class = api_settings.DEFAULT_PAGINATION_CLASS` – metoikos Jan 22 '16 at 14:06
  • I got this error. 'Settings' object has no attribute 'DEFAULT_PAGINATION_CLASS' What do I need to do in Setting? – Khant Thu Linn Feb 20 '16 at 15:39
  • This answer should help you https://stackoverflow.com/a/35525458/3045403 – dismine Dec 01 '17 at 12:55
  • Also, if you're returning none like me, make sure to set the default page size in your settings :) src: https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py – Braden Holt Apr 04 '18 at 16:58
21

When using regular APIView, you need to use Django's own Paginator class.

Django Pagination in Views

In your case you can paginate queryset before sending it to serializer.

Something like this:

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

    paginator = Paginator(cart_details, 10)
    page = request.GET.get('page')

    try:
        cart_details = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        cart_details = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        cart_details = paginator.page(paginator.num_pages)
    serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
    return Response(serializer.data)

Hope this helps.

Aakash Rayate
  • 934
  • 7
  • 17
4

I prefer extending the Paginator class, here is how it would look:

from rest_framework import status
from rest_framework.exceptions import NotFound as NotFoundError
from rest_framework.pagination import PageNumberPagination # Any other type works as well
from rest_framework.response import Response
from rest_framework.views import APIView

class CustomPaginator(PageNumberPagination):
    page_size = 10 # Number of objects to return in one page

    def generate_response(self, query_set, serializer_obj, request):
        try:
            page_data = self.paginate_queryset(query_set, request)
        except NotFoundError:
            return Response({"error": "No results found for the requested page"}, status=status.HTTP_400_BAD_REQUEST)

        serialized_page = serializer_obj(page_data, many=True)
        return self.get_paginated_response(serialized_page.data)

class CartView(APIView):

    def get(self, request, format=None):
        cart_details = Cart.objects.filter(user=request.user) # or any other query
        paginator = CustomPaginator()
        response = paginator.generate_response(cart_details, CartDetailSerializer, request)
        return response
NFern
  • 1,706
  • 17
  • 18
1

I am using DRF version 3.6.2. You don't need to code so much. Just use this simple steps.

 class ProductPagination(PageNumberPagination):
        page_size = 5

    class product_api(generics.ListCreateAPIView):    
            queryset = Products.objects.all()
            serializer_class = product_serilizer
            pagination_class = ProductPagination

if you want search functionality by getting method, you can write below code

class ProductPagination(PageNumberPagination):
        page_size = 5

class product_api(generics.ListCreateAPIView):
    queryset = Products.objects.all()
    serializer_class = product_serilizer
    pagination_class = SearchProductPagination    

    def get_queryset(self):
        qs = super(product_search_api,self).get_queryset()
        searched_product = self.request.query_params.get('searched_product',None)
        if search:
            qs = Products.objects.filter(Q(product_name__icontains= searched_product))
        return qs
Nids Barthwal
  • 2,205
  • 20
  • 12