|
| 1 | +from __future__ import annotations |
| 2 | + |
1 | 3 | import json
|
2 | 4 | from pathlib import Path
|
3 | 5 | from itertools import chain
|
4 | 6 | from types import MethodType
|
| 7 | +from typing import Optional |
5 | 8 |
|
6 | 9 | from django import VERSION as DJANGO_VERSION
|
7 | 10 | from django.conf import settings
|
|
11 | 14 | from django.core.exceptions import ImproperlyConfigured
|
12 | 15 | from django.core.paginator import EmptyPage
|
13 | 16 | from django.db import router, transaction
|
| 17 | +from django.db.models import OrderBy |
14 | 18 | from django.db.models.aggregates import Max
|
15 |
| -from django.db.models.expressions import F |
| 19 | +from django.db.models.expressions import BaseExpression, F |
16 | 20 | from django.db.models.functions import Coalesce
|
17 | 21 | from django.db.models.signals import post_save, pre_save
|
18 | 22 | from django.forms import widgets
|
|
26 | 30 | __all__ = ['SortableAdminMixin', 'SortableInlineAdminMixin']
|
27 | 31 |
|
28 | 32 |
|
| 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 | + |
29 | 44 | def _get_default_ordering(model, model_admin):
|
30 | 45 | try:
|
31 | 46 | # 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]) |
33 | 48 | except (AttributeError, IndexError, TypeError):
|
34 | 49 | pass
|
35 | 50 | else:
|
36 |
| - return prefix, field_name |
| 51 | + if field_name is not None: |
| 52 | + return prefix, field_name |
37 | 53 |
|
38 | 54 | try:
|
39 | 55 | # 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]) |
41 | 57 | 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 |
45 | 59 | 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 | + ) |
47 | 66 |
|
48 | 67 |
|
49 | 68 | class MovePageActionForm(admin.helpers.ActionForm):
|
@@ -174,7 +193,9 @@ def get_actions(self, request):
|
174 | 193 | def get_changelist_instance(self, request):
|
175 | 194 | cl = super().get_changelist_instance(request)
|
176 | 195 | 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]) |
178 | 199 | if order_field == self.default_order_field:
|
179 | 200 | self.enable_sorting = True
|
180 | 201 | self.order_by = f'{order_direction}{order_field}'
|
|
0 commit comments