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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy