update template select_related queryset query example python django filter django-queryset

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 )