注释过滤——只求和一些相关对象的字段

Annotate filtering -- sum only some of related objects' fields

假设有一个作者,他有书。为了获取作者和已写页数,可以执行以下操作:

Author.objects.annotate(total_pages=Sum('book__pages'))

但是,如果我想分别对科幻小说和奇幻书籍的页面求和怎么办?我想以作者结尾,它具有 total_pages_books_scifi_pages 和 total_pages_books_fantasy_pages 属性。

我知道我可以做到以下几点:

Author.objects.filter(book__category='scifi').annotate(total_pages_books_scifi_pages=Sum('book__pages'))
Author.objects.filter(book__category='fantasy').annotate(total_pages_books_fantasy_pages=Sum('book__pages'))

但是如何在一个查询集中做到这一点?

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

categories = ['scifi', 'fantasy']
annotations = {}

for category in categories:
    annotation_name = 'total_pages_books_{}'.format(category)
    case = Case(
        When(book__category=category, then=F('book__pages')),
        default=0,
        output_field=IntegerField()
    )
    annotations[annotation_name] = Sum(case)

Author.objects.filter(
    book__category__in=categories
).annotate(
    **annotations
)

尝试:

Author.objects.values("book__category").annotate(total_pages=Sum('book__pages'))

来自 Django 文档: https://docs.djangoproject.com/en/1.10/topics/db/aggregation/#values:

values()

Ordinarily, annotations are generated on a per-object basis - an annotated QuerySet will return one result for each object in the original QuerySet. However, when a values() clause is used to constrain the columns that are returned in the result set, the method for evaluating annotations is slightly different. Instead of returning an annotated result for each result in the original QuerySet, the original results are grouped according to the unique combinations of the fields specified in the values() clause. An annotation is then provided for each unique group; the annotation is computed over all members of the group.