8000 Fix CursorPagination when objects get deleted between calls (#6504) … · coderanger/django-rest-framework@43a9cc1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 43a9cc1

Browse files
ewjoachimTom Quinonero
authored andcommitted
Fix CursorPagination when objects get deleted between calls (encode#6504) (encode#6593)
* Added regression tests (encode#6504) Co-Authored-By: Tom Quinonero <tq@3yourmind.com> * Fix CursorPagination when objects get deleted between calls (encode#6504) Co-Authored-By: Tom Quinonero <tq@3yourmind.com>
1 parent ac0f0a1 commit 43a9cc1

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

rest_framework/pagination.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -637,20 +637,22 @@ def get_next_link(self):
637637
if not self.has_next:
638638
return None
639639

640-
if self.cursor and self.cursor.reverse and self.cursor.offset != 0:
640+
if self.page and self.cursor and self.cursor.reverse and self.cursor.offset != 0:
641641
# If we're reversing direction and we have an offset cursor
642642
# then we cannot use the first position we find as a marker.
643643
compare = self._get_position_from_instance(self.page[-1], self.ordering)
644644
else:
645645
compare = self.next_position
646646
offset = 0
647647

648+
has_item_with_unique_position = False
648649
for item in reversed(self.page):
649650
position = self._get_position_from_instance(item, self.ordering)
650651
if position != compare:
651652
# The item in this position and the item following it
652653
# have different positions. We can use this position as
653654
# our marker.
655+
has_item_with_unique_position = True
654656
break
655657

656658
# The item in this position has the same position as the item
@@ -659,7 +661,7 @@ def get_next_link(self):
659661
compare = position
660662
offset += 1
661663

662-
else:
664+
if self.page and not has_item_with_unique_position:
663665
# There were no unique positions in the page.
664666
if not self.has_previous:
665667
# We are on the first page.
@@ -678,27 +680,32 @@ def get_next_link(self):
678680
offset = self.cursor.offset + self.page_size
679681
position = self.previous_position
680682

683+
if not self.page:
684+
position = self.next_position
685+
681686
cursor = Cursor(offset=offset, reverse=False, position=position)
682687
return self.encode_cursor(cursor)
683688

684689
def get_previous_link(self):
685690
if not self.has_previous:
686691
return None
687692

688-
if self.cursor and not self.cursor.reverse and self.cursor.offset != 0:
693+
if self.page and self.cursor and not self.cursor.reverse and self.cursor.offset != 0:
689694
# If we're reversing direction and we have an offset cursor
690695
# then we cannot use the first position we find as a marker.
691696
compare = self._get_position_from_instance(self.page[0], self.ordering)
692697
else:
693698
compare = self.previous_position
694699
offset = 0
695700

701+
has_item_with_unique_position = False
696702
for item in self.page:
697703
position = self._get_position_from_instance(item, self.ordering)
698704
if position != compare:
699705
# The item in this position and the item following it
700706
# have different positions. We can use this position as
701707
# our marker.
708+
has_item_with_unique_position = True
702709
break
703710

704711
# The item in this position has the same position as the item
@@ -707,7 +714,7 @@ def get_previous_link(self):
707714
compare = position
708715
offset += 1
709716

710-
else:
717+
if self.page and not has_item_with_unique_position:
711718
# There were no unique positions in the page.
712719
if not self.has_next:
713720
# We are on the final page.
@@ -726,6 +733,9 @@ def get_previous_link(self):
726733
offset = 0
727734
position = self.next_position
728735

736+
if not self.page:
737< EDBE /td>+
position = self.previous_position
738+
729739
cursor = Cursor(offset=offset, reverse=True, position=position)
730740
return self.encode_cursor(cursor)
731741

tests/test_pagination.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,52 @@ def test_cursor_pagination(self):
630630

631631
assert isinstance(self.pagination.to_html(), str)
632632

633+
def test_cursor_pagination_current_page_empty_forward(self):
634+
# Regression test for #6504
635+
self.pagination.base_url = "/"
636+
637+
# We have a cursor on the element at position 100, but this element doesn't exist
638+
# anymore.
639+
cursor = pagination.Cursor(reverse=False, offset=0, position=100)
640+
url = self.pagination.encode_cursor(cursor)
641+
self.pagination.base_url = "/"
642+
643+
# Loading the page with this cursor doesn't crash
644+
(previous, current, next, previous_url, next_url) = self.get_pages(url)
645+
646+
# The previous url doesn't crash either
647+
(previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
648+
649+
# And point to things that are not completely off.
650+
assert previous == [7, 7, 7, 8, 9]
651+
assert current == [9, 9, 9, 9, 9]
652+
assert next == []
653+
assert previous_url is not None
654+
assert next_url is not None
655+
656+
def test_cursor_pagination_current_page_empty_reverse(self):
657+
# Regression test for #6504
658+
self.pagination.base_url = "/"
659+
660+
# We have a cursor on the element at position 100, but this element doesn't exist
661+
# anymore.
662+
cursor = pagination.Cursor(reverse=True, offset=0, position=100)
663+
url = self.pagination.encode_cursor(cursor)
664+
self.pagination.base_url = "/"
665+
666+
# Loading the page with this cursor doesn't crash
667+
(previous, current, next, previous_url, next_url) = self.get_pages(url)
668+
669+
# The previous url doesn't crash either
670+
(previous, current, next, previous_url, next_url) = self.get_pages(next_url)
671+
672+
# And point to things that are not completely off.
673+
assert previous == [7, 7, 7, 7, 8]
674+
assert current == []
675+
assert next is None
676+
assert previous_url is not None
677+
assert next_url is None
678+
633679
def test_cursor_pagination_with_page_size(self):
634680
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20')
635681

0 commit comments

Comments
 (0)
0