Django-用多个计数注释

时间:2018-08-06 05:34:20

标签: django django-models django-annotate

我有一个名为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}]>

但是即使这样也可以提供相同的输出。

2 个答案:

答案 0 :(得分:3)

尝试使用dictinct参数:

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

从文档中

  

将多个聚合与anateate()组合会产生错误   结果,因为使用联接而不是子查询。对于大多数   聚合,无法避免此问题,但是,计数   聚合有一个独特的参数可能会有所帮助。

答案 1 :(得分: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的字段,每次创建,修改或删除投票时,该字段都会使用汇总分数更新。