Skip to content

Commit 8da94de

Browse files
author
Andrey Zelenchuk
committed
Add support for ordering with OrderBy or F.
1 parent dcd13ba commit 8da94de

File tree

2 files changed

+48
-9
lines changed

2 files changed

+48
-9
lines changed

adminsortable2/admin.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from __future__ import annotations
2+
13
import json
24
from pathlib import Path
35
from itertools import chain
46
from types import MethodType
7+
from typing import Optional
58

69
from django import VERSION as DJANGO_VERSION
710
from django.conf import settings
@@ -11,8 +14,9 @@
1114
from django.core.exceptions import ImproperlyConfigured
1215
from django.core.paginator import EmptyPage
1316
from django.db import router, transaction
17+
from django.db.models import OrderBy
1418
from django.db.models.aggregates import Max
15-
from django.db.models.expressions import F
19+
from django.db.models.expressions import BaseExpression, F
1620
from django.db.models.functions import Coalesce
1721
from django.db.models.signals import post_save, pre_save
1822
from django.forms import widgets
@@ -26,24 +30,39 @@
2630
__all__ = ['SortableAdminMixin', 'SortableInlineAdminMixin']
2731

2832

33+
def _parse_ordering_part(part: OrderBy | BaseExpression | F | str) -> tuple[str, Optional[str]]:
34+
if isinstance(part, str):
35+
return ('-', part[1:]) if part.startswith('-') else ('', part)
36+
elif isinstance(part, OrderBy) and isinstance(part.expression, F):
37+
return ('-' if part.descending else ''), part.expression.name
38+
elif isinstance(part, F):
39+
return '', part.name
40+
else:
41+
return '', None
42+
43+
2944
def _get_default_ordering(model, model_admin):
3045
try:
3146
# first try with the model admin ordering
32-
_, prefix, field_name = model_admin.ordering[0].rpartition('-')
47+
prefix, field_name = _parse_ordering_part(model_admin.ordering[0])
3348
except (AttributeError, IndexError, TypeError):
3449
pass
3550
else:
36-
return prefix, field_name
51+
if field_name is not None:
52+
return prefix, field_name
3753

3854
try:
3955
# then try with the model ordering
40-
_, prefix, field_name = model._meta.ordering[0].rpartition('-')
56+
prefix, field_name = _parse_ordering_part(model._meta.ordering[0])
4157
except (AttributeError, IndexError):
42-
raise ImproperlyConfigured(
43-
f"Model {model.__module__}.{model.__name__} requires a list or tuple 'ordering' in its Meta class"
44-
)
58+
pass
4559
else:
46-
return prefix, field_name
60+
if field_name is not None:
61+
return prefix, field_name
62+
63+
raise ImproperlyConfigured(
64+
f"Model {model.__module__}.{model.__name__} requires a list or tuple 'ordering' in its Meta class"
65+
)
4766

4867

4968
class MovePageActionForm(admin.helpers.ActionForm):
@@ -174,7 +193,9 @@ def get_actions(self, request):
174193
def get_changelist_instance(self, request):
175194
cl = super().get_changelist_instance(request)
176195
qs = self.get_queryset(request)
177-
_, order_direction, order_field = cl.get_ordering(request, qs)[0].rpartition('-')
196+
ordering = cl.get_ordering(request, qs)
197+
assert len(ordering) > 0 # `ChangeList.get_ordering` always returns deterministic ordering.
198+
order_direction, order_field = _parse_ordering_part(ordering[0])
178199
if order_field == self.default_order_field:
179200
self.enable_sorting = True
180201
self.order_by = f'{order_direction}{order_field}'

testapp/test_parse_ordering_part.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.db.models import F, OrderBy
2+
3+
# noinspection PyProtectedMember
4+
from adminsortable2.admin import _parse_ordering_part as parse
5+
6+
7+
def test():
8+
assert parse('my_order') == ('', 'my_order')
9+
assert parse(F('my_order')) == ('', 'my_order')
10+
assert parse(F('my_order').asc()) == ('', 'my_order')
11+
assert parse(OrderBy(F('my_order'))) == ('', 'my_order')
12+
assert parse(OrderBy(F('my_order'), descending=False)) == ('', 'my_order')
13+
14+
assert parse('-my_order') == ('-', 'my_order')
15+
assert parse(F('my_order').desc()) == ('-', 'my_order')
16+
assert parse(OrderBy(F('my_order'), descending=True)) == ('-', 'my_order')
17+
18+
assert parse(F("foo") + F("bar")) == ('', None)

0 commit comments

Comments
 (0)