Skip to content

Commit 60b9e58

Browse files
cypreessCarlton Gibson
authored andcommitted
Add support for page_size parameter in CursorPaginator class
1 parent aecca9d commit 60b9e58

File tree

2 files changed

+195
-1
lines changed

2 files changed

+195
-1
lines changed

rest_framework/pagination.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,15 @@ class CursorPagination(BasePagination):
482482
ordering = '-created'
483483
template = 'rest_framework/pagination/previous_and_next.html'
484484

485+
# Client can control the page size using this query parameter.
486+
# Default is 'None'. Set to eg 'page_size' to enable usage.
487+
page_size_query_param = None
488+
page_size_query_description = _('Number of results to return per page.')
489+
490+
# Set to an integer to limit the maximum page size the client may request.
491+
# Only relevant if 'page_size_query_param' has also been set.
492+
max_page_size = None
493+
485494
# The offset in the cursor is used in situations where we have a
486495
# nearly-unique index. (Eg millisecond precision creation timestamps)
487496
# We guard against malicious users attempting to cause expensive database
@@ -566,6 +575,16 @@ def paginate_queryset(self, queryset, request, view=None):
566575
return self.page
567576

568577
def get_page_size(self, request):
578+
if self.page_size_query_param:
579+
try:
580+
return _positive_int(
581+
request.query_params[self.page_size_query_param],
582+
strict=True,
583+
cutoff=self.max_page_size
584+
)
585+
except (KeyError, ValueError):
586+
pass
587+
569588
return self.page_size
570589

571590
def get_next_link(self):
@@ -779,7 +798,7 @@ def to_html(self):
779798
def get_schema_fields(self, view):
780799
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
781800
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
782-
return [
801+
fields = [
783802
coreapi.Field(
784803
name=self.cursor_query_param,
785804
required=False,
@@ -790,3 +809,16 @@ def get_schema_fields(self, view):
790809
)
791810
)
792811
]
812+
if self.page_size_query_param is not None:
813+
fields.append(
814+
coreapi.Field(
815+
name=self.page_size_query_param,
816+
required=False,
817+
location='query',
818+
schema=coreschema.Integer(
819+
title='Page size',
820+
description=force_text(self.page_size_query_description)
821+
)
822+
)
823+
)
824+
return fields

tests/test_pagination.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,164 @@ def test_cursor_pagination(self):
633633

634634
assert isinstance(self.pagination.to_html(), type(''))
635635

