mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-12-04 00:34:00 +03:00
Merge 64647c0ca5 into 442444f0be
This commit is contained in:
commit
21ef11e798
|
|
@ -11,6 +11,7 @@ from urllib import parse
|
||||||
|
|
||||||
from django.core.paginator import InvalidPage
|
from django.core.paginator import InvalidPage
|
||||||
from django.core.paginator import Paginator as DjangoPaginator
|
from django.core.paginator import Paginator as DjangoPaginator
|
||||||
|
from django.db.models import OrderBy
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -615,7 +616,7 @@ class CursorPagination(BasePagination):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.base_url = request.build_absolute_uri()
|
self.base_url = request.build_absolute_uri()
|
||||||
self.ordering = self.get_ordering(request, queryset, view)
|
self.ordering = self.get_ordering(queryset)
|
||||||
|
|
||||||
self.cursor = self.decode_cursor(request)
|
self.cursor = self.decode_cursor(request)
|
||||||
if self.cursor is None:
|
if self.cursor is None:
|
||||||
|
|
@ -624,10 +625,11 @@ class CursorPagination(BasePagination):
|
||||||
(offset, reverse, current_position) = self.cursor
|
(offset, reverse, current_position) = self.cursor
|
||||||
|
|
||||||
# Cursor pagination always enforces an ordering.
|
# Cursor pagination always enforces an ordering.
|
||||||
if reverse:
|
if not queryset.ordered:
|
||||||
queryset = queryset.order_by(*_reverse_ordering(self.ordering))
|
if reverse:
|
||||||
else:
|
queryset = queryset.order_by(*_reverse_ordering(self.ordering))
|
||||||
queryset = queryset.order_by(*self.ordering)
|
else:
|
||||||
|
queryset = queryset.order_by(*self.ordering)
|
||||||
|
|
||||||
# If we have a cursor with a fixed position then filter by that.
|
# If we have a cursor with a fixed position then filter by that.
|
||||||
if current_position is not None:
|
if current_position is not None:
|
||||||
|
|
@ -801,28 +803,38 @@ class CursorPagination(BasePagination):
|
||||||
cursor = Cursor(offset=offset, reverse=True, position=position)
|
cursor = Cursor(offset=offset, reverse=True, position=position)
|
||||||
return self.encode_cursor(cursor)
|
return self.encode_cursor(cursor)
|
||||||
|
|
||||||
def get_ordering(self, request, queryset, view):
|
def get_ordering_from_queryset(self, queryset):
|
||||||
|
if not queryset.ordered:
|
||||||
|
return False
|
||||||
|
|
||||||
|
qs_ordering = queryset.query.order_by
|
||||||
|
|
||||||
|
# Fallback to model's Meta ordering if no order_by is given
|
||||||
|
if not qs_ordering:
|
||||||
|
qs_ordering = queryset.query.get_meta().ordering
|
||||||
|
|
||||||
|
ordering = []
|
||||||
|
for expr in qs_ordering:
|
||||||
|
if isinstance(expr, str):
|
||||||
|
ordering.append(expr)
|
||||||
|
|
||||||
|
elif isinstance(expr, OrderBy):
|
||||||
|
field_name = expr.expression.name
|
||||||
|
descending = expr.descending
|
||||||
|
ordering.append(f"{'-' if descending else ''}{field_name}")
|
||||||
|
|
||||||
|
return ordering
|
||||||
|
|
||||||
|
def get_ordering(self, queryset):
|
||||||
"""
|
"""
|
||||||
Return a tuple of strings, that may be used in an `order_by` method.
|
Return a tuple of strings, that may be used in an `order_by` method.
|
||||||
"""
|
"""
|
||||||
# The default case is to check for an `ordering` attribute
|
# Return the ordering value from the queryset if it has one.
|
||||||
# on this pagination instance.
|
if queryset.ordered:
|
||||||
|
return self.get_ordering_from_queryset(queryset)
|
||||||
|
|
||||||
ordering = self.ordering
|
ordering = self.ordering
|
||||||
|
|
||||||
ordering_filters = [
|
|
||||||
filter_cls for filter_cls in getattr(view, 'filter_backends', [])
|
|
||||||
if hasattr(filter_cls, 'get_ordering')
|
|
||||||
]
|
|
||||||
|
|
||||||
if ordering_filters:
|
|
||||||
# If a filter exists on the view that implements `get_ordering`
|
|
||||||
# then we defer to that filter to determine the ordering.
|
|
||||||
filter_cls = ordering_filters[0]
|
|
||||||
filter_instance = filter_cls()
|
|
||||||
ordering_from_filter = filter_instance.get_ordering(request, queryset, view)
|
|
||||||
if ordering_from_filter:
|
|
||||||
ordering = ordering_from_filter
|
|
||||||
|
|
||||||
assert ordering is not None, (
|
assert ordering is not None, (
|
||||||
'Using cursor pagination, but no ordering attribute was declared '
|
'Using cursor pagination, but no ordering attribute was declared '
|
||||||
'on the pagination class.'
|
'on the pagination class.'
|
||||||
|
|
|
||||||
|
|
@ -616,42 +616,6 @@ class CursorPaginationTestsMixin:
|
||||||
with pytest.raises(exceptions.NotFound):
|
with pytest.raises(exceptions.NotFound):
|
||||||
self.pagination.paginate_queryset(self.queryset, request)
|
self.pagination.paginate_queryset(self.queryset, request)
|
||||||
|
|
||||||
def test_use_with_ordering_filter(self):
|
|
||||||
class MockView:
|
|
||||||
filter_backends = (filters.OrderingFilter,)
|
|
||||||
ordering_fields = ['username', 'created']
|
|
||||||
ordering = 'created'
|
|
||||||
|
|
||||||
request = Request(factory.get('/', {'ordering': 'username'}))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
assert ordering == ('username',)
|
|
||||||
|
|
||||||
request = Request(factory.get('/', {'ordering': '-username'}))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
assert ordering == ('-username',)
|
|
||||||
|
|
||||||
request = Request(factory.get('/', {'ordering': 'invalid'}))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
assert ordering == ('created',)
|
|
||||||
|
|
||||||
def test_use_with_ordering_filter_without_ordering_default_value(self):
|
|
||||||
class MockView:
|
|
||||||
filter_backends = (filters.OrderingFilter,)
|
|
||||||
ordering_fields = ['username', 'created']
|
|
||||||
|
|
||||||
request = Request(factory.get('/'))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
# it gets the value of `ordering` provided by CursorPagination
|
|
||||||
assert ordering == ('created',)
|
|
||||||
|
|
||||||
request = Request(factory.get('/', {'ordering': 'username'}))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
assert ordering == ('username',)
|
|
||||||
|
|
||||||
request = Request(factory.get('/', {'ordering': 'invalid'}))
|
|
||||||
ordering = self.pagination.get_ordering(request, [], MockView())
|
|
||||||
assert ordering == ('created',)
|
|
||||||
|
|
||||||
def test_cursor_pagination(self):
|
def test_cursor_pagination(self):
|
||||||
(previous, current, next, previous_url, next_url) = self.get_pages('/')
|
(previous, current, next, previous_url, next_url) = self.get_pages('/')
|
||||||
|
|
||||||
|
|
@ -969,8 +933,9 @@ class TestCursorPagination(CursorPaginationTestsMixin):
|
||||||
self.created = idx
|
self.created = idx
|
||||||
|
|
||||||
class MockQuerySet:
|
class MockQuerySet:
|
||||||
def __init__(self, items):
|
def __init__(self, items, ordered=False):
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.ordered = ordered
|
||||||
|
|
||||||
def filter(self, created__gt=None, created__lt=None):
|
def filter(self, created__gt=None, created__lt=None):
|
||||||
if created__gt is not None:
|
if created__gt is not None:
|
||||||
|
|
@ -987,7 +952,7 @@ class TestCursorPagination(CursorPaginationTestsMixin):
|
||||||
|
|
||||||
def order_by(self, *ordering):
|
def order_by(self, *ordering):
|
||||||
if ordering[0].startswith('-'):
|
if ordering[0].startswith('-'):
|
||||||
return MockQuerySet(list(reversed(self.items)))
|
return MockQuerySet(list(reversed(self.items)), ordered=True)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, sliced):
|
def __getitem__(self, sliced):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user