80

I have a ViewSet like this one to list users' data:

class Foo(viewsets.ViewSet):

    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

I want to turn on pagination like the default pagination for ModelViewSet:

{
    "count": 55,
    "next": "http://myUrl/?page=2",
    "previous": null,
    "results": [{...},{...},...,{...}]
}

The official doc says:

Pagination is only performed automatically if you're using the generic views or viewsets

...but my resultset is not paginated at all. How can I paginate it?

Moppo
  • 18,797
  • 5
  • 65
  • 64
floatingpurr
  • 7,749
  • 9
  • 46
  • 106
  • Here is a very [simple answer](https://stackoverflow.com/a/65301286/13168560) from a similar Stack Overflow question. – Yonas Dec 15 '20 at 07:05

7 Answers7

95

For those using DRF 3.1 or higher, they are changing the default way pagination is handled. See http://www.django-rest-framework.org/topics/3.1-announcement/ for details.

Now if you want to enable pagination for a ModelViewSet you can either do it globally by setting in your settings.py file:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100
}

Or if you just want it for one ModelViewSet you can manually set the pagination_class for just that viewset.

from rest_framework.pagination import PageNumberPagination

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 100
    page_size_query_param = 'page_size'
    max_page_size = 1000

class FooViewSet(viewsets.ModelViewSet):
    pagination_class = StandardResultsSetPagination

This also allows you to tweak the way the pagination is handled for just that viewset.

DRF 3.1 also has introduced new types of default pagination schemes that you can use such as LimitOffset and Cursor.

jeffjv
  • 3,461
  • 2
  • 21
  • 28
  • 1
    If you ask for page1 using PageNumberPagination, and after that a new item is added to the list, and you then ask for page2, the last item you just got in page1 will be shown again as the first item in page2 (shown 2 times in a row). CursorPagination is a much more recommended way than PageNumberPagination. CursorPagination keeps reference to objects, and does not have to calculate content for each page. For implementation see https://stackoverflow.com/a/47657610/5881884 – DevB2F Dec 05 '17 at 16:09
73

Pagination is only performed automatically if you're using the generic views or viewsets

The first roadblock is translating the docs to english. What they intended to convey is that you desire a generic viewset. The generic viewsets extend from generic ApiViews which have extra class methods for paginating querysets and responses.

Additionally, you're providing your own list method, but the default pagination process is actually handled by the mixin:

class ListModelMixin(object):
    """
    List a queryset.
    """
    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(serializer.data)

The easy solution, use the framework code:

