python - template - select_related django example
Django filter queryset__in para*cada*elemento en la lista (4)
Digamos que tengo los siguientes modelos
class Photo(models.Model):
tags = models.ManyToManyField(Tag)
class Tag(models.Model):
name = models.CharField(max_length=50)
En una vista, tengo una lista con filtros activos llamados categorías . Quiero filtrar objetos de fotos que tienen todas las etiquetas presentes en categorías .
Lo intenté:
Photo.objects.filter(tags__name__in=categories)
Pero esto coincide con cualquier elemento en categorías, no en todos los artículos.
Entonces, si las categorías fueran [''vacaciones'', ''verano''], quiero fotos con etiquetas de vacaciones y de verano.
¿Se puede lograr esto?
Esto también se puede hacer mediante generación dinámica de consultas utilizando Django ORM y algo de magia de Python :)
from operator import and_
from django.db.models import Q
categories = [''holiday'', ''summer'']
res = Photo.filter(reduce(and_, [Q(tags__name=c) for c in categories]))
La idea es generar objetos Q apropiados para cada categoría y luego combinarlos usando el operador AND en un QuerySet. Por ejemplo, para su ejemplo, sería igual a
res = Photo.filter(Q(tags__name=''holiday'') & Q(tags__name=''summer''))
Otro enfoque que funciona, aunque solo PostgreSQL, usa django.contrib.postgres.fields.ArrayField
:
Ejemplo copiado de docs :
>>> Post.objects.create(name=''First post'', tags=[''thoughts'', ''django''])
>>> Post.objects.create(name=''Second post'', tags=[''thoughts''])
>>> Post.objects.create(name=''Third post'', tags=[''tutorial'', ''django''])
>>> Post.objects.filter(tags__contains=[''thoughts''])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contains=[''django''])
<QuerySet [<Post: First post>, <Post: Third post>]>
>>> Post.objects.filter(tags__contains=[''django'', ''thoughts''])
<QuerySet [<Post: First post>]>
ArrayField
tiene algunas características más potentes, como la overlap y las transformaciones de índice .
Si queremos hacerlo dinámicamente, seguimos el ejemplo:
tag_ids = [t1.id, t2.id]
qs = Photo.objects.all()
for tag_id in tag_ids:
qs = qs.filter(tag__id=tag_id)
print qs
Resumen:
Una opción es, como lo sugieren jpic y sgallen en los comentarios, agregar .filter()
para cada categoría. Cada filter
adicional agrega más combinaciones, lo que no debería ser un problema para un pequeño conjunto de categorías.
Existe el approach aggregation . Esta consulta sería más corta y quizás más rápida para un gran conjunto de categorías.
También tiene la opción de usar consultas personalizadas .
Algunos ejemplos
Configuración de prueba:
class Photo(models.Model):
tags = models.ManyToManyField(''Tag'')
class Tag(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
In [2]: t1 = Tag.objects.create(name=''holiday'')
In [3]: t2 = Tag.objects.create(name=''summer'')
In [4]: p = Photo.objects.create()
In [5]: p.tags.add(t1)
In [6]: p.tags.add(t2)
In [7]: p.tags.all()
Out[7]: [<Tag: holiday>, <Tag: summer>]
Usando el enfoque de docs.djangoproject.com/en/dev/topics/db/queries/… :
In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]
Consulta resultante:
In [17]: print Photo.objects.filter(tags=t1).filter(tags=t2).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_photo_tags" T4 ON ("test_photo"."id" = T4."photo_id")
WHERE ("test_photo_tags"."tag_id" = 3 AND T4."tag_id" = 4 )
Tenga en cuenta que cada filter
agrega más JOINS
a la consulta.
Usando el approach aggregation :
In [29]: from django.db.models import Count
In [30]: Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count(''tags'')).filter(num_tags=2)
Out[30]: [<Photo: Photo object>]
Consulta resultante:
In [32]: print Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count(''tags'')).filter(num_tags=2).query
SELECT "test_photo"."id", COUNT("test_photo_tags"."tag_id") AS "num_tags"
FROM "test_photo"
LEFT OUTER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
WHERE ("test_photo_tags"."tag_id" IN (3, 4))
GROUP BY "test_photo"."id", "test_photo"."id"
HAVING COUNT("test_photo_tags"."tag_id") = 2
AND
objetos ed Q
no funcionarían:
In [9]: from django.db.models import Q
In [10]: Photo.objects.filter(Q(tags__name=''holiday'') & Q(tags__name=''summer''))
Out[10]: []
In [11]: from operator import and_
In [12]: Photo.objects.filter(reduce(and_, [Q(tags__name=''holiday''), Q(tags__name=''summer'')]))
Out[12]: []
Consulta resultante:
In [25]: print Photo.objects.filter(Q(tags__name=''holiday'') & Q(tags__name=''summer'')).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_tag" ON ("test_photo_tags"."tag_id" = "test_tag"."id")
WHERE ("test_tag"."name" = holiday AND "test_tag"."name" = summer )