我有一个名为Post
的模型,该模型具有两个字段upvotes
和downvotes
。现在,upvotes
,downvotes
是ManyToManyField
到Profile
。这是模型:
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}]>
似乎up
和down
都具有相同的值(实际上是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}]>
但是即使这样也可以提供相同的输出。
答案 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
的字段,每次创建,修改或删除投票时,该字段都会使用汇总分数更新。