jhon arab
jhon arab

Reputation: 81

How to count queryset from ManyToManyField in django?

In models:

class Match(models.Model):
    user = models.ManyToManyField(User, blank=True)
    hot_league = models.ManyToManyField('HotLeague', blank=True)

class HotLeague(models.Model):
    user = models.ManyToManyField(User, blank=True)
    price_pool = models.IntegerField()
    winner = models.IntegerField()

In views:

match = Match.objects.filter(user=request.user)
hot_league = match.hot_league.filter(user=request.user).count()

Here in views count() is not working. How can I count the hot_league which situated in match ??

Upvotes: 1

Views: 77

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477883

The problem here is that match is not a Match object, it is a QuerySet that contains zero, one, or more Match objects. Therefore you can not use the hot_league relation on such QuerySet.

If you want to count all the HotLeagues that belong to the request.user, and have a Match that belongs to that request.user as well, you can count this as:

HotLeague.objects.filter(user=request.user, match__user=request.user).count()

This will count the same HotLeague multiple times if it belongs to multiple matches with request.user as user. If you wish to count each HotLeague only once, you can add a .distinct() [Django-doc] to it:

HotLeague.objects.filter(user=request.user, match__user=request.user).distinct().count()

Or you can annotate the number the Matches with the number of HotLeagues by that user, like:

from django.db.models import Count, Q

matches = Match.objects.filter(user=request.user).annotate(
    nhotleagues=Count('hotleague', filter=Q(hotleague__user=request.user))
)

Each Match that originates from this queryset, will have an extra attribute nhotleagues that specifies the number of HotLeagues of that user. So you can render this like:

{% for match in matches %}
    {{ match.pk }}: {{ match.notleagues }}<br>
{% endfor %}

You can sum up counts, like:

from django.db.models import Count, Q

matches = Match.objects.filter(user=request.user).annotate(
    total=Count('hotleague', distinct=True, filter=Q(hotleague__user=request.user)) +
          Count('mega_league', distinct=True, filter=Q(megaleague__user=request.user)) +
          Count('head_to_head_league', distinct=True, filter=Q(head_to_head_league__user=request.user))
)

Although of course, the more tables you add in a JOIN, the more expensive the query will be.

Upvotes: 1

Related Questions