diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 360ef1a2e..3346c70a3 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -147,6 +147,10 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q Should be mixed in with [MultipleObjectAPIView]. +**Arguments**: + +* `page_size_kwarg` - Allows you to overwrite the global settings `PAGE_SIZE_KWARG` for a specific view. You can also turn it off for a specific view by setting it to `None`. Default is `page_size`. + ## CreateModelMixin Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 4f87b30da..8fce9e4e7 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -150,4 +150,14 @@ Default: `'accept'` Default: `'format'` +## PAGE_SIZE_KWARG + +Allows you to globally pass a page size parameter for an individual request. + +The name of the GET parameter of views which inherit ListModelMixin for requesting data with an individual page size. + +If the value if this setting is `None` the passing a page size is turned off by default. + +Default: `'page_size'` + [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 35e8a8b35..85c19f5bc 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -7,6 +7,7 @@ ## Master * Support for `read_only_fields` on `ModelSerializer` classes. +* Support for individual page sizes per request via `page_size` GET parameter in views which inherit ListModelMixin. ## 2.1.2 diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 53c4d9842..0da4c2cc6 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -7,6 +7,7 @@ which allows mixin classes to be composed in interesting ways. from django.http import Http404 from rest_framework import status from rest_framework.response import Response +from rest_framework.settings import api_settings class CreateModelMixin(object): @@ -39,6 +40,7 @@ class ListModelMixin(object): Should be mixed in with `MultipleObjectAPIView`. """ empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." + page_size_kwarg = api_settings.PAGE_SIZE_KWARG def list(self, request, *args, **kwargs): queryset = self.get_queryset() @@ -64,6 +66,17 @@ class ListModelMixin(object): return Response(serializer.data) + def get_paginate_by(self, queryset): + if self.page_size_kwarg is not None: + page_size_kwarg = self.request.QUERY_PARAMS.get(self.page_size_kwarg) + if page_size_kwarg: + try: + page_size = int(page_size_kwarg) + return page_size + except ValueError: + pass + return super(ListModelMixin, self).get_paginate_by(queryset) + class RetrieveModelMixin(object): """ diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 4f10481de..8883b9637 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -66,7 +66,9 @@ DEFAULTS = { 'URL_ACCEPT_OVERRIDE': 'accept', 'URL_FORMAT_OVERRIDE': 'format', - 'FORMAT_SUFFIX_KWARG': 'format' + 'FORMAT_SUFFIX_KWARG': 'format', + + 'PAGE_SIZE_KWARG': 'page_size' } diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 713a7255b..8aae21471 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -34,6 +34,29 @@ if django_filters: filter_backend = filters.DjangoFilterBackend +class DefaultPageSizeKwargView(generics.ListAPIView): + """ + View for testing default page_size usage + """ + model = BasicModel + + +class CustomPageSizeKwargView(generics.ListAPIView): + """ + View for testing custom page_size usage + """ + model = BasicModel + page_size_kwarg = 'ps' + + +class NonePageSizeKwargView(generics.ListAPIView): + """ + View for testing None page_size usage + """ + model = BasicModel + page_size_kwarg = None + + class IntegrationTestPagination(TestCase): """ Integration tests for paginated list views. @@ -135,7 +158,7 @@ class IntegrationTestPaginationAndFiltering(TestCase): class UnitTestPagination(TestCase): """ - Unit tests for pagination of primative objects. + Unit tests for pagination of primitive objects. """ def setUp(self): @@ -156,3 +179,121 @@ class UnitTestPagination(TestCase): self.assertEquals(serializer.data['next'], None) self.assertEquals(serializer.data['previous'], '?page=2') self.assertEquals(serializer.data['results'], self.objects[20:]) + + +class TestDefaultPageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = DefaultPageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_default_page_size_kwarg(self): + """ + If page_size_kwarg is set not set, the default page_size kwarg should limit per view requests. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data['count'], 13) + self.assertEquals(response.data['results'], self.data[:5]) + + +class TestCustomPageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = CustomPageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_disabled_default_page_size_kwarg(self): + """ + If page_size_kwarg is set set, the default page_size kwarg should not work. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_custom_page_size_kwarg(self): + """ + If page_size_kwarg is set set, the new kwarg should limit per view requests. + """ + request = factory.get('/?ps=5') + response = self.view(request).render() + self.assertEquals(response.data['count'], 13) + self.assertEquals(response.data['results'], self.data[:5]) + + +class TestNonePageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = NonePageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_none_page_size_kwarg(self): + """ + If page_size_kwarg is set to None, custom page_size per request should be disabled. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data, self.data)