Supongamos que tengo un modelo Box
con un GenericForeignKey
que apunta a o bien un Apple
instancia o una instancia de Chocolate
. Apple
y Chocolate
, a su vez, tienen ForeignKeys a Farm
y Factory
, respectivamente. Quiero mostrar una lista de Box
es, para lo cual necesito acceder a Farm
y Factory
. ¿Cómo hago esto en la menor cantidad posible de consultas DB?django: objetos relacionados de recuperación previa de un GenericForeignKey
Mínimo ejemplo ilustrativo:
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)
Estas son algunas cosas que he probado. En todos estos ejemplos, N es el número de cuadros. El recuento de consultas supone que ContentType
s para Apple
y Chocolate
ya se han almacenado en caché, por lo que las llamadas get_for_model()
no llegan a la base de datos.
1) Naive:
print [box for box in Box.objects.all()]
Esto (traiga cajas) + N (fetch Apple o chocolate para cada caja) + N (fetch Granja para cada Apple y Fábrica para cada chocolate) consultas.
2) select_related
no ayuda aquí, porque Box.content_object
es un GenericForeignKey
.
3) A partir de django 1.4, prefetch_related
puede obtener GenericForeignKey
s.
print [box for box in Box.objects.prefetch_related('content_object').all()]
Esto (traiga cajas) + (ir a buscar las manzanas y chocolates para todas las cajas) + N (fetch Granja para cada manzana y la fábrica de chocolate) para cada pregunta.
4) Al parecer prefetch_related
no es lo suficientemente inteligente como para seguir ForeignKeys de GenericForeignKeys. Si trato:
print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]
que con razón se queja de que Chocolate
objetos no tienen un campo farm
, y viceversa.
5) que podía hacer:
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')
Esto (fetch Cajas) + (podido recuperar Manzanas y Chocolates para todas las cajas) + (FETCH de campo en todas las manzanas y Fábricas para todos los chocolates) consultas. La desventaja es que tengo que fusionar y ordenar los dos conjuntos de consultas (boxes_with_apples
, boxes_with_chocolates
) manualmente. En mi aplicación real, estoy mostrando estas cajas en un ModelAdmin paginado. No es obvio cómo integrar esta solución allí. ¿Tal vez podría escribir un Paginator personalizado para hacer este almacenamiento en caché de forma transparente?
6) Podría improvisar algo basado en this que también hace O (1) consultas. Pero prefiero no meterme con los internos (_content_object_cache
) si puedo evitarlo.
En resumen: La impresión de un cuadro requiere acceso a las teclas externas de un GenericForeignKey. ¿Cómo puedo imprimir N Boxes en O (1) consultas? ¿Es (5) lo mejor que puedo hacer, o hay una solución más simple?
Puntos de bonificación: ¿Cómo podría refaccionar este esquema de base de datos para facilitar tales consultas?
Si cambia el nombre 'farm' /' factory' a un nombre común, como 'creator', se prefetch_related trabajo? – Igor
De hecho, 'prefetch_related ('content_object__creator')' funciona después de su cambio de nombre sugerido. Desafortunadamente, el cambio de nombre puede o no tener sentido según los modelos reales que tenga en lugar de Apple/Farm y Chocolate/Factory. – cberzan