Pagination: allowing negative page numbers and offsets

This commit is contained in:
Denny Biasiolli 2024-10-19 22:23:33 +02:00
parent d3dd45b3f4
commit 16dc419ff0
No known key found for this signature in database
2 changed files with 85 additions and 0 deletions

View File

@ -191,6 +191,7 @@ class PageNumberPagination(BasePagination):
last_page_strings = ('last',)
template = 'rest_framework/pagination/numbers.html'
allow_negative_page_numbers = False
invalid_page_message = _('Invalid page.')
@ -225,6 +226,14 @@ class PageNumberPagination(BasePagination):
page_number = request.query_params.get(self.page_query_param) or 1
if page_number in self.last_page_strings:
page_number = paginator.num_pages
if self.allow_negative_page_numbers:
try:
page_number = int(page_number)
if page_number < 0:
page_number = paginator.num_pages + page_number
return max(page_number, 0)
except ValueError:
return page_number
return page_number
def get_paginated_response(self, data):
@ -384,6 +393,7 @@ class LimitOffsetPagination(BasePagination):
offset_query_description = _('The initial index from which to return the results.')
max_limit = None
template = 'rest_framework/pagination/numbers.html'
allow_negative_offsets = False
def paginate_queryset(self, queryset, request, view=None):
self.request = request
@ -447,6 +457,11 @@ class LimitOffsetPagination(BasePagination):
def get_offset(self, request):
try:
if self.allow_negative_offsets:
offset = int(request.query_params[self.offset_query_param])
if offset < 0:
offset = self.count + offset
return max(offset, 0)
return _positive_int(
request.query_params[self.offset_query_param],
)

View File

@ -260,6 +260,40 @@ class TestPageNumberPagination:
with pytest.raises(exceptions.NotFound):
self.paginate_queryset(request)
def test_negative_page(self):
request = Request(factory.get('/', {'page': -1}))
print(request)
with pytest.raises(exceptions.NotFound):
self.paginate_queryset(request)
def test_allowed_negative_page(self):
self.pagination.allow_negative_page_numbers = True
request = Request(factory.get('/', {'page': -2}))
queryset = self.paginate_queryset(request)
content = self.get_paginated_content(queryset)
context = self.get_html_context()
assert queryset == [86, 87, 88, 89, 90]
assert content == {
'results': [86, 87, 88, 89, 90],
'previous': 'http://testserver/?page=17',
'next': 'http://testserver/?page=19',
'count': 100
}
assert context == {
'previous_url': 'http://testserver/?page=17',
'next_url': 'http://testserver/?page=19',
'page_links': [
PageLink('http://testserver/', 1, False, False),
PAGE_BREAK,
PageLink('http://testserver/?page=17', 17, False, False),
PageLink('http://testserver/?page=18', 18, True, False),
PageLink('http://testserver/?page=19', 19, False, False),
PageLink('http://testserver/?page=20', 20, False, False),
]
}
assert self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), str)
def test_get_paginated_response_schema(self):
unpaginated_schema = {
'type': 'object',
@ -527,6 +561,42 @@ class TestLimitOffset:
queryset = self.paginate_queryset(request)
assert queryset == [1, 2, 3, 4, 5]
def test_negative_offset(self):
"""
A negative offset query param should be treated as 0.
"""
request = Request(factory.get('/', {'limit': 5, 'offset': -5}))
queryset = self.paginate_queryset(request)
assert queryset == [1, 2, 3, 4, 5]
def test_allowed_negative_offset(self):
"""
A negative offset query param should be treated as `count - offset`.
"""
self.pagination.allow_negative_offsets = True
request = Request(factory.get('/', {'limit': 5, 'offset': -10}))
queryset = self.paginate_queryset(request)
content = self.get_paginated_content(queryset)
context = self.get_html_context()
assert queryset == [91, 92, 93, 94, 95]
assert content == {
'results': [91, 92, 93, 94, 95],
'previous': 'http://testserver/?limit=5&offset=85',
'next': 'http://testserver/?limit=5&offset=95',
'count': 100
}
assert context == {
'previous_url': 'http://testserver/?limit=5&offset=85',
'next_url': 'http://testserver/?limit=5&offset=95',
'page_links': [
PageLink('http://testserver/?limit=5', 1, False, False),
PAGE_BREAK,
PageLink('http://testserver/?limit=5&offset=85', 18, False, False),
PageLink('http://testserver/?limit=5&offset=90', 19, True, False),
PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
]
}
def test_invalid_limit(self):
"""
An invalid limit query param should be ignored in favor of the default.