Skip to content

fix: fallback on CursorPagination ordering if unset on the view #8954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 17 additions & 20 deletions rest_framework/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,10 @@ def get_ordering(self, request, queryset, view):
"""
Return a tuple of strings, that may be used in an `order_by` method.
"""
# The default case is to check for an `ordering` attribute
# on this pagination instance.
ordering = self.ordering

ordering_filters = [
filter_cls for filter_cls in getattr(view, 'filter_backends', [])
if hasattr(filter_cls, 'get_ordering')
Expand All @@ -811,26 +815,19 @@ def get_ordering(self, request, queryset, view):
# then we defer to that filter to determine the ordering.
filter_cls = ordering_filters[0]
filter_instance = filter_cls()
ordering = filter_instance.get_ordering(request, queryset, view)
assert ordering is not None, (
'Using cursor pagination, but filter class {filter_cls} '
'returned a `None` ordering.'.format(
filter_cls=filter_cls.__name__
)
)
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else statement block is not needed anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it's not needed.

previous behavior was:

  • get the ordering from the filter if it exists, raises an error if the value is None
  • else get the ordering from the instance (self.ordering) and raise an error if None

new behavior is:

  • get the ordering from the instance (default case)
  • get the ordering from the filter if it exists, override ordering value if it's not None
  • raises an error if the ordering variable is None

# The default case is to check for an `ordering` attribute
# on this pagination instance.
ordering = self.ordering
assert ordering is not None, (
'Using cursor pagination, but no ordering attribute was declared '
'on the pagination class.'
)
assert '__' not in ordering, (
'Cursor pagination does not support double underscore lookups '
'for orderings. Orderings should be an unchanging, unique or '
'nearly-unique field on the model, such as "-created" or "pk".'
)
ordering_from_filter = filter_instance.get_ordering(request, queryset, view)
if ordering_from_filter:
ordering = ordering_from_filter

assert ordering is not None, (
'Using cursor pagination, but no ordering attribute was declared '
'on the pagination class.'
)
assert '__' not in ordering, (
'Cursor pagination does not support double underscore lookups '
'for orderings. Orderings should be an unchanging, unique or '
'nearly-unique field on the model, such as "-created" or "pk".'
)

assert isinstance(ordering, (str, list, tuple)), (
'Invalid ordering. Expected string or tuple, but got {type}'.format(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,24 @@ class MockView:
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):
(previous, current, next, previous_url, next_url) = self.get_pages('/')

Expand Down