Fixed #30542 -- Fixed crash of numerical aggregations with filter.

Filters in annotations crashed when used with numerical-type
aggregations (i.e. Avg, StdDev, and Variance). This was caused as the
source expressions no not necessarily have an output_field (such as the
filter field), which lead to an AttributeError: 'WhereNode' object has
no attribute output_field.

Thanks to Chuan-Zheng Lee for the report.

Regression in c690afb873 and two following
commits.
This commit is contained in:
Étienne Beaulé 2019-06-04 04:01:16 -03:00 committed by Mariusz Felisiak
parent 1f81e2df69
commit 4b6dfe1622
3 changed files with 21 additions and 6 deletions

View File

@ -42,9 +42,9 @@ class FixDurationInputMixin:
class NumericOutputFieldMixin: class NumericOutputFieldMixin:
def _resolve_output_field(self): def _resolve_output_field(self):
source_expressions = self.get_source_expressions() source_fields = self.get_source_fields()
if any(isinstance(s.output_field, DecimalField) for s in source_expressions): if any(isinstance(s, DecimalField) for s in source_fields):
return DecimalField() return DecimalField()
if any(isinstance(s.output_field, IntegerField) for s in source_expressions): if any(isinstance(s, IntegerField) for s in source_fields):
return FloatField() return FloatField()
return super()._resolve_output_field() if source_expressions else FloatField() return super()._resolve_output_field() if source_fields else FloatField()

View File

@ -9,4 +9,6 @@ Django 2.2.3 fixes several bugs in 2.2.2.
Bugfixes Bugfixes
======== ========
* ... * Fixed a regression in Django 2.2 where :class:`~django.db.models.Avg`,
:class:`~django.db.models.StdDev`, and :class:`~django.db.models.Variance`
crash with ``filter`` argument (:ticket:`30542`).

View File

@ -1,8 +1,11 @@
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from django.db.models import Case, Count, F, OuterRef, Q, Subquery, Sum, When from django.db.models import (
Avg, Case, Count, F, OuterRef, Q, StdDev, Subquery, Sum, Variance, When,
)
from django.test import TestCase from django.test import TestCase
from django.test.utils import Approximate
from .models import Author, Book, Publisher from .models import Author, Book, Publisher
@ -40,6 +43,16 @@ class FilteredAggregateTests(TestCase):
agg = Sum('age', filter=Q(name__startswith='test')) agg = Sum('age', filter=Q(name__startswith='test'))
self.assertEqual(Author.objects.aggregate(age=agg)['age'], 200) self.assertEqual(Author.objects.aggregate(age=agg)['age'], 200)
def test_filtered_numerical_aggregates(self):
for aggregate, expected_result in (
(Avg, Approximate(66.7, 1)),
(StdDev, Approximate(24.9, 1)),
(Variance, Approximate(622.2, 1)),
):
with self.subTest(aggregate=aggregate.__name__):
agg = aggregate('age', filter=Q(name__startswith='test'))
self.assertEqual(Author.objects.aggregate(age=agg)['age'], expected_result)
def test_double_filtered_aggregates(self): def test_double_filtered_aggregates(self):
agg = Sum('age', filter=Q(Q(name='test2') & ~Q(name='test'))) agg = Sum('age', filter=Q(Q(name='test2') & ~Q(name='test')))
self.assertEqual(Author.objects.aggregate(age=agg)['age'], 60) self.assertEqual(Author.objects.aggregate(age=agg)['age'], 60)