Fixed #31880 -- Made QuerySet.aggregate() raise FieldError when aggregating over aggregation aliases.

This commit is contained in:
David Wobrock 2020-09-27 15:47:27 +02:00 committed by Mariusz Felisiak
parent 01a7af09b9
commit 058747b77a
2 changed files with 20 additions and 2 deletions

View File

@ -17,7 +17,7 @@ from django.db import (
from django.db.models import AutoField, DateField, DateTimeField, sql from django.db.models import AutoField, DateField, DateTimeField, sql
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector from django.db.models.deletion import Collector
from django.db.models.expressions import Case, Expression, F, Value, When from django.db.models.expressions import Case, Expression, F, Ref, Value, When
from django.db.models.functions import Cast, Trunc from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, Q from django.db.models.query_utils import FilteredRelation, Q
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
@ -386,8 +386,16 @@ class QuerySet:
query = self.query.chain() query = self.query.chain()
for (alias, aggregate_expr) in kwargs.items(): for (alias, aggregate_expr) in kwargs.items():
query.add_annotation(aggregate_expr, alias, is_summary=True) query.add_annotation(aggregate_expr, alias, is_summary=True)
if not query.annotations[alias].contains_aggregate: annotation = query.annotations[alias]
if not annotation.contains_aggregate:
raise TypeError("%s is not an aggregate expression" % alias) raise TypeError("%s is not an aggregate expression" % alias)
for expr in annotation.get_source_expressions():
if expr.contains_aggregate and isinstance(expr, Ref) and expr.refs in kwargs:
name = expr.refs
raise exceptions.FieldError(
"Cannot compute %s('%s'): '%s' is an aggregate"
% (annotation.name, name, name)
)
return query.get_aggregation(self.db, kwargs) return query.get_aggregation(self.db, kwargs)
def count(self): def count(self):

View File

@ -1004,6 +1004,16 @@ class AggregateTestCase(TestCase):
self.assertEqual(author.sum_age, other_author.sum_age) self.assertEqual(author.sum_age, other_author.sum_age)
def test_aggregate_over_aggregate(self):
msg = "Cannot compute Avg('age'): 'age' is an aggregate"
with self.assertRaisesMessage(FieldError, msg):
Author.objects.annotate(
age_alias=F('age'),
).aggregate(
age=Sum(F('age')),
avg_age=Avg(F('age')),
)
def test_annotated_aggregate_over_annotated_aggregate(self): def test_annotated_aggregate_over_annotated_aggregate(self):
with self.assertRaisesMessage(FieldError, "Cannot compute Sum('id__max'): 'id__max' is an aggregate"): with self.assertRaisesMessage(FieldError, "Cannot compute Sum('id__max'): 'id__max' is an aggregate"):
Book.objects.annotate(Max('id')).annotate(Sum('id__max')) Book.objects.annotate(Max('id')).annotate(Sum('id__max'))