如何在反向外键字段上过滤 Django 注释

How to filter Django annotations on reverse foreign key fields

我正在尝试计算具有特定字段值的所有相关模型。

这是一些代码...

models.py:

class Author(models.Model):
  name = models.CharField(max_length=100)

class Book(models.Model):
  BAD = "BAD"
  MEH = "MEH"
  GOOD = "GOOD"
  GREAT = "GREAT"
  REVIEW_CHOICES = (
    (BAD, BAD.title()),
    (MEH, MEH.title()),
    (GOOD, GOOD.title()),
    (GREAT, GREAT.title()),
  )
  title = models.CharField(max_length=100)
  review = models.CharField(max_length=100, choices=REVIEW_CHOICES)
  author = models.ForeignKey(Author, related_name="books")

假设我想列出每个作者的每种评论的数量。

我试过:

Authors.object.annotate(n_good_books=Count("books")).filter(books__review="GOOD").values("name", "n_good_books")  

我也试过:

Authors.object.annotate(n_good_books=Count("books", filter=Q(books_review="GOOD"))).values("name", "n_good_books")  

但这些都不起作用。

有什么建议吗?

您需要 .filter(..) 之前 .annotate(..),所以:

Authors.object.filter(
    <b>books__review="GOOD"</b>  # before the annotate
).annotate(
    n_good_books=Count("books")
)

这将导致 QuerySetAuthor 秒,其中每个 Author 都有一个额外的属性 .n_good_books,其中包含商品的数量 Book秒。相反意味着您 将检索 Author 至少 一个相关的 Book 有一个很好的评论. specified in the documentation:

When used with an annotate() clause, a filter has the effect of constraining the objects for which an annotation is calculated. For example, you can generate an annotated list of all books that have a title starting with "Django" using the query:

>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

(..)

Annotated values can also be filtered. The alias for the annotation can be used in filter() and exclude() clauses in the same way as any other model field.

For example, to generate a list of books that have more than one author, you can issue the query:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

This query generates an annotated result set, and then generates a filter based upon that annotation.

Count(..., filter=Q(..)) 方法只有效,因为 , so in 不会 有效。

@willem-van-onsem 对我提出的问题有正确的答案。

但是,如果我想一次获得所有书籍类型的计数,我可以这样做:

from django.db.models import Case, When, IntegerField

Authors.object.annotate(
  n_bad_books=Count(Case(When(books__review="BAD", then=1), output_field=IntegerField())),
  n_meh_books=Count(Case(When(books__review="MEH", then=1), output_field=IntegerField())),
  n_good_books=Count(Case(When(books__review="GOOD", then=1), output_field=IntegerField())),
  n_great_books=Count(Case(When(books__review="GREAT", then=1), output_field=IntegerField())),
)

他是对的,这很不雅。