代码之家  ›  专栏  ›  技术社区  ›  wiesion

Django树形物化路径节点的动态排序

  •  2
  • wiesion  · 技术社区  · 7 年前

    我有一个基于 django-oscar (和 django-cms ,它运行在具有不同 SITE_ID 使用 django.contrib.sites 模块。这个项目已经很有成效了,我不能再改变类别slug了——我也希望避免将整个代码切换到嵌套的集合树或相邻的树上——为了让您更好地理解:最初的需求不希望每个域有不同的类别排序,所以我只是使用了奥斯卡默认类别的实现。

    但是由于奥斯卡的分类模型/管理器是基于 django-treebeard S公司 materialized path tree 实现时,我不得不考虑一些不同于通常的django更改默认顺序的方法。

    给定模型中的这两个额外字段(它继承自 奥斯卡 抽象范畴)

    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_树逻辑,它必须依赖于在插入时生成的排序(存储在 path ,因此这种方法将违背mp_tree本身的目的。

    我也看了一眼 node_order_by -但正如 docs 以下内容:

    节点顺序 已启用。订购是 在节点插入时强制执行,因此如果 节点顺序 是 修改节点插入后,树排序将是 不一致的。

    但在多个不同类别排序的域上,这同样没有用处。我唯一能想到的办法就是 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')
    

    但是这种方法显然是错误的,因为MPTHEAR依赖于 路径 排序以生成正确的树-我想的是遍历所有条目,比较 路径 ,忽略最后一部分并根据 order_site_{id} 并将其重新转换为 QuerySet 是的。

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

    1 回复  |  直到 7 年前
        1
  •  1
  •   wiesion    7 年前

    因此,这个问题让我很头疼最初-我尝试了不同的方法,但没有一个是令人满意的-试图使一个动态排序的mp_树是我在这一点上会极力劝阻,这是一个太多的混乱。最好坚持在插入时创建的顺序,并在该阶段处理所有变量。同时我也感谢@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')