如何使用 django prefetch_related 在 mptt 树上预取孩子的父母?

Sen*_* H. 7 django prefetch mptt

比如说,我有一个产品实例。产品实例链接到第 4 级子类别。如果我只想获取根类别和第 4 级子类别,下面的查询足以以最少的数据库查询获取数据:

Product.objects.filter(active=True).prefetch_related('category__root',
                                                     'category')
Run Code Online (Sandbox Code Playgroud)

如果我必须联系该产品类别的父级并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')
Run Code Online (Sandbox Code Playgroud)

但是当深度级别未知时,此查询无效。有没有办法在上面的查询中动态预取父母?

nig*_*239 2

老问题,但我会尝试一下。

但这将需要额外的查询。(但这比可能的数百个要好。-如果不是更多的话。)

一些解释;

首先:我们需要确定活跃产品的类别深度有多少。

为了避免每次额外的查询,如果类别是 static ,您可以在启动时缓存以下代码。

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

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

level 0 = no parents
level 1 = 1 parent
level 2 = 2 parents (nested)
level 3 = 3 parents (nested)
Run Code Online (Sandbox Code Playgroud)

这意味着我们可以轻松地循环range级别,并将父级(字符串)附加到列表中。

prefetch_string = 'category'
prefetch_list = []
for i in range(max_level):
    prefetch_string += '__parent'
    prefetch_list.append(prefetch_string)
Run Code Online (Sandbox Code Playgroud)

第三:我们传入prefetch_list,也将其解包。

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

然后我们可以轻松地将其重构为单个动态函数。

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
Run Code Online (Sandbox Code Playgroud)

然后你可以通过以下方式获取预取列表:

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

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