Django rest framework global pagination parameters

2019-07-26 22:17发布

问题:

The DRF docs state that:

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

But I'm using a ModelViewSet which inherits from ViewSet, so I tell myself "Cool, all I have to do is add this to my settings.py":

'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 12, # for 12-col grid css frameworks

However, this didn't work.
If I send a GET request for 27 items it returns all of them (in both the browsable API and json).

  • Am I right in thinking I should only be returned 12?
  • Sub-question: PAGE_SIZE is the number of top-level objects returned per page, right? I saw a few examples with PAGINATE_BY but it doesn't feature in the source so I presume it's deprecated?

I'm using DRF 3.6.3, django 1.11.2.

Edit: my settings:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 12,
}

I've also added pagination_class = PageNumberPagination to the ModelViewSet class, without effect.

Here's the verification from the shell that the Pagination class does know the page size it's supposed to provide:

>>> from rest_framework.pagination import PageNumberPagination
>>> p = PageNumberPagination()
>>> p.max_page_size
>>> print(p.page_size)
12

回答1:

It is true that pagination doesn't work by default on a ModelViewSet despite inheriting from GenericViewSet and the ListMixin. You need to add it manually:

I have composed a Q&A style example which attends that problem and have tested it on a ModelViewSet class.

I will summarize it a bit:

  1. Create a custom mixin to utilize the pagination_class:

    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)
    
  2. Make your viewset extend that mixin and override the list() method of the ModelViewSet:

    class MyViewset(MyPaginationMixin, viewsets.ModelViewSet):
        # since you are setting pagination on the settings, use this:
        pagination_class = settings.DEFAULT_PAGINATION_CLASS
    
        def list(self, request, *args, **kwargs):
            response = dict(
                super(MyViewSet, self).list(self, *args, **kwargs).data)
    
            page = self.paginate_queryset(response['results'])
            if page is not None:
                serializer = self.serializer_class(page, many=True)
                return self.get_paginated_response(serializer.data)
            else:
                # Something went wrong here...
    

You need to calibrate this solution to your needs of course, but this will add pagination to a ModelViewSet.


For the subquestion, @Linovia 's comment is correct, PAGINATE_BY is deprecated and PAGE_SIZE is the current setting for the response page size.



回答2:

It should work. If it doesn't and you don't have typo, look at the server logs and you may find something telling you that pagination without order will not work:

xxxx/django/core/paginator.py:112: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: ....

In order to fix that make sure you specify an ordering in the model's Meta or set an order_by within ModelViewSet's queryset.