django-treebeard 物化路径节点的动态排序
Dynamic ordering of django-treebeard materialized path nodes
我有一个基于 django-oscar
(和 django-cms
)的项目,它使用 django.contrib.sites
模块在具有不同 SITE_ID
的多个域上运行。该项目已经富有成效,我不能再更改类别 slugs - 我希望避免将整个代码切换到嵌套集树或相邻树 - 为了您更好地理解:初始要求不需要不同的类别排序对于每个域,所以我只使用 Oscars 默认类别实现。
但是由于 Oscars 类别 model/manager 是基于 django-treebeard
s materialized path tree 实现的,我必须考虑一些与更改默认排序的常用 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 中,例如:
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
) 中,因此这种方法违背了 MP_Tree 本身的目的。
我也看过 node_order_by
- 但正如 docs 中所述:
Incorrect ordering of nodes when node_order_by
is enabled. Ordering is
enforced on node insertion, so if an attribute in node_order_by
is
modified after the node is inserted, the tree ordering will be
inconsistent.
但是对于具有不同类别排序的多个域,这同样没有用。我能想到的唯一方法是覆盖 MP_NodeManager
class:
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
?
所以这个问题一开始让我很头疼 - 我尝试了不同的方法,但没有一个是令人满意的 - 尝试动态排序 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')
我有一个基于 django-oscar
(和 django-cms
)的项目,它使用 django.contrib.sites
模块在具有不同 SITE_ID
的多个域上运行。该项目已经富有成效,我不能再更改类别 slugs - 我希望避免将整个代码切换到嵌套集树或相邻树 - 为了您更好地理解:初始要求不需要不同的类别排序对于每个域,所以我只使用 Oscars 默认类别实现。
但是由于 Oscars 类别 model/manager 是基于 django-treebeard
s materialized path tree 实现的,我必须考虑一些与更改默认排序的常用 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 中,例如:
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
) 中,因此这种方法违背了 MP_Tree 本身的目的。
我也看过 node_order_by
- 但正如 docs 中所述:
Incorrect ordering of nodes when
node_order_by
is enabled. Ordering is enforced on node insertion, so if an attribute innode_order_by
is modified after the node is inserted, the tree ordering will be inconsistent.
但是对于具有不同类别排序的多个域,这同样没有用。我能想到的唯一方法是覆盖 MP_NodeManager
class:
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
?
所以这个问题一开始让我很头疼 - 我尝试了不同的方法,但没有一个是令人满意的 - 尝试动态排序 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')