Move page settings to property

This commit is contained in:
Stanislav Khlud 2024-08-21 09:01:54 +07:00
parent 91695fb3c6
commit b51141cb69
No known key found for this signature in database
GPG Key ID: 4A9896D700E79EB7
2 changed files with 135 additions and 17 deletions

View File

@ -169,9 +169,6 @@ class PageNumberPagination(BasePagination):
http://api.example.org/accounts/?page=4 http://api.example.org/accounts/?page=4
http://api.example.org/accounts/?page=4&page_size=100 http://api.example.org/accounts/?page=4&page_size=100
""" """
# The default page size.
# Defaults to `None`, meaning pagination is disabled.
page_size = api_settings.PAGE_SIZE
django_paginator_class = DjangoPaginator django_paginator_class = DjangoPaginator
@ -184,18 +181,33 @@ class PageNumberPagination(BasePagination):
page_size_query_param = None page_size_query_param = None
page_size_query_description = _('Number of results to return per page.') page_size_query_description = _('Number of results to return per page.')
# Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'page_size_query_param' has also been set.
# Defaults to `None`, meaning page size is unlimited.
# It's recommended that you would set a limit to avoid api abuse.
max_page_size = api_settings.MAX_PAGE_SIZE
last_page_strings = ('last',) last_page_strings = ('last',)
template = 'rest_framework/pagination/numbers.html' template = 'rest_framework/pagination/numbers.html'
invalid_page_message = _('Invalid page.') invalid_page_message = _('Invalid page.')
@property
def page_size(self) -> int:
"""Get default page size.
Defaults to `None`, meaning pagination is disabled.
"""
return api_settings.PAGE_SIZE
@property
def max_page_size(self) -> int:
"""Limit page size.
Set to an integer to limit the maximum page size the client may request.
Only relevant if 'page_size_query_param' has also been set.
Defaults to `None`, meaning page size is unlimited.
It's recommended that you would set a limit to avoid api abuse.
"""
return api_settings.MAX_PAGE_SIZE
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
""" """
Paginate a queryset if required, either returning a Paginate a queryset if required, either returning a
@ -379,14 +391,33 @@ class LimitOffsetPagination(BasePagination):
http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?limit=100
http://api.example.org/accounts/?offset=400&limit=100 http://api.example.org/accounts/?offset=400&limit=100
""" """
default_limit = api_settings.PAGE_SIZE
limit_query_param = 'limit' limit_query_param = 'limit'
limit_query_description = _('Number of results to return per page.') limit_query_description = _('Number of results to return per page.')
offset_query_param = 'offset' offset_query_param = 'offset'
offset_query_description = _('The initial index from which to return the results.') offset_query_description = _('The initial index from which to return the results.')
max_limit = api_settings.MAX_PAGE_SIZE
template = 'rest_framework/pagination/numbers.html' template = 'rest_framework/pagination/numbers.html'
@property
def max_limit(self) -> int:
"""Limit maximum page size.
Set to an integer to limit the maximum page size the client may request.
Only relevant if 'page_size_query_param' has also been set.
Defaults to `None`, meaning page size is unlimited.
It's recommended that you would set a limit to avoid api abuse.
"""
return api_settings.MAX_PAGE_SIZE
@property
def default_limit(self) -> int:
"""Get default page size.
Defaults to `None`, meaning pagination is disabled.
"""
return api_settings.PAGE_SIZE
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
self.request = request self.request = request
self.limit = self.get_limit(request) self.limit = self.get_limit(request)
@ -590,7 +621,6 @@ class CursorPagination(BasePagination):
""" """
cursor_query_param = 'cursor' cursor_query_param = 'cursor'
cursor_query_description = _('The pagination cursor value.') cursor_query_description = _('The pagination cursor value.')
page_size = api_settings.PAGE_SIZE
invalid_cursor_message = _('Invalid cursor') invalid_cursor_message = _('Invalid cursor')
ordering = '-created' ordering = '-created'
template = 'rest_framework/pagination/previous_and_next.html' template = 'rest_framework/pagination/previous_and_next.html'
@ -600,16 +630,33 @@ class CursorPagination(BasePagination):
page_size_query_param = None page_size_query_param = None
page_size_query_description = _('Number of results to return per page.') page_size_query_description = _('Number of results to return per page.')
# Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'page_size_query_param' has also been set.
max_page_size = api_settings.MAX_PAGE_SIZE
# The offset in the cursor is used in situations where we have a # The offset in the cursor is used in situations where we have a
# nearly-unique index. (Eg millisecond precision creation timestamps) # nearly-unique index. (Eg millisecond precision creation timestamps)
# We guard against malicious users attempting to cause expensive database # We guard against malicious users attempting to cause expensive database
# queries, by having a hard cap on the maximum possible size of the offset. # queries, by having a hard cap on the maximum possible size of the offset.
offset_cutoff = 1000 offset_cutoff = 1000
@property
def page_size(self) -> int:
"""Get default page size.
Defaults to `None`, meaning pagination is disabled.
"""
return api_settings.PAGE_SIZE
@property
def max_page_size(self) -> int:
"""Limit page size.
Set to an integer to limit the maximum page size the client may request.
Only relevant if 'page_size_query_param' has also been set.
Defaults to `None`, meaning page size is unlimited.
It's recommended that you would set a limit to avoid api abuse.
"""
return api_settings.MAX_PAGE_SIZE
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
self.request = request self.request = request
self.page_size = self.get_page_size(request) self.page_size = self.get_page_size(request)

