Чтобы иметь возможность сделать заказ по нему, необходимо аннотировать набор запросов в modeladmin, поэтому метод модели не поможет.
admin.py
from django.db.models.expressions import F
...
@admin.register(PrivateContentCategory)
class PrivateContentCategoryAdmin(admin.ModelAdmin):
list_display = (
'name',
'relative_name',
)
def get_queryset(self, request):
qs = super().get_queryset(request) # type: QuerySet
qs = qs.annotate(relative_name=F('name')) # for now :)
return qs
def relative_name(self, obj: PrivateContentCategory):
return obj.relative_name
relative_name.admin_order_field = 'relative_name'
Это добавит столбец к администратору и позволит вам отсортировать его по клику.
Одна вещь, которая не позволит вам сделать это для сортировки по умолчанию в этом столбце.Это не удастся:
class PrivateContentCategoryAdmin(admin.ModelAdmin):
...
ordering = ('relative_name',)
ОШИБКИ:
: (admin.E033) Значение 'ordering [0]' ссылается на 'относительное_имя', которое не является атрибутом 'cats.PrivateContentCategory'.
Это давняя ошибка вDjango: https://code.djangoproject.com/ticket/17522
Есть способы обойти это, но я ухожу от темы ...
Итак, вторая проблема, очевидно, заключается в том, что нам нужно построить относительные имена вместо этого F('name')
.Я могу ошибаться, но я думаю, что единственный движок БД, который поддерживает это на лету, - это Postgres.Если вы используете другой механизм БД, то, я думаю, вам придется немного денормализовать ваши данные и иметь столбец с полным родительским именем для каждого потомка.
Могут быть более эффективные способы сделать это,но вот как я это сделал:
admin.py
...
from django.db.models.expressions import RawSQL
relative_name_query = '''
WITH RECURSIVE "relative_names" as (
SELECT "id", "parent_category_id", CAST("name" AS TEXT)
FROM "{table}"
WHERE "parent_category_id" IS NULL
UNION ALL
SELECT "t"."id", "t"."parent_category_id", CONCAT_WS('/', "r"."name", "t"."name")
FROM "{table}" "t"
JOIN "relative_names" "r" ON "t"."parent_category_id" = "r"."id"
)
SELECT "name"
FROM "relative_names" WHERE "relative_names"."id" = "{table}"."id"
'''
@admin.register(PrivateContentCategory)
class PrivateContentCategoryAdmin(admin.ModelAdmin):
...
# instead of that F('name') line:
qs = qs.annotate(relative_name=RawSQL(
relative_name_query.format(
table=qs.model._meta.db_table,
),
(),
))
PS
Oracle, кажется, также поддерживает это, хотя и с другим синтаксисом: Рекурсивный SQL-запрос к самореферентной таблице (Oracle)
PPS
Если в конечном итоге вам необходимо сохранить родительское имя для модели, аннотирование выглядит примерно так:
qs = qs.annotate(relative_name=Concat(F('parent_name'), Value('/'), F('name')))
PPPS
Можно добавить две аннотации, одну для отображения значений, а другую для сортировки.На самом деле, глядя на ваш вопрос еще раз, я думаю, что это будет необходимо, потому что ваш пример имеет subcat -- cat
, а не cat -- subcat
, как я предполагал выше.Для этого нам понадобятся две аннотации, одна из которых будет возвращена методом relative_name
modeladmin, а другая - для relative_name.admin_order_field
.