From 3954bf50fb814e7362e69f1020a747e601cc73fe Mon Sep 17 00:00:00 2001
From: Artur Beltsov <artur1998g@gmail.com>
Date: Sun, 16 May 2021 15:25:35 +0500
Subject: [PATCH] Fixed #32750 -- Fixed crash of Extract() transform on
 OuterRef() expressions.

Thanks Simon Charette for the review.
---
 django/db/models/functions/datetime.py        |  4 ++-
 .../datetime/test_extract_trunc.py            | 34 +++++++++++++++++++
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/django/db/models/functions/datetime.py b/django/db/models/functions/datetime.py
index ce249b2854..7fc49897b5 100644
--- a/django/db/models/functions/datetime.py
+++ b/django/db/models/functions/datetime.py
@@ -64,7 +64,9 @@ class Extract(TimezoneMixin, Transform):
 
     def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
         copy = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
-        field = copy.lhs.output_field
+        field = getattr(copy.lhs, 'output_field', None)
+        if field is None:
+            return copy
         if not isinstance(field, (DateField, DateTimeField, TimeField, DurationField)):
             raise ValueError(
                 'Extract input expression must be DateField, DateTimeField, '
diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py
index fe80904330..e9e069c1cf 100644
--- a/tests/db_functions/datetime/test_extract_trunc.py
+++ b/tests/db_functions/datetime/test_extract_trunc.py
@@ -266,6 +266,15 @@ class DateFunctionTests(TestCase):
             with self.subTest(t):
                 self.assertIsNone(DTModel.objects.annotate(extracted=t).first().extracted)
 
+    def test_extract_outerref_validation(self):
+        inner_qs = DTModel.objects.filter(name=ExtractMonth(OuterRef('name')))
+        msg = (
+            'Extract input expression must be DateField, DateTimeField, '
+            'TimeField, or DurationField.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
+            DTModel.objects.annotate(related_name=Subquery(inner_qs.values('name')[:1]))
+
     @skipUnlessDBFeature('has_native_duration_field')
     def test_extract_duration(self):
         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
@@ -1098,6 +1107,31 @@ class DateFunctionTests(TestCase):
             ]
         )
 
+    def test_extract_outerref(self):
+        datetime_1 = datetime(2000, 1, 1)
+        datetime_2 = datetime(2001, 3, 5)
+        datetime_3 = datetime(2002, 1, 3)
+        if settings.USE_TZ:
+            datetime_1 = timezone.make_aware(datetime_1, is_dst=False)
+            datetime_2 = timezone.make_aware(datetime_2, is_dst=False)
+            datetime_3 = timezone.make_aware(datetime_3, is_dst=False)
+        obj_1 = self.create_model(datetime_1, datetime_3)
+        obj_2 = self.create_model(datetime_2, datetime_1)
+        obj_3 = self.create_model(datetime_3, datetime_2)
+
+        inner_qs = DTModel.objects.filter(
+            start_datetime__year=2000,
+            start_datetime__month=ExtractMonth(OuterRef('end_datetime')),
+        )
+        qs = DTModel.objects.annotate(
+            related_pk=Subquery(inner_qs.values('pk')[:1]),
+        )
+        self.assertSequenceEqual(qs.order_by('name').values('pk', 'related_pk'), [
+            {'pk': obj_1.pk, 'related_pk': obj_1.pk},
+            {'pk': obj_2.pk, 'related_pk': obj_1.pk},
+            {'pk': obj_3.pk, 'related_pk': None},
+        ])
+
 
 @override_settings(USE_TZ=True, TIME_ZONE='UTC')
 class DateFunctionWithTimeZoneTests(DateFunctionTests):