summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsharonwoo <29156885+sharonwoo@users.noreply.github.com>2024-09-27 09:17:07 +0800
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-01-30 13:51:25 +0100
commit77f9e6bcd3302eda1a2f9f9e2ce5ac8983b37951 (patch)
treeaa2593b391dc868a805751585f945d4c2008d3ce
parentd567e3a52e8b3ef0f830e07b602ae1382657eb07 (diff)
[5.2.x] Fixed #35235 -- Removed caching of BaseExpression._output_field_or_none.
Backport of cbb0812683cf3236e4a4003bf7f74b119d3cde0c from main.
-rw-r--r--django/db/models/expressions.py17
-rw-r--r--tests/expressions/tests.py11
-rw-r--r--tests/postgres_tests/test_aggregates.py16
3 files changed, 37 insertions, 7 deletions
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index ad8f8e6650..a89acaf5a9 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -167,13 +167,16 @@ class Combinable:
return NegatedExpression(self)
+class OutputFieldIsNoneError(FieldError):
+ pass
+
+
class BaseExpression:
"""Base class for all query expressions."""
empty_result_set_value = NotImplemented
# aggregate specific fields
is_summary = False
- _output_field_resolved_to_none = False
# Can the expression be used in a WHERE clause?
filterable = True
# Can the expression be used as a source expression in Window?
@@ -323,11 +326,12 @@ class BaseExpression:
"""Return the output type of this expressions."""
output_field = self._resolve_output_field()
if output_field is None:
- self._output_field_resolved_to_none = True
- raise FieldError("Cannot resolve expression type, unknown output_field")
+ raise OutputFieldIsNoneError(
+ "Cannot resolve expression type, unknown output_field"
+ )
return output_field
- @cached_property
+ @property
def _output_field_or_none(self):
"""
Return the output field of this expression, or None if
@@ -335,9 +339,8 @@ class BaseExpression:
"""
try:
return self.output_field
- except FieldError:
- if not self._output_field_resolved_to_none:
- raise
+ except OutputFieldIsNoneError:
+ return
def _resolve_output_field(self):
"""
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index af4cf01fca..d36107b358 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -51,6 +51,7 @@ from django.db.models.expressions import (
Combinable,
CombinedExpression,
NegatedExpression,
+ OutputFieldIsNoneError,
RawSQL,
Ref,
)
@@ -2329,6 +2330,16 @@ class ValueTests(TestCase):
time = Time.objects.annotate(one=Value(1, output_field=DecimalField())).first()
self.assertEqual(time.one, 1)
+ def test_output_field_is_none_error(self):
+ with self.assertRaises(OutputFieldIsNoneError):
+ Employee.objects.annotate(custom_expression=Value(None)).first()
+
+ def test_output_field_or_none_property_not_cached(self):
+ expression = Value(None, output_field=None)
+ self.assertIsNone(expression._output_field_or_none)
+ expression.output_field = BooleanField()
+ self.assertIsInstance(expression._output_field_or_none, BooleanField)
+
def test_resolve_output_field(self):
value_types = [
("str", CharField),
diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py
index 885512cfca..ddd3b6bbd0 100644
--- a/tests/postgres_tests/test_aggregates.py
+++ b/tests/postgres_tests/test_aggregates.py
@@ -334,6 +334,22 @@ class TestGeneralAggregate(PostgreSQLTestCase):
)
self.assertCountEqual(qs, [[], [5]])
+ def test_array_agg_with_empty_filter_and_default_values(self):
+ for filter_value in ([-1], []):
+ for default_value in ([], Value([])):
+ with self.subTest(filter=filter_value, default=default_value):
+ queryset = AggregateTestModel.objects.annotate(
+ test_array_agg=ArrayAgg(
+ "stattestmodel__int1",
+ filter=Q(pk__in=filter_value),
+ default=default_value,
+ )
+ )
+ self.assertSequenceEqual(
+ queryset.values_list("test_array_agg", flat=True),
+ [[], [], [], []],
+ )
+
def test_bit_and_general(self):
values = AggregateTestModel.objects.filter(integer_field__in=[0, 1]).aggregate(
bitand=BitAnd("integer_field")