636+
def test_cursor_pagination_with_page_size(self):
637+
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20')
638+
639+
assert previous is None
640+
assert current == [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 7, 7, 7, 7, 7]
641+
assert next == [7, 7, 7, 8, 9, 9, 9, 9, 9, 9]
642+
643+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
644+
assert previous == [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 7, 7, 7, 7, 7]
645+
assert current == [7, 7, 7, 8, 9, 9, 9, 9, 9, 9]
646+
assert next is None
647+
648+
def test_cursor_pagination_with_page_size_over_limit(self):
649+
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=30')
650+
651+
assert previous is None
652+
assert current == [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 7, 7, 7, 7, 7]
653+
assert next == [7, 7, 7, 8, 9, 9, 9, 9, 9, 9]
654+
655+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
656+
assert previous == [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 7, 7, 7, 7, 7]
657+
assert current == [7, 7, 7, 8, 9, 9, 9, 9, 9, 9]
658+
assert next is None
659+
660+
def test_cursor_pagination_with_page_size_zero(self):
661+
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=0')
662+
663+
assert previous is None
664+
assert current == [1, 1, 1, 1, 1]
665+
assert next == [1, 2, 3, 4, 4]
666+
667+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
668+
669+
assert previous == [1, 1, 1, 1, 1]
670+
assert current == [1, 2, 3, 4, 4]
671+
assert next == [4, 4, 5, 6, 7]
672+
673+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
674+
675+
assert previous == [1, 2, 3, 4, 4]
676+
assert current == [4, 4, 5, 6, 7]
677+
assert next == [7, 7, 7, 7, 7]
678+
679+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
680+
681+
assert previous == [4, 4, 4, 5, 6] # Paging artifact
682+
assert current == [7, 7, 7, 7, 7]
683+
assert next == [7, 7, 7, 8, 9]
684+
685+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
686+
687+
assert previous == [7, 7, 7, 7, 7]
688+
assert current == [7, 7, 7, 8, 9]
689+
assert next == [9, 9, 9, 9, 9]
690+
691+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
692+
693+
assert previous == [7, 7, 7, 8, 9]
694+
assert current == [9, 9, 9, 9, 9]
695+
assert next is None
696+
697+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
698+
699+
assert previous == [7, 7, 7, 7, 7]
700+
assert current == [7, 7, 7, 8, 9]
701+
assert next == [9, 9, 9, 9, 9]
702+
703+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
704+
705+
assert previous == [4, 4, 5, 6, 7]
706+
assert current == [7, 7, 7, 7, 7]
707+
assert next == [8, 9, 9, 9, 9] # Paging artifact
708+
709+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
710+
711+
assert previous == [1, 2, 3, 4, 4]
712+
assert current == [4, 4, 5, 6, 7]
713+
assert next == [7, 7, 7, 7, 7]
714+
715+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
716+
717+
assert previous == [1, 1, 1, 1, 1]
718+
assert current == [1, 2, 3, 4, 4]
719+
assert next == [4, 4, 5, 6, 7]
720+
721+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
722+
723+
assert previous is None
724+
assert current == [1, 1, 1, 1, 1]
725+
assert next == [1, 2, 3, 4, 4]
726+
727+
def test_cursor_pagination_with_page_size_negative(self):
728+
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=-5')
729+
730+
assert previous is None
731+
assert current == [1, 1, 1, 1, 1]
732+
assert next == [1, 2, 3, 4, 4]
733+
734+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
735+
736+
assert previous == [1, 1, 1, 1, 1]
737+
assert current == [1, 2, 3, 4, 4]
738+
assert next == [4, 4, 5, 6, 7]
739+
740+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
741+
742+
assert previous == [1, 2, 3, 4, 4]
743+
assert current == [4, 4, 5, 6, 7]
744+
assert next == [7, 7, 7, 7, 7]
745+
746+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
747+
748+
assert previous == [4, 4, 4, 5, 6] # Paging artifact
749+
assert current == [7, 7, 7, 7, 7]
750+
assert next == [7, 7, 7, 8, 9]
751+
752+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
753+
754+
assert previous == [7, 7, 7, 7, 7]
755+
assert current == [7, 7, 7, 8, 9]
756+
assert next == [9, 9, 9, 9, 9]
757+
758+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
759+
760+
assert previous == [7, 7, 7, 8, 9]
761+
assert current == [9, 9, 9, 9, 9]
762+
assert next is None
763+
764+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
765+
766+
assert previous == [7, 7, 7, 7, 7]
767+
assert current == [7, 7, 7, 8, 9]
768+
assert next == [9, 9, 9, 9, 9]
769+
770+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
771+
772+
assert previous == [4, 4, 5, 6, 7]
773+
assert current == [7, 7, 7, 7, 7]
774+
assert next == [8, 9, 9, 9, 9] # Paging artifact
775+
776+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
777+
778+
assert previous == [1, 2, 3, 4, 4]
779+
assert current == [4, 4, 5, 6, 7]
780+
assert next == [7, 7, 7, 7, 7]
781+
782+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
783+
784+
assert previous == [1, 1, 1, 1, 1]
785+
assert current == [1, 2, 3, 4, 4]
786+
assert next == [4, 4, 5, 6, 7]
787+
788+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
789+
790+
assert previous is None
791+
assert current == [1, 1, 1, 1, 1]
792+
assert next == [1, 2, 3, 4, 4]
793+
636794

637795
class TestCursorPagination(CursorPaginationTestsMixin):
638796
"""
@@ -671,6 +829,8 @@ def __getitem__(self, sliced):
671829

672830
class ExamplePagination(pagination.CursorPagination):
673831
page_size = 5
832+
page_size_query_param = 'page_size'
833+
max_page_size = 20
674834
ordering = 'created'
675835

676836
self.pagination = ExamplePagination()
@@ -727,6 +887,8 @@ class TestCursorPaginationWithValueQueryset(CursorPaginationTestsMixin, TestCase
727887
def setUp(self):
728888
class ExamplePagination(pagination.CursorPagination):
729889
page_size = 5
890+
page_size_query_param = 'page_size'
891+
max_page_size = 20
730892
ordering = 'created'
731893

732894
self.pagination = ExamplePagination()

0 commit comments

Comments
 (0)