17

Prologue:

I have seen this question arising in more than one posts:

and can also be applied here:

I have composed an example on SO Documentation to unify my answers in the above questions but since the Documentation will get shutdown on August 8 2017, I will follow the suggestion of this widely upvoted and discussed meta answer and transform my example to a self-answered post.

Of course I would be more than happy to see any different approach as well!!


Question:

I want to use a Non Generic View/Viewset (eg: APIView) on a Django Rest Framework project.
As I read on the pagination documentation:

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.ListModelMixin and generics.GenericAPIView classes for an example.

Can I still continue using a non generic view/viewset?
How can I implement pagination on it?

John Moutafis
  • 22,254
  • 11
  • 68
  • 112

1 Answers1

28

We can find a solution without the need to reinvent the wheel:

  1. Let's have a look on how the generics pagination is implemented: django-rest-framework/rest_framework/generics.py.
    That is exactly what we are going to use to our view as well!

  2. Let's assume that we have a global pagination setup like the following in:
    settings.py:

    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 
            'rest_framework.pagination.DESIRED_PAGINATION_STYLE',
        'PAGE_SIZE': 100
    }
    
  3. In order not to bloat our view/viewset's code, we can create a custom mixin to store our pagination code:

    class MyPaginationMixin(object):
    
        @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)
    
  4. Then on views.py:

    from rest_framework.settings import api_settings
    from rest_framework.views import APIView
    
    from my_app.mixins import MyPaginationMixin
    
    class MyView(APIView, MyPaginationMixin):
        queryset = OurModel.objects.all()
        serializer_class = OurModelSerializer
        pagination_class = api_settings.DEFAULT_PAGINATION_CLASS 
    
        # We need to override the get method to insert pagination
        def get(self, request):
            ...
            page = self.paginate_queryset(self.queryset)
            if page is not None:
                serializer = self.serializer_class(page, many=True)
                return self.get_paginated_response(serializer.data)
    

And now we have an APIView with pagination.

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • 'NoneType' object has no attribute 'paginate_queryset'. im getting this error – Thameem Aug 21 '17 at 08:21
  • 1
    @Thameem It's a bit difficult to help you without some code to see what you have done... If the issue persists, consider making a question here on SO. – John Moutafis Aug 21 '17 at 11:51
  • @thanks for your message i missed this response return self.get_paginated_response(serializer.data). thanks. its working now – Thameem Aug 21 '17 at 12:05
  • @JohnMoutafis I've changed the 'PAGE_SIZE' to 10, but it gives me 100 items – Edwin Harly Jul 20 '18 at 06:54
  • 3
    This page is still saving lives to this day. – Opeyemi Odedeyi Feb 22 '20 at 17:07
  • By the way, Have you tried combining this (APIView, pagination) with filters in Django? That is filtering (filter search) multiple fields of a model in Django – Opeyemi Odedeyi Feb 23 '20 at 00:40
  • 1
    @OpeyemiOdedeyi I have tried using the `django-filters` module: django-filter.readthedocs.io/en/master and it worked fine: https://stackoverflow.com/questions/44048156/django-filter-use-paginations/44541540#44541540 (updated comment with a corresponding link) – John Moutafis Feb 26 '20 at 21:46