Python Django预取与使用管理器方法的自定义queryset

Python Django预取与使用管理器方法的自定义queryset,python,django,Python,Django,让我们看一看django docs的比萨饼和顶级模特的例子。 一个比萨饼可能有多种配料 如果我们提出疑问: pizzas = Pizza.objects.prefetch_related('toppings') 我们将在两次查询中获得所有比萨及其配料。 现在让我们假设我只想预取素食配料(假设我们有这样的属性): 它工作得很好,Django在制作类似这样的东西时,不会对每个比萨饼执行另一个查询: for pizza in pizzas: print(pizza.toppings.filt

让我们看一看django docs的比萨饼和顶级模特的例子。 一个比萨饼可能有多种配料

如果我们提出疑问:

pizzas = Pizza.objects.prefetch_related('toppings')
我们将在两次查询中获得所有比萨及其配料。 现在让我们假设我只想预取素食配料(假设我们有这样的属性):

它工作得很好,Django在制作类似这样的东西时,不会对每个比萨饼执行另一个查询:

for pizza in pizzas:
    print(pizza.toppings.filter(is_vegetarian=True))
现在,让我们假设我们有一个用于浇头模型的自定义管理器,我们决定在其中放置一个方法,该方法允许我们只过滤素食浇头,如上面的代码示例所示:

class ToppingManager(models.Manager):
    def filter_vegetarian(self):
        return self.filter(is_vegetarian=True)
现在,我使用manager中的方法进行新查询并预取自定义查询集:

    pizzas = Pizza.objects.prefetch_related(
        Prefetch('toppings', queryset=Topping.objects.filter_vegetarian()))
并尝试执行我的代码:

    for pizza in pizzas:
        print(pizza.toppings.filter_vegeterian())
对于循环的每次迭代,我都会得到一个新的查询。 这是我的问题。为什么? 这两种构造都返回同一类型的对象,即queryset:

   Topping.objects.filter_vegetarian()
   Topping.objects.filter(is_vegetarian=True)

我没有直接对此进行测试,但是您不应该在循环中再次调用方法或过滤器,因为prefetch_related已经附加了数据。因此,这两种方法中的任何一种都应该有效:

pizzas = Pizza.objects.prefetch_related(
    Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True))
)
for pizza in pizzas:
    print(pizza.toppings.all()) # uses prefetched queryset

您的示例不起作用,因为它们调用了另一个queryset,并且无法将其与预取的queryset进行比较以确定其是否相同

它还说:

prefetch\u相关的('toppings')
隐含
pizza.toppings.all()
,但
pizza.toppings.filter()
是一个新的不同查询。预取缓存在这里无能为力;事实上,这会影响性能,因为您已经完成了一个尚未使用的数据库查询

在筛选预取结果时,建议使用to_attr,因为它比将筛选结果存储在相关管理器的缓存中更加模糊

这一实施:

class ToppingManager(models.Manager):
    def filter_vegetarian(self):
        return self.filter(is_vegetarian=True)
看起来不标准。看起来他们做了一个更安全的方法,为这种懒惰的评估修改超类方法。如果我以这种风格重写您的方法,它将如下所示:

class ToppingManager(models.Manager):
    def filter_vegetarian(self):
        return super(ToppingManager, self).get_queryset().filter(is_vegetarian=True)
这里不需要严格使用super(),但使用它更安全,因为您应该知道您希望从models.Manager get_queryset方法开始

在我自己的环境中对此做了一个简短的测试,我发现它可以在不触发对每个项目的查询的情况下将数据输入到
预回迁
。我没有任何理由相信这不会解决这里的问题


然而,我也倾向于认为,在webjunkie的答案中指定
to_attr
可能也是必要的。

如果使用管理器方法进行预回迁,但在for循环中进行打印(pizza.toppings.filter(is_素食=True)),是否会进行额外的查询?我有一种感觉,我理解为什么会发生这种情况,只是想确保它像我想象的那样运行。我开始调试它,甚至在第一个示例中,我们也会有一个查询循环。这就是为什么医生们建议使用我们来进行治疗,但这仍然很有趣。为什么不在django中实现这个特性呢?如果在我们仍然可以诚实地使用缓存结果之后,预取查询集与过滤查询集是一样的,我认为原因是遵循逻辑会更加困难,并且会使开发成为任何在实现时没有直接处理它的人的噩梦。您必须记住,Django是开源的,因此,一定数量的人需要能够理解它(它不一定是一个很大的数字)。由于您查询一个模型,而queryset返回模型,因此让它返回的模型包含它们自己的queryset和已经执行的查找可能会让人头晕目眩,特别是如果您有一个更复杂的预回迁,您当然可以考虑自己添加它!这似乎是一个利基,但潜在有用的功能,也许如果你可以简单地编码它跟随,核心团队会考虑加入它。我喜欢这个,但我有一个大问题。什么样的东西应该
veg_浇头
覆盖?如果我没有应用这个高度特定的
prefetch\u related
,那么我希望通过一个新查询获得相同的内容,否则它违反了重用性的良好实践。如果我可以为自定义管理器设置一个属性,那么我更愿意以某种方式直接预取该属性,而不是为了优化而重新编写行为。
class ToppingManager(models.Manager):
    def filter_vegetarian(self):
        return self.filter(is_vegetarian=True)
class ToppingManager(models.Manager):
    def filter_vegetarian(self):
        return super(ToppingManager, self).get_queryset().filter(is_vegetarian=True)