diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 1051dc5b22..36e0d470f3 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -313,13 +313,22 @@ def get_paginated_response(self, data): ('results', data) ])) + def get_zero_limit(self): + # By default we return max_limit, but one could also return default_limit. + return self.max_limit + def get_limit(self, request): if self.limit_query_param: try: - return _positive_int( + limit = _positive_int( request.query_params[self.limit_query_param], cutoff=self.max_limit ) + # User can specify limit == 0 to specify max (and not default) limit. + # By default (max_limit is None) this disables pagination. + if limit == 0: + limit = self.get_zero_limit() + return limit except (KeyError, ValueError): pass @@ -357,7 +366,23 @@ def get_previous_link(self): return replace_query_param(url, self.offset_query_param, offset) def get_html_context(self): + # paginate_queryset should be called before, to set + # self.limit and other values on self. + + if self.limit is None: + return { + 'previous_url': None, + 'next_url': None, + 'page_links': [] + } + base_url = self.request.build_absolute_uri() + + # This changes the URL in fact only when limit is 0 (which makes limit == max_limit) + # or when limit is missing (which makes it default_limit). We want to inform + # the user what this effective limit is. + base_url = replace_query_param(base_url, self.limit_query_param, self.limit) + current = _divide_with_ceil(self.offset, self.limit) + 1 # The number of pages is a little bit fiddly. # We need to sum both the number of pages from current offset to end diff --git a/tests/test_pagination.py b/tests/test_pagination.py index c6caaf6419..633ba14f4b 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -322,7 +322,10 @@ class ExamplePagination(pagination.LimitOffsetPagination): self.queryset = range(1, 101) def paginate_queryset(self, request): - return list(self.pagination.paginate_queryset(self.queryset, request)) + queryset = self.pagination.paginate_queryset(self.queryset, request) + if queryset is None: + return None + return list(queryset) def get_paginated_content(self, queryset): response = self.pagination.get_paginated_response(queryset) @@ -505,6 +508,52 @@ def test_max_limit(self): assert content.get('next') == next_url assert content.get('previous') == prev_url + def test_limit_is_zero(self): + """ + A limit of zero should set limit to max_limit. + """ + offset = 20 + request = Request(factory.get('/', {'limit': 0, 'offset': offset})) + queryset = self.paginate_queryset(request) + content = self.get_paginated_content(queryset) + context = self.get_html_context() + assert queryset == list(self.queryset[20:35]) + assert content == { + 'results': list(self.queryset[20:35]), + 'previous': 'http://testserver/?limit=15&offset=5', + 'next': 'http://testserver/?limit=15&offset=35', + 'count': 100 + } + assert context == { + 'previous_url': 'http://testserver/?limit=15&offset=5', + 'next_url': 'http://testserver/?limit=15&offset=35', + 'page_links': [ + PageLink('http://testserver/?limit=15', 1, False, False), + PageLink('http://testserver/?limit=15&offset=5', 2, False, False), + PageLink('http://testserver/?limit=15&offset=20', 3, True, False), + PageLink('http://testserver/?limit=15&offset=35', 4, False, False), + PAGE_BREAK, + PageLink('http://testserver/?limit=15&offset=95', 8, False, False), + ] + } + + def test_limit_is_zero_with_no_limit(self): + """ + A limit of zero should set limit to max_limit, but when it is None, + pagination should be disabled and whole queryset returned. + """ + self.pagination.max_limit = None + offset = 30 + request = Request(factory.get('/', {'limit': 0, 'offset': offset})) + queryset = self.paginate_queryset(request) + context = self.get_html_context() + assert queryset is None + assert context == { + 'previous_url': None, + 'next_url': None, + 'page_links': [] + } + class TestCursorPagination: """