Pregunta

Tengo la siguiente estructura de modelo:

class Container(models.Model):
    pass

class Generic(models.Model):
    name = models.CharacterField(unique=True)
    cont = models.ManyToManyField(Container, null=True)
    # It is possible to have a Generic object not associated with any container, 
    # thats why null=True

class Specific1(Generic):
    ...

class Specific2(Generic):
    ...

...

class SpecificN(Generic):
    ...

Digamos que necesito recuperar todos los modelos de tipo Específico , que tienen una relación con un Contenedor particular.

El SQL para eso es más o menos trivial, pero esa no es la cuestión. Desafortunadamente, no tengo mucha experiencia trabajando con ORM (el ORM de Django en particular), por lo que es posible que me falte un patrón aquí.

Cuando se hace de forma bruta, -

c = Container.objects.get(name='somename') # this gets me the container
items = c.generic_set.all() 
# this gets me all Generic objects, that are related to the container
# Now what? I need to get to the actual Specific objects, so I need to somehow
# get the type of the underlying Specific object and get it
for item in items:
    spec = getattr(item, item.get_my_specific_type())

esto resulta en una tonelada de db hits (uno para cada registro Genérico, que se relaciona con un Contenedor), por lo que obviamente esta no es la forma de hacerlo. Ahora, tal vez, podría hacerse obteniendo los objetos SpecificX directamente:

s = Specific1.objects.filter(cont__name='somename')
# This gets me all Specific1 objects for the specified container
...
# do it for every Specific type

de esa manera se golpeará la base de datos una vez para cada tipo específico (aceptable, supongo).

Lo sé, que .select_related () no funciona con relaciones m2m, por lo que no es de mucha ayuda aquí.

Para reiterar, el resultado final debe ser una colección de objetos SpecificX (no genéricos).

¿Fue útil?

Solución

Creo que ya has delineado las dos posibilidades fáciles. O bien realiza una consulta de filtro único contra Genérico y luego convierte cada elemento en su subtipo específico (da como resultado n + 1 consultas, donde n es el número de elementos devueltos), o realiza una consulta separada para cada tabla específica (resultados en k consultas, donde k es el número de tipos específicos).

Realmente vale la pena comparar para ver cuál de estos es más rápido en realidad. El segundo parece mejor porque es (probablemente) menos consultas, pero cada una de esas consultas tiene que realizar una unión con la tabla intermedia m2m. En el primer caso, solo realiza una consulta de unión y luego muchas más simples. Algunos backends de la base de datos funcionan mejor con muchas consultas pequeñas que pocas y más complejas.

Si el segundo es en realidad significativamente más rápido para su caso de uso, y está dispuesto a hacer un trabajo adicional para limpiar su código, debería ser posible escribir un método de administrador personalizado para el modelo genérico que '' recupera " todos los datos de subtipo de las tablas específicas relevantes para un conjunto de consultas dado, usando solo una consulta por tabla de subtipo; similar a cómo este fragmento optimiza las claves externas genéricas con una captación previa masiva. Esto le daría las mismas consultas que su segunda opción, con la sintaxis DRYer de su primera opción.

Otros consejos

No es una respuesta completa, pero puede evitar una gran cantidad de visitas haciendo esto

items= list(items)
for item in items:
    spec = getattr(item, item.get_my_specific_type())

en lugar de esto:

for item in items:
    spec = getattr(item, item.get_my_specific_type())

De hecho, al forzar una conversión a una lista de Python, obliga al django orm a cargar todos los elementos en su conjunto de consultas. Luego hace esto en una consulta.

Tropecé accidentalmente con la siguiente publicación, que prácticamente responde a tu pregunta:

http://lazypython.blogspot.com/2008 /11/timeline-view-in-django.html

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top