Angenommen, ich habe ein Modell Box
mit einer GenericForeignKey
, die entweder auf eine Apple
Instanz oder eine Chocolate
Instanz zeigt. Apple
und Chocolate
wiederum haben ForeignKeys zu Farm
bzw. Factory
. Ich möchte eine Liste von Box
es anzeigen, für die ich auf Farm
und Factory
zugreifen muss. Wie mache ich das in so wenigen DB-Abfragen wie möglich?django: Prefetch verwandte Objekte eines GenericForeignKey
Minimal anschauliches Beispiel:
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)
Hier sind ein paar Dinge, die ich versuchte. In all diesen Beispielen ist N die Anzahl der Boxen. Der Abfrage-Count geht davon aus, dass die ContentType
s für Apple
und Chocolate
bereits zwischengespeichert wurden, so dass die get_for_model()
Anrufe nicht die DB treffen.
1) Naive:
print [box for box in Box.objects.all()]
Dies gilt (Fetch Boxes) + N (Fetch Apple oder Schokolade für jede Box) + N (Fetch Farm für jeden Apple und Factory für jede Schokolade) Abfragen.
2) select_related
hilft hier nicht, denn Box.content_object
ist ein GenericForeignKey
.
3) Ab django 1.4, prefetch_related
kann GenericForeignKey
s holen.
print [box for box in Box.objects.prefetch_related('content_object').all()]
Dies gilt (Fetch Boxes) + (holen Äpfel und Schokolade für alle Boxen) + N (Fetch Farm für jeden Apple und Fabrik für jede Schokolade) Abfragen.
4) Anscheinend prefetch_related
ist nicht intelligent genug, ForeignKeys von GenericForeignKeys zu folgen. Wenn ich versuche:
print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]
es beschwert sich zu Recht, dass Chocolate
Objekte haben keine farm
Feld, und umgekehrt.
5) Ich konnte tun:
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')
Dies gilt (Fetch Boxes) + (holen Äpfel und Schokolade für alle Boxen) + (Fetch Farms für alle Äpfel und Fabriken für alle Schokoladen) Abfragen. Der Nachteil ist, dass ich die beiden Abfragesätze (boxes_with_apples
, boxes_with_chocolates
) manuell zusammenführen und sortieren muss. In meiner realen Anwendung zeige ich diese Boxen in einem paginierten ModelAdmin an. Es ist nicht offensichtlich, wie diese Lösung dort integriert werden kann. Vielleicht könnte ich einen benutzerdefinierten Paginator schreiben, um diesen Cache transparent zu machen?
6) Ich könnte etwas zusammen bauen, basierend auf this, das auch O (1) Abfragen durchführt. Aber ich würde lieber nicht mit Interna (_content_object_cache
) Probleme machen, wenn ich es vermeiden kann.
Zusammengefasst: Drucken einer Box erfordert Zugriff auf die ForeignKeys eines GenericForeignKey. Wie kann ich N Boxen in O (1) Abfragen drucken? Ist (5) das Beste, was ich tun kann, oder gibt es eine einfachere Lösung?
Bonuspunkte: Wie würden Sie dieses DB-Schema umgestalten, um solche Abfragen zu vereinfachen?
verwenden Wenn Sie umbenennen 'Zuchtbetrieb /' factory' zu einem gemeinsamen Namen, wie 'creator', Arbeit prefetch_related wird? – Igor
Tatsächlich funktioniert 'prefetch_related ('content_object__creator')' nach dem vorgeschlagenen Umbenennen. Leider kann das Umbenennen in Abhängigkeit von den tatsächlichen Modellen, die Sie anstelle von Apple/Farm und Chocolate/Factory haben, sinnvoll sein oder auch nicht. – cberzan