Django:用过滤器注释计数

Django: annotate Count with filter

我有 "post" 个对象和一个 "post like" 个对象,其中 post 收到了哪个用户的点赞数:

class Post(models.Model):
    text     = models.CharField(max_length=500, default ='')
    user     = models.ForeignKey(User)

class PostLike(models.Model):
    user    = models.ForeignKey(User)
    post    = models.ForeignKey(Post)

我可以 select 像这样 post 收到了多少赞:

Post.objects.all().annotate(likes=Count('postlike'))

这大致转化为:

SELECT p.*,
       Count(l.id) AS likes
    FROM post p, postlike l
    WHERE p.id = l.post_id
    GROUP BY (p.id)

有效。现在,我如何 filter 当前用户的 Count 聚合?我想检索的不是所有 post 的点赞,而是 登录用户 的所有点赞。结果 SQL 应该是这样的:

SELECT p.*,
    (SELECT COUNT(*) FROM postlike WHERE postlike.user_id = 1 AND postlike.post_id = p.id) AS likes
FROM post p, postlike l
WHERE p.id = l.post_id
GROUP BY (p.id)

先尝试添加过滤器:

Post.objects.filter(postlike__user=request.user).annotate(likes=Count('postlike'))

来自docs

The filter precedes the annotation, so the filter constrains the objects considered when calculating the annotation.

它不是很干净,但你可以使用 Case/When...

posts = Post.objects.all().annotate(likes=models.Count(
  models.Case(
    models.When(postlike__user_id=user.id, then=1),
    default=0,
    output_field=models.IntegerField(),
  )
))

当然,当您无法通过 Django ORM 表达某些内容时,您可以随时使用 .extra() 甚至原始 SQL。

你知道 Count 有一个 filter 论点吗?

Post.objects.annotate(
    likes=Count('postlike', filter=Q(postlike__user=logged_in_user))
)