Динамическое упорядочение материализованных узлов пути django-treebeard

У меня есть проект на основе django-oscar (а также django-cms), который работает на нескольких доменах с разными SITE_IDс помощью django.contrib.sites модуль. Проект уже продуктивен, и я больше не могу менять слагов категорий. Также я хочу избежать переключения всего кода на деревья вложенных множеств или смежные деревья - для вашего лучшего понимания: первоначальные требования не требовали сортировки по другим категориям. для каждого домена, поэтому я просто использовал реализацию категории Oscars по умолчанию.

Но так как модель / менеджер категории Оскар основана на django-treebeardРеализовав реализацию дерева путей, я должен рассмотреть несколько отличий от обычного Django-способа изменения порядка по умолчанию.

Учитывая эти два дополнительных поля в модели (которая наследует от django-oscars 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']

Во-первых, он будет игнорировать эту директиву, так как treebeards 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')
Другие вопросы по тегам