У меня есть модель "Категория" с ForeignKey для "parent_category". Как я могу заказать эту модель в виде списка администратора Django, например:
- category 1
-- subcategory 1 of category 1
--- subsubcategory 1 of subcategory 1 of category 1
-- subcategory 2 of category 1
-- subcategory 3 of category 1
- category 2
-- subcategory 1 of category 2
-- subcategory 2 of category 2
Я попробовал следующее, но это не сработает. Поэтому мне нужна помощь, чтобы заказать функцию 'get_relative_name'.
class PrivateContentCategory(models.Model): name = models.CharField( max_length=250, verbose_name=_('Naam'), ) slug = models.SlugField( verbose_name=_('Url'), blank=True, ) parent_category = models.ForeignKey( 'self', on_delete=models.SET_NULL, related_name='child_category_list', verbose_name=_('Hoofdcategorie'), blank=True, null=True, ) def __str__(self): str = self.name parent_category_obj = self.parent_category while parent_category_obj is not None: str = parent_category_obj.name + ' --> ' + str parent_category_obj = parent_category_obj.parent_category return str def get_relative_name(self): str = self.name parent_category_obj = self.parent_category while parent_category_obj is not None: str = '--' + str parent_category_obj = parent_category_obj.parent_category get_relative_name.short_description = _('Naam') get_relative_name.admin_order_field = [ 'parent_category__parent_category', 'name', ]
РЕДАКТИРОВАТЬ!!! Имена родительской категории не должны отображаться вместе с категорией. Я написал это так, чтобы показать, как модель должна быть заказана. Отображение списка будет просто:
- OS
-- Windows
--- Windows 7
--- Windows 8
--- Windows 10
-- Mac
-- Linux
--- Debian
---- Ubuntu
--- Fedora
---- CentOS
---- Oracle Linux
Всего 2 ответа
Чтобы иметь возможность упорядочить его, необходимо аннотировать набор запросов в 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',)
ОШИБКИ:
<class 'cats.admin.PrivateContentCategoryAdmin'>: (admin.E033) Значение 'ordering [0]' относится к'lative_name ', которое не является атрибутом' 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 также поддерживает это, хотя и с другим синтаксисом:
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
имени.admin_order_field.
Для меня работало добавление нового поля "absolute_name" в модель, которое будет автоматически заполнено сигналом pre_save. После сохранения экземпляра это поле будет содержать имена для всех parent_categories экземпляра до его собственного имени. Наконец, мне просто нужно было заказать экземпляр в этом поле:
class PrivateContentCategory(models.Model):
name = models.CharField(
max_length=250,
verbose_name=_('Naam'),
)
slug = models.SlugField(
verbose_name=_('Url'),
blank=True,
)
parent_category = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
related_name='child_category_list',
verbose_name=_('Hoofdcategorie'),
blank=True,
null=True,
)
absolute_name = models.TextField(
verbose_name=_('Absolute naam'),
blank=True,
)
def __str__(self):
return self.absolute_name
def get_relative_name(self):
str = self.name
parent_category_obj = self.parent_category
while parent_category_obj is not None:
str = '--' + str
parent_category_obj = parent_category_obj.parent_category
return str
get_relative_name.short_description = _('Naam')
get_relative_name.admin_order_field = [
'absolute_name',
]
class Meta:
verbose_name = _('Privé inhoud categorie')
verbose_name_plural = _('Privé inhoud categorieën')
ordering = [
'absolute_name',
]
@receiver(models.signals.pre_save, sender=PrivateContentCategory)
def pre_save_private_content_category_obj(sender, instance, **kwargs):
# START Generate instance.absolute_name
instance.absolute_name = instance.name
parent_category_obj = instance.parent_category
while parent_category_obj is not None:
instance.absolute_name = parent_category_obj.name + ' --> ' + instance.absolute_name
parent_category_obj = parent_category_obj.parent_category
# END Generate instance.absolute_name