114

How can I add a field for the count of objects in a database. I have the following models:

class Item(models.Model):
    name = models.CharField()

class Contest(models.Model);
    name = models.CharField()

class Votes(models.Model):
    user = models.ForeignKey(User)
    item = models.ForeignKey(Item)
    contest = models.ForeignKey(Contest)
    comment = models.TextField()

To find the votes for contestA I am using the following query in my view

current_vote = Item.objects.filter(votes__contest=contestA)

This returns a queryset with all of the votes individually but I want to get the count votes for each item, anyone know how I can do that? thanks

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
thesteve
  • 2,413
  • 6
  • 26
  • 28

4 Answers4

192

To get the number of votes for a specific item, you would use:

vote_count = Item.objects.filter(votes__contest=contestA).count()

If you wanted a break down of the distribution of votes in a particular contest, I would do something like the following:

contest = Contest.objects.get(pk=contest_id)
votes   = contest.votes_set.select_related()

vote_counts = {}

for vote in votes:
  if not vote_counts.has_key(vote.item.id):
    vote_counts[vote.item.id] = {
      'item': vote.item,
      'count': 0
    }

  vote_counts[vote.item.id]['count'] += 1

This will create dictionary that maps items to number of votes. Not the only way to do this, but it's pretty light on database hits, so will run pretty quickly.

Gary Chambers
  • 24,930
  • 4
  • 35
  • 31
  • 1
    for anyone interested - `has_key` was removed in Python 3: You can update it to use the `in` operator instead. **Example:**. `if vote.item.id not in vote_counts: vote_counts[vote.item.id] = { 'item': vote.item, 'count': 0 }` – RealScatman May 06 '22 at 15:43
25

Another way of doing this would be using Aggregation. You should be able to achieve a similar result using a single query. Such as this:

from django.db.models import Count

Item.objects.values("contest").annotate(Count("id"))

I did not test this specific query, but this should output a count of the items for each value in contests as a dictionary.

Flimm
  • 136,138
  • 45
  • 251
  • 267
salomonvh
  • 1,739
  • 1
  • 15
  • 15
  • 1
    This is still confusing to me. If you annotate on a queryset, you're counting the number of id's belonging to each object, which is obviously 1. So how is that helpful? – AlxVallejo Jun 14 '18 at 17:08
  • Yes, if you were counting `contest` on the Contest model that would be the case. However, in this case we are aggregating on `Item` which have unique contests. – salomonvh Jun 18 '18 at 08:25
  • What if the count for two objects are same and then we have to sort them based on some other condition. Then how to do that? – Tarun Sep 06 '18 at 20:24
  • @Tarun you can add values in the annotate, just make sure you do not try to add items that get aggregated during the annotation. Since it returns a queryset all the usual sort functions still apply `.order_by('some_value')`. – salomonvh Sep 07 '18 at 17:16
2

Use related name to count votes for a specific contest

class Item(models.Model):
    name = models.CharField()

class Contest(models.Model);
    name = models.CharField()

class Votes(models.Model):
    user = models.ForeignKey(User)
    item = models.ForeignKey(Item)
    contest = models.ForeignKey(Contest, related_name="contest_votes")
    comment = models.TextField()

>>> comments = Contest.objects.get(id=contest_id).contest_votes.count()
sifar96
  • 21
  • 2
0

You can use len() to get the count of contestA's votes:

current_vote = len(Item.objects.filter(votes__contest=contestA))

Actually, len() is used with select_for_update() as shown below:

current_vote = len(Item.objects.select_for_update().filter(votes__contest=contestA))

Because select_for_update() doesn't work with count() as shown below:

current_vote = Item.objects.select_for_update().filter(votes__contest=contestA).count()

You can see my answer explaning about select_for_update() with count() or len().

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129