2016-07-20 7 views
5

Spójrzmy na przykład z django docs z modelami Pizza and Topping. Jedna pizza może mieć wiele dodatków.Django Prefetch z niestandardowym zestawem zapytań, który korzysta z metody menedżerów

Jeśli robimy zapytanie:

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

Będziemy uzyskać wszystkie pizze i ich dodatków w 2 zapytaniami. Teraz załóżmy, że chcę wstępne pobieranie tylko wegetariańskie polewy (zakładamy, mamy taką właściwość):

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

To działa całkiem dobrze i Django nie wykonuje kolejną kwerendę dla każdej pizzy, gdy zrobienie czegoś takiego:

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

teraz załóżmy mamy menedżera niestandardową Doładowanie model i zdecydowaliśmy się umieścić tam metodę, która pozwala nam na filtrowanie tylko wegetariańskie siarkowe jak w przykładzie powyżej kod:

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

Teraz robię nowe zapytanie i preselekcji niestandardowego queryset z mojej metody z menedżerem:

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

a próbuję wykonać mój kod:

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

dostaję nową kwerendę dla każdej iteracji pętli. Oto moje pytanie. Czemu? Obie te konstrukcje wrócić tego samego obiektu typu, który jest queryset:

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

Jeśli wykonujesz wstępne pobieranie za pomocą metody menedżera, ale następnie w pętli for do drukowania (pizza.toppings.filter (is_vegetarian = True)), czy robi dodatkowe kwerendy? Mam przeczucie, że rozumiem, dlaczego tak się dzieje, chcę tylko upewnić się, że działa tak, jak sobie wyobrażam. –

+0

Zacząłem debugować i wygląda na to, że nawet w pierwszym przykładzie będziemy mieli również mnóstwo pytań. Dlatego docs polecają nas używać to_attr –

+0

Ale nadal jest to interesujące. Dlaczego nie wdrożyć tej funkcji w django? Jeśli zestaw zapytań jest taki sam jak filtrowany po tym, jak nadal możemy używać wyników z pamięci podręcznej –

Odpowiedz

1

Nie testowałem tego bezpośrednio, ale nie powinien wywołać metodę lub filtr ponownie w pętli, jak prefetch_related już dołączone dane. Więc albo z nich powinno działać:

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 

lub

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) 

Twoje przykłady nie działają, ponieważ wywołać kolejną queryset, a to nie może być porównywane do wstępnie pobrać jednego celu ustalenia, czy będzie to samo.

mówi też tak in the docs:

prefetch_related('toppings') domniemanych pizza.toppings.all(), ale pizza.toppings.filter() jest nowe i inne zapytania. W tym przypadku nie można uzyskać pomocy z poprzedniej pamięci podręcznej; w rzeczywistości boli wydajność, ponieważ wykonałeś kwerendę bazy danych, która nie była używana.

i

Korzystanie to_attr jest zalecany podczas filtrowania w dół wyniku preselekcji, ponieważ jest mniej dwuznaczne niż przechowywanie przefiltrowaną wynik w pamięci podręcznej powiązanego menedżera.

+0

Podoba mi się to, ale mam jedno wielkie pytanie. Jakie rzeczy powinny zostać zastąpione przez veg_toppings? Jeśli nie zastosowałem tego wysoce specyficznego "prefetch_related", to chciałbym uzyskać tę samą zawartość z nowym zapytaniem, w przeciwnym razie narusza to dobre praktyki dotyczące ponownego użycia. Gdybym mógł ustawić atrybut dla niestandardowego menedżera, wolałbym go jakoś wstępnie pobrać bezpośrednio, zamiast przepisać to zachowanie na potrzeby optymalizacji. – AlanSE

0

Ta implementacja:

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

Wygląda niestandardowe. docs Wygląda na to, że robią bezpieczniejszą metodę modyfikacji superklasycznej metody dla tego rodzaju leniwych rzeczy. Gdybym przepisać swoją metodę w tym stylu, to będzie wyglądać:

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

Nie będzie ściśle potrzebują super() tutaj, ale bezpieczniejsze w użyciu, bo trzeba wiedziećże chcesz zacząć model.Manager get_queryset method.

Przeprowadzając krótki test tego w moim własnym środowisku, stwierdzam, że działa on zasilając Prefetch bez wyzwalania zapytań dotyczących każdego elementu. Nie mam żadnego powodu, by sądzić, że to nie zadziała w tym przypadku.

Jednak jestem również skłonny uwierzyć, że określenie to_attr w odpowiedzi webjunkie może również być konieczne.