Tengo un caso de uso en el que tengo que contar las ocurrencias de ManyToManyField, pero se está volviendo más complejo de lo que pensaba.
modelos.py :
class Tag(models.Model): name = models.CharField(max_length=100, unique=True) class People(models.Model): tag = models.ManyToManyField(Tag, blank=True) Aquí tengo que crear una lista de Tags y la cantidad de veces que aparecen en general, pero solo para aquellas People que tienen> 0 y <6 etiquetas. Algo como:
tag1 - 265338 tag2 - 4649303 tag3 - 36636 ...Así es como se me ocurrió el conteo inicialmente:
q = People.objects.annotate(tag_count=Count('tag')).filter(tag_count__lte=6, tag_count__gt=0) for tag in Tag.objects.all(): cnt = q.filter(tag__name=tag.name).count() # doing something with the cnt Pero luego me di cuenta de que esto puede ser ineficiente ya que probablemente estoy iterando a través del modelo People muchas veces (los registros en People son mucho más grandes que los de Tag).
Intuitivamente, creo que debería poder hacer una iteración del modelo Tag sin ninguna iteración del modelo People . Entonces se me ocurrió esto:
for tag in Tag.objects.all(): cnt = tag.people_set.annotate(tag_count=Count('tag')).filter(tag_count__lte=6).count() # doing something with the cntPero, en primer lugar, esto no está dando los resultados esperados. En segundo lugar, estoy pensando que esto se ha vuelto más complejo de lo que parecía ser, así que quizás esté complicando algo simple. Todos los oídos a cualquier consejo.
Actualización: obtuve queryset.query y ejecuté la consulta en la base de datos para depurarla. Por alguna razón, la columna tag_count en la combinación resultante muestra todos los 1. Parece que no puedo entender por qué.
Se puede hacer usando la consulta de campo inversa ManyToMany.
También reduciría la sobrecarga y cambiaría la mayor parte de la sobrecarga de python al servidor de la base de datos.
from some_app.models import Tag, People from django.db.models import F, Value, Count, CharField from django.db.models.functions import Concat # queryset: people with tags >0 and <6, ie 1 to 5 tags people_qualified = People.objects.annotate(tag_count=Count('tag'))\ .filter(tag_count__range=(1, 5)) # query tags used with above category of people, with count tag_usage = Tag.objects.filter(people__in=people_qualified)\ .annotate(tag=F('name'), count=Count('people'))\ .values('tag', 'count') # Result: <QuerySet [{'count': 3, 'tag': u'hello'}, {'count': 2, 'tag': u'world'}]> # similarily, if needed the string output tag_usage_list = Tag.objects.filter(people__in=people_qualified)\ .annotate(tags=Concat(F('name'), Value(' - '), Count('people'), output_field=CharField()))\ .values_list('tags', flat=True) # Result: <QuerySet [u'hello - 3', u'world - 2']>