简体   繁体   中英

Strange behaviour for annotate in Django

I have two database tables which are the following:

class Story(models.Model):
    user = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    title = models.CharField(max_length=150)
    description = models.CharField(blank=True, null=True, max_length=2000)
    story_text = models.TextField()

    def __unicode__(self):
        return self.title

class StoryVote(models.Model):
    votes = models.IntegerField()
    story = models.ForeignKey(Story, related_name="votes")

    def __unicode__(self):
        return self.votes

I'm trying to get a list of the 5 stories with the higest number of votes. I have read the documentation about aggregation and I used the annotate function to get a list of the 5 stories with the higest number of comments which worked nicely, but it doesnt work when I'm using it to count the votes.

This is the code im using to get a list of the 5 stories with the highest number of votes:

most_voted = Story.objects.annotate(num_votes=Count('votes')).order_by('-num_votes')[:5]

This should work according to the documentation, and I used the same way of counting the comments, but the count is wrong.

I'm using the following template code:

{% for story in most_voted %}
{{ story.id }}
{{ story.num_votes }}
{% endfor %}

I get a wierd result back which is not what I expected. I get a list of stories back but no matter how many votes the stories have the higest number displayed is 1 and if the story doesn't have any vote a 0 is displayed. If I vote on the story multiple times it still displays the number 1 on the story. I'm confused, can anyone explain why this isn't working when its working fine when getting the list of comments?

For information, the comment table where I don't have any problems looks like this:

class Comment(models.Model):
    user = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    emailOnReply = models.NullBooleanField(blank=True, null=True)
    comment_text = models.TextField()
    story = models.ForeignKey(Story, related_name="comments")

    def __unicode__(self):
        return self.comment_text

When executing this in the Python shell python manage.py shell :

>>> most_voted = Story.objects.annotate(num_votes=Count('votes')).order_by('-num_votes')
>>> for story in most_voted:
...     print story.title + " has " + str(story.num_votes) + " votes."
...
story1 has 1 votes.
story2 has 1 votes.
story3 has 1 votes.
story4 has 0 votes.

When the actual values are the following:

select * from base_storyvote ;
1|6|2
2|-3|3
3|3|4

Where the ID 1 above is story1 which is shown to have 1 vote but actually has 6 .

Your model structure is confusing. It seems like you have a single StoryVote instance for each Story, and the StoryVote's vote field gets incremented each time. That is an usual structure: you would normally either keep the vote count on the Story itself, or just add a new StoryVote instance for each vote, so there would be no need for the vote count field. In your case, if you really do want to keep the models separate for reasons of your own, you should probably be using a OneToOneField rather than a foreign key.

Anyway, what your annotation is doing is counting the number of StoryVote instances for each Story: but as you note, that is always 1. But in your case there is no reason to use annotation, because there is just a single StoryVote: you can access the value directly:

most_voted = Story.objects.prefetch_related().order_by('-storyvote__votes')
for story in most_voted:
    print story.title + " has " + str(story.votes[0].votes) + " votes."

This would be even easier if you take my advice and used a OneToOne field:

most_voted = Story.objects.select_related().order_by('-storyvote__votes')
for story in most_voted:    
    print story.title + " has " + str(story.votes.votes) + " votes."

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM