Django - 使用多个计数进行注释

Django - annotate with multiple Count

我有一个名为 Post 的模型,它有两个字段 upvotesdownvotes。现在,upvotesdownvotesManyToManyFieldProfile。这是型号:

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')

所以,我想获取所有帖子,以便它们按

的顺序排列

total(upvotes) - total(downvotes)

所以我使用了这个查询:

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

此查询的问题是 total_votes 总是变成

以下查询将说明情况:

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}]>

似乎updown 都具有相同的值(实际上是up 的值)。我该如何解决这个问题?

我试过这个:

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}]>

但即使这样也会产生相同的输出。

尝试使用dictinct参数:

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

来自文档:

Combining multiple aggregations with annotate() will yield the wrong results because joins are used instead of subqueries. For most aggregates, there is no way to avoid this problem, however, the Count aggregate has a distinct parameter that may help.

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

更好的数据模型是

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'),]

这样你就可以用

计算post的当前总分
Vote.objects.filter(post=post).aggregate(score=Sum('score'))

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

score = models.IntegerField(editable=False)

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