首页 > 解决方案 > django-treebeard物化路径节点的动态排序

问题描述

我有一个基于django-oscar(and ) 的项目,它使用该模块django-cms在具有不同SITE_IDs 的多个域上运行。django.contrib.sites该项目已经富有成效,我不能再更改类别 slug - 我希望避免将整个代码切换到嵌套集树或相邻树 - 为了您更好地理解:初始要求不想要不同的类别排序对于每个域,所以我只使用 Oscars 默认类别实现。

但是由于 Oscars 类别模型/管理器基于物化路径树实现,因此我必须考虑与通常的 Django 更改默认排序方式的一些差异django-treebeard

给定模型中的这两个额外字段(继承自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']

首先,它将忽略该指令,因为treebeardsMP_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

标签: djangodjango-oscardjango-treebeard

解决方案


所以这个问题一开始让我很头疼——我尝试了不同的方法,但没有一个是令人满意的——尝试制作一个动态排序的 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/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')

推荐阅读