Search code examples
pythondjangodjango-querysetdjango-taggit

Django taggit aggregation query to return count per tag


I am using Django 3.2 and django-taggit 1.4

I have a model like this:

class Meal(models.Model):
    # some fields
    is_cooked = models.Boolean()
    tags = TaggitManager()

I am trying to generate a dictionary keyed by tag name and value of the number of times the tag appears - but only for meals that have the is_cooked flag set to True.

I have tried the following:

pks = list(Meal.objects.filter(is_cooked=True))
ct = ContentType.objects.get_for_model(Meal)
b=TaggedItem.objects.filter(content_type=ct, object_id__in=pks).annotate(num_times=Count('tag__name'))

This is not returning the expected results (and I expect that it might be hitting the DB several times because of so many lookups - but I don't want to get distracted by premature optimisation concerns.

What am I doing wrong, and how do I get the count of Tags per tag?

[[Edit]]

Here are the results from running the query above:

<QuerySet [<TaggedItem: Meal One tagged with rice>, <TaggedItem: Meal Two tagged with chips>, <TaggedItem: Meal Three tagged with curry>, <TaggedItem: Meal Four tagged with royalty>, <TaggedItem: Meal Five tagged with rice>, <TaggedItem: Meal Six tagged with chips>]>


>>> b[0].num_times
1

Solution

  • You can filter the annotation with:

    from django.db.models import Count, Q
    from taggit.models import Tag
    
    Tag.objects.annotate(
        nmeal=Count('meal', filter=Q(meal__is_cooked=True))
    )

    The Tag objects that arise from thsi queryset will have an extra attribute .nmeals that contains the number of cooked meals.

    If you only want to retrieve Tag objects where there is at least one related cooked meal, you can work with:

    from django.db.models import Count, Q
    from taggit.models import Tag
    
    Tag.objects.filter(
        meal__is_cooked=True
    ).annotate(
        nmeal=Count('meal')
    )