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
We need pagination of course. Let have a try.
Setup (also is GLOBAL level)
First we add django system config:
REST_FRAMEWORK = {
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ 'PAGE_SIZE': 5
}
ViewSet level
Now it works on all list endpoints. But how to specify some endpoint's page size?
The official document tell us to define a class implementate Pagination class:
class LittleResultsSetPagination(PageNumberPagination):
page_size = 2
max_page_size = 10
class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, UserCanOnlyModifyHimSelfPermission]
pagination_class = LittleResultsSetPagination
Maybe almost every list endpoint need pagination. Only one want return all data. How to implementate?
class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, UserCanOnlyModifyHimSelfPermission]
pagination_class = None # Intuitive 😂
Let user specify(from url query params)
Now we want users to be able to specify the page size they need but I didn't find it on document. Let find it out from debug the source code.
Because we are using ListModelMixin. So we add a break point at list:
Meaningful method paginate_queryset. I like it.
All the pagination works on this paginator instance.
It will use request.query_params[self.page_size_query_param] to do paginate.Means we need to set this value with user's query_params.
class LittleResultsSetPagination(PageNumberPagination):
page_size_query_param = 'page_size'
class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, UserCanOnlyModifyHimSelfPermission]
pagination_class = LittleResultsSetPagination
It works!
PS: PageNumberPagination also have a lot of attributes we could override. Read the doc!
Custom response field name
Frontend may want response data looks like code below but official doesn't support any docuemnt about this. Let find it out by ourselves.
{
- "count": 9,
+ "total": 9,
- "next": "http://127.0.0.1:22222/user/?page=2",
+ "have_next": true,
- "previous": null,
+ "have_previous": false,
- "results": [
+ "list": [
{
"id": 14
}
]
}
😂 Cannot config! Hard code dict key!
But this method is very short and easy. So we can override it(even can add some other fields):
class LittleResultsSetPagination(PageNumberPagination):
page_size_query_param = 'page_size'
def get_paginated_response(self, data):
from collections import OrderedDict
return Response(OrderedDict([
('total', self.page.paginator.count),
('have_next', bool(self.get_next_link())),
('have_previous', bool(self.get_previous_link())),
('list', data)
]))
At last, you can set this class as django's default, global level PagationClass:
REST_FRAMEWORK = {
- 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ 'DEFAULT_PAGINATION_CLASS': 'your_class_foler.LittleResultsSetPagination',
'PAGE_SIZE': 5
}
Test case
class MyResultsSetPagination(PageNumberPagination):
page_size_query_param = 'pageSize'
page_query_param = 'current'
page_size = 10
def get_paginated_response(self, data):
from collections import OrderedDict
return Response(OrderedDict([
('pageSize', self.get_page_size(self.request)),
('total', self.page.paginator.count),
('current', self.page.number),
('haveNext', bool(self.get_next_link())),
('havePrevious', bool(self.get_previous_link())),
('list', data)
]))
class TestMyResultsSetPagination(TestCase, TransactionTestCase):
databases = {'default'}
def setUp(self):
from django.core.management import call_command
call_command('migrate', verbosity=0, interactive=False, database='default')
users = [User(username=f'pagination_user_{i}') for i in range(17)]
User.objects.bulk_create(users)
def test_get_paginated_response_without_specify_query_params(self):
response = self.client.get('/users/')
self.assertEqual(response.data['total'], User.objects.count())
self.assertEqual(response.data['current'], 1)
self.assertEqual(response.data['pageSize'], MyResultsSetPagination.page_size)
self.assertEqual(len(response.data['list']), MyResultsSetPagination.page_size)
def test_get_paginated_response_with_specify_query_params(self):
response = self.client.get('/users/?pageSize=12')
self.assertEqual(response.data['total'], User.objects.count())
self.assertEqual(response.data['current'], 1)
self.assertEqual(response.data['pageSize'], 12)
self.assertEqual(len(response.data['list']), 12)
response = self.client.get('/users/?pageSize=12¤t=2')
self.assertEqual(response.data['total'], User.objects.count())
self.assertEqual(response.data['current'], 2)
self.assertEqual(response.data['pageSize'], 12)
self.assertEqual(len(response.data['list']), 5)
Thanks for reading!