4

I have a model called Post which has two fields upvotes and downvotes. Now, upvotes, downvotes are ManyToManyField to a Profile. This is the model:

class Post(models.Model):
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    title = models.CharField(max_length=300)
    content = models.CharField(max_length=1000)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
    upvotes = models.ManyToManyField(Profile, blank=True, related_name='upvoted_posts')
    downvotes = models.ManyToManyField(Profile, blank=True, related_name='downvoted_posts')

So, I want to fetch all the posts such that they are in the order of

total(upvotes) - total(downvotes)

So I have used this query:

Post.objects.annotate(
    total_votes=Count('upvotes')-Count('downvotes')
).order_by('total_votes')

The problem with this query is the total_votes is always turning out to be zero.

The below queries will explain the situation:

In [5]: Post.objects.annotate(up=Count('upvotes')).values('up')
Out[5]: <QuerySet [{'up': 1}, {'up': 3}, {'up': 2}]>

In [6]: Post.objects.annotate(down=Count('downvotes')).values('down')
Out[6]: <QuerySet [{'down': 1}, {'down': 1}, {'down': 1}]>

In [10]: Post.objects.annotate(up=Count('upvotes'), down=Count('downvotes'), total=Count('upvotes')-Count('downvotes')).values('up', 'down', 'total')
Out[10]: <QuerySet [{'up': 1, 'down': 1, 'total': 0}, {'up': 3, 'down': 3, 'total': 0}, {'up': 2, 'down': 2, 'total': 0}]>

Seems like both up and down are having the same value(which is actually the value of up). How can I solve this?

I have tried this:

In [9]: Post.objects.annotate(up=Count('upvotes')).annotate(down=Count('downvotes')).values('up', 'down')
Out[9]: <QuerySet [{'up': 1, 'down': 1}, {'up': 3, 'down': 3}, {'up': 2, 'down': 2}]>

but even this gives the same output.

4

2 回答 2

9

尝试使用dictinct参数:

Post.objects.annotate(
    total_votes=Count('upvotes', distinct=True)-Count('downvotes', distinct=True)
).order_by('total_votes')

从文档:

将多个聚合与 annotate() 组合会产生错误的结果,因为使用的是连接而不是子查询。对于大多数聚合,没有办法避免这个问题,但是,Count 聚合有一个不同的参数可能会有所帮助。

于 2018-08-06T05:37:14.153 回答
0

(我知道这不完全是一个答案,但代码不能嵌入到评论中。)

更好的数据模型是

class Post:
  # ...

class Vote:
  voter = models.ForeignKey(Profile, on_delete=models.PROTECT)
  post = models.ForeignKey(Post, on_delete=models.CASCADE)
  score = models.IntegerField()  # either -1 or +1; validate accordingly

  class Meta:
    unique_together = [('voter', 'post'),]

这样,您可以简单地计算帖子的当前总分

Vote.objects.filter(post=post).aggregate(score=Sum('score'))

但是,您应该清楚每次执行此操作(或您的原始版本)对性能的影响。最好加一个

score = models.IntegerField(editable=False)

的字段,Post每次创建、修改或删除投票时都会使用总得分进行更新。

于 2018-08-06T07:27:22.453 回答