class Foo(mixins.ListModelMixin, viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer = UserSerializer

The more complex solution would be if you need a custom list method, then you should write it as you see fit but in the style of the above mixin code snippet.

Mark Galloway
  • 4,050
  • 19
  • 26
8

Try providing a class variable

paginate_by = 10 #This will paginate by 10 results per page.

Create a Custom ViewSet which performs only list operation as your case for here currently.

class ListModelViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    pass

Now inherit your class Foo with this custom made viewset

class Foo(ListModelViewSet):

    paginate_by = 10

    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

This should help you get the pagination working.

Arpit Goyal
  • 2,212
  • 11
  • 31
  • I added `paginate_by = 10` but It does not work. If I change `class Foo(viewsets.ViewSet):` with `class Foo(generics.ListCreateAPIView):` I get this error: _as_view() takes exactly 1 argument (3 given)_ – floatingpurr Aug 03 '15 at 12:20
  • 1
    are you passing any argument to .as_view()? Secondly why are you not using `ModelViewSet`? – Arpit Goyal Aug 03 '15 at 12:26
  • No, nothing to .as_views(). I'm not using `ModelViewSet` because I need to code some logic to filter a subset of my `queryset` and to add some data to resultset not related to User model (that logic is omitted in the code of my question) – floatingpurr Aug 03 '15 at 12:30
  • 1
    Check this link : http://www.django-rest-framework.org/api-guide/viewsets/#custom-viewset-base-classes – Arpit Goyal Aug 03 '15 at 12:35
  • Still not working :( P.S.: why did you use `MongoGenericViewSet`? I used `viewsets.GenericViewSet` instead... – floatingpurr Aug 03 '15 at 13:48
  • I am sorry actually my project involved `mongo` hence I told to import `MongoGenericViewSet`. You can rather try `viewsets.GenericViewSet` – Arpit Goyal Aug 04 '15 at 06:45
  • I tried it yesterday and I don't remember exactly what was the problem. I am quite new to Python and to DRF, hence I do not exclude that I made mistakes while testing your solution...I solved with `class Foo(mixins.ListModelMixin, viewsets.GenericViewSet):`. – floatingpurr Aug 04 '15 at 07:25
7

This way also worked in Django Rest apiViews Api's

from rest_framework.pagination import PageNumberPagination
class FooList(APIView):

    page_size = 10

    def get(self, request):
        foo = Foo.objects.all()
        paginator = PageNumberPagination()
        paginator.page_size = page_size
        result_page = paginator.paginate_queryset(foo, request)
        serializer = FooSerializer(result_page, many=True)
        return paginator.get_paginated_response(serializer.data)
Neeraj Kumar
  • 133
  • 1
  • 4
4

Pagination in DRF using viewsets and list.

Here I have handled a exception If page is empty it will show empty records.

In setting define the page size, this page size is global and it is used by paginator_queryset in view.

REST_FRAMEWORK = { 'PAGE_SIZE': 10, }

In view.py:

from rest_framework import mixins, viewsets

class SittingViewSet(viewsets.GenericViewSet,
    mixins.ListModelMixin):

    serializer_class = SittingSerializer
    queryset = Sitting.objects.all()
    serializer = serializer_class(queryset, many=True)

    def list(self, request, *args, **kwargs):
        queryset =self.filter_queryset(Sitting.objects.all().order_by('id'))

        page = request.GET.get('page')

        try: 
            page = self.paginate_queryset(queryset)
        except Exception as e:
            page = []
            data = page
            return Response({
                "status": status.HTTP_200_OK,
                "message": 'No more record.',
                "data" : data
                })

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

        # serializer = self.get_serializer(queryset, many=True)
        return Response({
            "status": status.HTTP_200_OK,
            "message": 'Sitting records.',
            "data" : data
        })

Note: If you not use Order_by it will show exception because this list gives unordered list.

Vinay Kumar
  • 1,199
  • 13
  • 16
  • YOU can also visit https://stackoverflow.com/a/46173281/8231158 I have give two answer one foe Viewset and other is for API view. – Vinay Kumar Sep 12 '17 at 09:58
  • 1
    queryset = Sitting.objects.all() , isn't it wrong to get all the records and then paginate based on it? What if there are millions of records, wouldn't it impact performance? – Sandhu Jul 16 '19 at 13:00
  • Sitting.objects.all() this was a example @Sandhu as per your requirement you can filter the record, you can write your own query as per your requirement – Vinay Kumar Jul 18 '19 at 10:08
  • 1
    You should return `200 OK` with an empty list because both URL and collection do exist in the service but none matched the query arguments. Check https://stackoverflow.com/questions/13366730 – JP Ventura Nov 24 '21 at 12:51
4

A slightly simpler variation on this answer if you want pagination for a particular ViewSet, but don't need to customize the page size:

REST_FRAMEWORK = {
    'PAGE_SIZE': 100
}

class FooViewSet(viewsets.ModelViewSet):
    pagination_class = PageNumberPagination
morningstar
  • 8,952
  • 6
  • 31
  • 42
2

Custom Pagination using GenericAPIView
1.view.py file

class ModelView(GenericAPIView):
    pagination_class = CustomPagination
    def post(self, request):
        data = request.data
        example_data = data.get('comment_id')
        replies = ExampleModel.objects.filter(model_field=example_data).order_by('-id')
        if replies.exists():
            replies = self.paginate_queryset(replies)
            replies = self.get_paginated_response(replies)
            return replies
        else:
            return Response({'message': 'This is the reply message!'}, status=status.HTTP_200_OK)

2.pagination.py file

class CustomPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 1000
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.page.next_page_number() if self.page.has_next() else None,
                'previous': self.page.previous_page_number() if self.page.has_previous() else None
            },
            'total': self.page.paginator.count,
            'page': int(self.request.GET.get('page', 1)), 
            'page_size': int(self.request.GET.get('page_size', self.page_size)),
            'results': ModelSerializer(data, context={'request':self.request}, many=True).data
        }, status=status.HTTP_200_OK)

3.my_serializer.py file

class ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        request = self.context.get('request')
        if instance.user_information.image else None
            ret['user_name'] = instance.user_information.name
 
        return ret

It's all that you need.

Shaiful Islam
  • 335
  • 2
  • 12