View File

@ -1,7 +1,7 @@
import pytest import pytest
from django.core.paginator import Paginator as DjangoPaginator from django.core.paginator import Paginator as DjangoPaginator
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase, override_settings
from rest_framework import ( from rest_framework import (
exceptions, filters, generics, pagination, serializers, status exceptions, filters, generics, pagination, serializers, status
@ -135,6 +135,77 @@ class TestPaginationIntegration:
} }
class TestPaginationSettingsIntegration:
"""
Integration tests for pagination settings.
"""
def setup_method(self):
class PassThroughSerializer(serializers.BaseSerializer):
def to_representation(self, item):
return item
class EvenItemsOnly(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return [item for item in queryset if item % 2 == 0]
class BasicPagination(pagination.PageNumberPagination):
page_size_query_param = 'page_size'
self.view = generics.ListAPIView.as_view(
serializer_class=PassThroughSerializer,
queryset=range(1, 101),
filter_backends=[EvenItemsOnly],
pagination_class=BasicPagination
)
@override_settings(
REST_FRAMEWORK={
"MAX_PAGE_SIZE": 20,
"PAGE_SIZE": 5,
}
)
def test_setting_page_size_over_maximum(self):
"""
When page_size parameter exceeds maximum allowable,
then it should be capped to the maximum.
"""
request = factory.get('/', {'page_size': 1000})
response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 20, response.data
assert response.data == {
'results': [
2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
22, 24, 26, 28, 30, 32, 34, 36, 38, 40
],
'previous': None,
'next': 'http://testserver/?page=2&page_size=1000',
'count': 50
}
@override_settings(
REST_FRAMEWORK={
"MAX_PAGE_SIZE": 20,
"PAGE_SIZE": 5,
}
)
def test_setting_page_size_to_zero(self):
"""
When page_size parameter is invalid it should return to the default.
"""
request = factory.get('/', {'page_size': 0})
response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 5, response.data
assert response.data == {
'results': [2, 4, 6, 8, 10],
'previous': None,
'next': 'http://testserver/?page=2&page_size=0',
'count': 50
}
class TestPaginationDisabledIntegration: class TestPaginationDisabledIntegration:
""" """
Integration tests for disabled pagination. Integration tests for disabled pagination.