Динамическое упорядочение материализованных узлов пути django-treebeard
У меня есть проект на основе django-oscar
(а также django-cms
), который работает на нескольких доменах с разными SITE_ID
с помощью django.contrib.sites
модуль. Проект уже продуктивен, и я больше не могу менять слагов категорий. Также я хочу избежать переключения всего кода на деревья вложенных множеств или смежные деревья - для вашего лучшего понимания: первоначальные требования не требовали сортировки по другим категориям. для каждого домена, поэтому я просто использовал реализацию категории Oscars по умолчанию.
Но так как модель / менеджер категории Оскар основана на django-treebeard
Реализовав реализацию дерева путей, я должен рассмотреть несколько отличий от обычного Django-способа изменения порядка по умолчанию.
Учитывая эти два дополнительных поля в модели (которая наследует от django-oscar
s AbstractCategory)
class Category(AbstractCategory):
# ...
order_site_1 = models.IntegerField(
null=False,
default=0
)
order_site_2 = models.IntegerField(
null=False,
default=0
)
Я не могу просто добавить порядок в мета-класс, как:
class Category(AbstractCategory):
class Meta:
ordering = ['depth', '-order_site_{}'.format(Site.objects.get_current().id), 'name']
Во-первых, он будет игнорировать эту директиву, так как treebeard
s MP_NodeManager.get_queryset()
не учитывает пользовательскую сортировку - для логики MP_Tree она должна полагаться на сортировку, сгенерированную во время вставки (которая хранится в path
), таким образом, этот подход не соответствует цели самого MP_Tree.
Я также посмотрел на node_order_by
- но, как указано в документах:
Неправильный порядок узлов при
node_order_by
включен. Упорядочение осуществляется при вставке узла, поэтому, если атрибут вnode_order_by
изменяется после вставки узла, упорядочение дерева будет несовместимым.
Но опять же на нескольких доменах с разным упорядочением по категориям это бесполезно. Единственный путь, который я могу придумать, это переопределить MP_NodeManager
учебный класс:
class CustomOrderQuerySet(MP_NodeManager):
def get_queryset(self):
sort_key = '-order_site_{}'.format(Site.objects.get_current().id)
return MP_NodeQuerySet(self.model).order_by('depth', sort_key, 'name')
Но этот подход явно неверен, потому что MP_Tree опирается на path
сортировка для генерации правильного дерева - то, о чем я думал, это перебирать все записи, сравнить path
, игнорировать последнюю часть и сортировать по order_site_{id}
и преобразовать его в QuerySet
,
Я даже не уверен, возможно ли это, и я хочу избежать этого, поэтому мой вопрос: есть ли способ отразить эту логику в цепочке операторов ORM, чтобы я мог продолжать использовать нативный QuerySet
?
1 ответ
Так что эта проблема поначалу вызывала у меня определенную головную боль - я пробовал разные подходы, и ни один из них не был удовлетворительным - попытка создать динамически отсортированное MP_Tree - это то, что я бы сильно обескуражил на этом этапе, это слишком много беспорядка. Лучше придерживаться порядка, созданного во время вставки, и работать со всеми переменными на этом этапе. Также я благодарю @solarissmoke за предоставленную помощь в комментариях.
модели
class Product(AbstractProduct):
# ...
@property
def site_categories(self):
return self.categories.filter(django_site_id=settings.SITE_ID)
@property
def first_category(self):
if not self.site_categories:
return None
return self.site_categories[0]
class Category(AbstractCategory):
# ...
django_site = models.ForeignKey(Site, null=False)
site_rank = models.IntegerField(_('Site Rank'), null=False, default=0)
node_order_by = ['django_site_id', 'site_rank', 'name']
Templatetags
копия templatetags/category_tags#get_annotated_list
спроектировать и немного изменить его:
# ...
start_depth, prev_depth = (None, None)
if parent:
categories = parent.get_descendants()
if max_depth is not None:
max_depth += parent.get_depth()
else:
categories = Category.get_tree()
# Just add this line
categories = categories.filter(django_site_id=settings.SITE_ID)
# ...
Шаблоны
За templates/catalogue/detail.html
: Заменить {% with category=product.categories.all.0 %}
с {% with category=product.first_category %}
Администратор
Вам нужно будет внести изменения в представления, формы и наборы форм внутри apps.dashboard
позаботиться о недавно добавленном django_site
а также site_rank
- Я пропустил это, потому что в моем случае все категории определяются через регулярно импортируемые CSV. Это не должно быть большим делом, чтобы быть выполненным - возможно позже я сделаю это и обновлю этот ответ.
Создание узлов MP
Всякий раз, когда вы добавляете корневые, дочерние или дочерние узлы, вам нужно передать django_site_id
параметр (вместе со всеми другими обязательными значениями, содержащимися в node_order_by
), например:
parent_category.add_child(
django_site_id=settings.SITE_ID, site_rank=10, name='Child 1')
Category.add_root(
django_site_id=settings.SITE_ID, site_rank=1, name='Root 1')