如何使用Django prefetch_related预取MPTT树中子节点的父节点?

10

比如说,我有一个产品实例。该产品实例与第四级子类别相关联。如果我只想获取根类别和第四级子类别,则下面的查询足以使用最少的数据库查询来提取数据:

Product.objects.filter(active=True).prefetch_related('category__root',
                                                     'category')

如果我需要联系到这个产品类别的父级,并且使用get_ancestors()方法,数据库查询次数将增加近三倍。

相反,如果我像下面这样编写查询,而不使用get_ancestors()方法,数据库查询次数就会保持较低水平。

Product.objects.filter(active=True).prefetch_related(
    'category__root',
    'category', 
    'category__parent',
    'category__parent__parent',
    'category__parent__parent__parent',
    'category__parent__parent__parent__parent')

但是当深度未知时,这个查询并不有效。那么在上述查询中有没有一种动态预取父项的方法呢?

1个回答

2

虽然这个问题早已有人问过,但我会尽力回答。

不过这需要进行额外的查询。(但这比可能出现的数百次查询要好——如果不是更多的话。)

一些解释:

首先:我们需要确定活跃产品所属类别的深度。

为了避免每次都进行额外的查询,如果类别是静态的,你可以在启动时缓存以下代码。

max_level = Category.objects.filter(product_set__active=True)\ # Reverse lookup on product
    .values('level')\
    .aggregate(
        max_level=models.Max('level')
    )['max_level']

第二步:我们需要根据级别创建预取字符串。最大级别数量等于最大父级数量。

level 0 = no parents
level 1 = 1 parent
level 2 = 2 parents (nested)
level 3 = 3 parents (nested)

这意味着我们可以轻松地循环遍历级别的范围,并将父项(字符串)添加到列表中。
prefetch_string = 'category'
prefetch_list = []
for i in range(max_level):
    prefetch_string += '__parent'
    prefetch_list.append(prefetch_string)

第三步:我们传入prefetch_list,同时对其进行解包。

Product.objects.filter(active=True).prefetch_related(
    'category__root',
    'category', 
    *prefetch_list) # unpack the list into args.

我们可以将这个内容轻松地重构成一个单一的动态函数。
def get_mptt_prefetch(field_name, lookup_name='__parent', related_model_qs=None): 
    max_level = related_model_qs\
            .values('level')\
            .aggregate(
                max_level=models.Max('level')
            )['max_level']
    prefetch_list = []
    prefetch_string = field_name
    for i in range(max_level):
        prefetch_string += lookup_name
        prefetch_list.append(prefetch_string)
    return prefetch_list

然后,您可以使用以下方法获取预取列表:

prefetch_list = get_mptt_prefetch(
    'category',
    related_model_qs=Category.objects.filter(product_set__active=True), # To only get categories which contain active products.
)

https://django-mptt.readthedocs.io/en/latest/technical_details.html#level


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接