【问题标题】:Dynamic ordering of django-treebeard materialized path nodesdjango-treebeard物化路径节点的动态排序
【发布时间】:2018-12-04 06:06:37
【问题描述】:

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

但由于 Oscars 类别模型/经理基于 django-treebeards materialized path tree 实现,我必须考虑与通常的 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 - 但正如docs中所述:

启用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,忽略最后一部分并根据 @987654341 排序@ 并将其重新转换为 QuerySet

我什至不确定这是否可能,我想避免去那里,所以我的问题是:有没有办法在 ORM 语句链中反映这种逻辑,以便我可以继续使用本机QuerySet?

【问题讨论】:

  • 我并没有真正适合您的解决方案,但只想说使用物化路径的全部意义在于提高查询效率。如果您最终覆盖查询集/排序,您将失去这种效率,并且执行基本查询(例如,获取类别及其祖先)很快就会变得非常昂贵。这在奥斯卡已经有些低效了,而且会变得更糟。
  • 我完全同意你写的,但正如我所说,现在改变分类系统意味着很多工作,特别是为每个 MP 重定向到新的 NS/AS 是我希望避免的事情,所以我想知道...除此之外,您对如何替换类别系统有什么建议吗?
  • 您在同一个数据库上运行了多少个站点/域?
  • 目前只有2个,没有第三个或第四个的计划,所以目前不需要完全动态SITE_ID
  • 您可以考虑维护两个单独的类别树,每个站点一个。然后,您需要重写一些视图逻辑来确定每个站点使用哪个树,但您不必为各个类别弄乱路径。

标签: django django-oscar django-treebeard


【解决方案1】:

所以这个问题一开始让我很头疼 - 我尝试了不同的方法,但没有一种方法令人满意 - 尝试制作动态排序的 MP_Tree 是我在这一点上非常不鼓励的事情,它太混乱了.最好坚持在插入时创建的排序并在该阶段处理所有变量。我还要感谢 @solarissmoke 在 cmets 中提供的帮助。

 模型

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_sitesite_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')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多