Załóżmy, że mam model Box
z GenericForeignKey
, który wskazuje na instancję Apple
lub instancję Chocolate
. Apple
i Chocolate
, z kolei mają klucze ForeignKeys odpowiednio do Farm
i Factory
. Chcę wyświetlić listę Box
es, dla których muszę uzyskać dostęp do Farm
i Factory
. Jak to zrobić w jak najmniejszej liczbie zapytań DB?django: obiekty związane z prefetch z GenericForeignKey
Minimal przykład ilustracyjny:
class Farm(Model):
...
class Apple(Model):
farm = ForeignKey(Farm)
...
class Factory(Model):
...
class Chocolate(Model):
factory = ForeignKey(Factory)
...
class Box(Model)
content_type = ForeignKey(ContentType)
object_id = PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
...
def __unicode__(self):
if self.content_type == ContentType.objects.get_for_model(Apple):
apple = self.content_object
return "Apple {} from Farm {}".format(apple, apple.farm)
elif self.content_type == ContentType.objects.get_for_model(Chocolate):
chocolate = self.content_object
return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory)
Oto kilka rzeczy próbowałem. We wszystkich tych przykładach liczba pudełek to N. Liczba zapytań zakłada, że numery ContentType
dla Apple
i Chocolate
zostały już zbuforowane, więc wywołania get_for_model()
nie trafiają w DB.
1) naiwnego
print [box for box in Box.objects.all()]
ten sposób (pobranie Pudełka) + N (pobranie jabłkowy lub czekolady na każdym pudełku) + N (pobranie Farm dla każdego Apple i Fabryka dla każdej czekolady).
2) select_related
nie pomaga tutaj, ponieważ Box.content_object
to GenericForeignKey
.
3) Od django 1.4, prefetch_related
można pobrać GenericForeignKey
s.
print [box for box in Box.objects.prefetch_related('content_object').all()]
ten sposób (pobranie skrzynek) + (pobierania jabłka czekolady dla wszystkich skrzynek) + N (pobranie Farm każdego Apple i fabrycznego każdej czekolady) zapytania.
4) Podobno prefetch_related
nie jest wystarczająco inteligentny, aby śledzić ForeignKeys z GenericForeignKeys. Gdy próbuję:
print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]
to słusznie twierdzi, że Chocolate
obiekty nie mają pole farm
, i vice versa.
5) można zrobić:
apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm')
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory')
ten sposób (pobranie skrzynek) + (pobierania jabłka czekolady wszystkich ramkach) + (pobranie Farms dla jabłka i Fabryki dla wszystkich czekoladek). Minusem jest to, że muszę scalić i sortować dwa zestawy zapytań (boxes_with_apples
, boxes_with_chocolates
) ręcznie. W mojej prawdziwej aplikacji wyświetlam te Ramki w paginowanym ModelAdmin. Nie jest oczywiste, jak zintegrować to rozwiązanie. Może mógłbym napisać niestandardowy Paginator, aby to buforowanie było przejrzyste?
6) Mogłem połączyć coś na podstawie this, które również wykonuje zapytania O (1). Ale wolałbym nie zadzierać z internals (_content_object_cache
), jeśli mogę tego uniknąć.
Podsumowując: Drukowanie skrzynki wymaga dostępu do przycisków ForeignKeys klucza GenericForeignKey. Jak mogę wydrukować skrzynki N w zapytaniach O (1)? Czy (5) najlepsze, co mogę zrobić, czy jest prostsze rozwiązanie?
Punkty bonusowe: Jak można zmienić ten schemat bazy danych, aby ułatwić takie zapytania?
przypadku zmiany nazwy 'farm' /' factory' pewnym wspólną nazwą, jak 'creator', będzie prefetch_related pracę? – Igor
Rzeczywiście, 'prefetch_related ('content_object__creator')' działa po twojej sugerowanej zmianie nazwy. Niestety, zmiana nazwy może być lub nie mieć sensu w zależności od rzeczywistych modeli, które masz na miejscu Apple/Farm i Chocolate/Factory. – cberzan