Динамическое упорядочение материализованных узлов пути django-treebeard - PullRequest
0 голосов
/ 25 июня 2018

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

Но так как модель / менеджер категории Oscars основана на реализации django-treebeard s материализованного дерева путей , я должен рассмотреть несколькоОтличия от обычного 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), так что этот подход не подействуетose самого 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 Ответ

0 голосов
/ 29 июня 2018

Так что эта проблема поначалу вызывала у меня сильную головную боль - я пробовал разные подходы, и ни один из них не был удовлетворительным - попытка создать динамически отсортированное MP_Tree - это то, что я бы сильно обескуражил на этом этапе, это слишком много беспорядка,Лучше придерживаться порядка, созданного во время вставки, и работать со всеми переменными на этом этапе.Также я благодарю @solarissmoke за предоставленную помощь в комментариях.

Models

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 %}

Admin

Вам нужно будет внести изменения в представления, форм и наборов форм в 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')
...