Skip to content

Commit d7978be

Browse files
authored
Merge pull request jrief#368 from AndreyMZ/pr
Add support for ordering with `OrderBy` or `F`
2 parents 396aada + 42297ad commit d7978be

File tree

7 files changed

+68
-18
lines changed

7 files changed

+68
-18
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,9 @@ max_line_length = 100
2121
indent_style = space
2222
indent_size = 2
2323

24+
[*.yml]
25+
indent_style = space
26+
indent_size = 2
27+
2428
[*.md]
2529
trim_trailing_whitespace = false

.github/workflows/tests.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ jobs:
2121

2222
strategy:
2323
matrix:
24-
python-version: ["3.8", "3.9", "3.10"]
24+
python-version: ["3.8", "3.9", "3.10", "3.11"]
2525
node-version: ["16.x"]
2626
django-version: ["4.0.*", "4.1.*", "4.2.*"]
27+
exclude: # https://docs.djangoproject.com/en/4.2/faq/install/#what-python-version-can-i-use-with-django
28+
- python-version: "3.11"
29+
django-version: "4.0.*"
2730

2831
steps:
2932
- uses: actions/checkout@v3

adminsortable2/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.1.8'
1+
__version__ = '2.1.9'

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}'

docs/source/contributing.rst

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ version of NodeJS.
6161
6262
# we use the default template files and patch them, rather than using our own modified one
6363
django_version=$(python -c 'from django import VERSION; print("{0}.{1}".format(*VERSION))')
64-
curl --silent --output adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/stacked.html
65-
curl --silent --output adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/tabular.html
64+
mkdir adminsortable2/templates/adminsortable2/edit_inline
65+
curl --no-progress-meter --output adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/stacked.html
66+
curl --no-progress-meter --output adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/tabular.html
6667
patch -p0 adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html patches/stacked-django-4.0.patch
6768
patch -p0 adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html patches/tabular-django-4.0.patch
6869
@@ -103,19 +104,23 @@ Follow these steps to run all unit- and end-to-end tests.
103104
104105
git clone https://github.com/jrief/django-admin-sortable2.git
105106
cd django-admin-sortable2
106-
npm install --also=dev
107+
npm install --include=dev
107108
npm run build
108109
python -m pip install Django
109110
python -m pip install -r testapp/requirements.txt
110111
python -m playwright install
111112
python -m playwright install-deps
112-
python -m pytest testapp
113+
114+
# we use the default template files and patch them, rather than using our own modified one
113115
django_version=$(python -c 'from django import VERSION; print("{0}.{1}".format(*VERSION))')
114-
curl --silent --output adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/stacked.html
115-
curl --silent --output adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/tabular.html
116+
mkdir adminsortable2/templates/adminsortable2/edit_inline
117+
curl --no-progress-meter --output adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/stacked.html
118+
curl --no-progress-meter --output adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html https://raw.githubusercontent.com/django/django/stable/$django_version.x/django/contrib/admin/templates/admin/edit_inline/tabular.html
116119
patch -p0 adminsortable2/templates/adminsortable2/edit_inline/stacked-django-$django_version.html patches/stacked-django-4.0.patch
117120
patch -p0 adminsortable2/templates/adminsortable2/edit_inline/tabular-django-$django_version.html patches/tabular-django-4.0.patch
118121
122+
python -m pytest testapp
123+
119124
.. _Playwright-Python: https://playwright.dev/python/
120125
.. _pytest-django: https://pytest-django.readthedocs.io/en/latest/
121126

testapp/admin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from django.contrib import admin
2-
from django.template.response import TemplateResponse
32

43
from adminsortable2.admin import SortableAdminBase, SortableAdminMixin, SortableInlineAdminMixin, SortableStackedInline, SortableTabularInline
54

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)