[2.2.x] 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 inc690afb873
and two following commits. Backport of4b6dfe1622
from master.
This commit is contained in:
parent
ca3f86288a
commit
4e6f0024f1
|
@ -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()
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.db.models import Case, Count, F, Q, Sum, When
|
from django.db.models import (
|
||||||
|
Avg, Case, Count, F, Q, StdDev, 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)
|
||||||
|
|
Loading…
Reference in New Issue