Django Prefetch with a custom request that uses the managers method

Check out an example from django docs with the Pizza and Topping models. One pizza can have several toppings.

If we make a request:

pizzas = Pizza.objects.prefetch_related('toppings') 

We will get all the pizzas and their filling in 2 requests. Now suppose I want to pre-select only vegetarian fillings (suppose we have this property):

 pizzas = Pizza.objects.prefetch_related( Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True)) ) 

This works very well, and Django does not do another request for every pizza, doing something like this:

 for pizza in pizzas: print(pizza.toppings.filter(is_vegetarian=True)) 

Now suppose we have a user manager for the Topping model, and we decided to put a method there that allows us to filter only vegetarian fillings, as in the above code example:

 class ToppingManager(models.Manager): def filter_vegetarian(self): return self.filter(is_vegetarian=True) 

Now I make a new request and pre-select a user request with my method from the manager:

  pizzas = Pizza.objects.prefetch_related( Prefetch('toppings', queryset=Topping.objects.filter_vegetarian())) 

And try to execute my code:

  for pizza in pizzas: print(pizza.toppings.filter_vegeterian()) 

I get a new request for each iteration of the loop. This is my question. What for? Both of these constructs return an object of the same type as the query:

  Topping.objects.filter_vegetarian() Topping.objects.filter(is_vegetarian=True) 
+5
source share
2 answers

I have not tested this directly, but you should not call the method or filter again in a loop, since prefetch_related already binds the data. Therefore, any of them should work:

 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 

or

 pizzas = Pizza.objects.prefetch_related( Prefetch('toppings', queryset=Topping.objects.filter_vegetarian(), to_attr="veg_toppings")) for pizza in pizzas: print(pizza.toppings.veg_toppings) 

Your examples do not work because they trigger a different set of queries, and this cannot be compared with the predefined ones to determine if it will be the same.

He also says in the docs :

prefetch_related('toppings') implied by pizza.toppings.all() , but pizza.toppings.filter() is a new and different request. A pre-programmed cache can help here; in fact, it will hurt performance because you made a query on the database you used.

and

Using to_attr is recommended when filtering the result of the prefetch, since it is less ambiguous than storing the filtered result in the cache of the respective managers.

+1
source

This implementation:

 class ToppingManager(models.Manager): def filter_vegetarian(self): return self.filter(is_vegetarian=True) 

It looks non-standard. docs look as if they are making a safer method of changing the superclass method for these kind of lazy-eval things. If I rewrote your method in this style, it will look like this:

 class ToppingManager(models.Manager): def filter_vegetarian(self): return super(ToppingManager, self).get_queryset().filter(is_vegetarian=True) 

You won't need super () here, but it’s safer to use it because you need to know that you want to start with the model.Manager get_queryset method.

Performing a brief check of this in my own environment, I found that it works in Prefetch mode without running queries on each element. I have no reason to believe that this will not work for the problem here.

However, I am also inclined to believe that specifying to_attr in the webjunkie answer may also be necessary.

0
source

All Articles