diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 5db549e6bf..866e38df83 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -171,9 +171,10 @@ class Lookup(Expression): c.lhs = self.lhs.resolve_expression( query, allow_joins, reuse, summarize, for_save ) - c.rhs = self.rhs.resolve_expression( - query, allow_joins, reuse, summarize, for_save - ) + if hasattr(self.rhs, "resolve_expression"): + c.rhs = self.rhs.resolve_expression( + query, allow_joins, reuse, summarize, for_save + ) return c def select_format(self, compiler, sql, params): diff --git a/docs/releases/4.0.5.txt b/docs/releases/4.0.5.txt index 8caf03bc5d..b626f9ef36 100644 --- a/docs/releases/4.0.5.txt +++ b/docs/releases/4.0.5.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed a bug in Django 4.0 where not all :setting:`OPTIONS ` were passed to a Redis client (:ticket:`33681`). + +* Fixed a bug in Django 4.0 that caused a crash of ``QuerySet.filter()`` on + ``IsNull()`` expressions (:ticket:`33705`). diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 9f2e9d4b46..11a277d7e0 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -24,6 +24,7 @@ from django.db.models.lookups import ( Exact, GreaterThan, GreaterThanOrEqual, + IsNull, LessThan, LessThanOrEqual, ) @@ -1284,7 +1285,7 @@ class LookupQueryingTests(TestCase): @classmethod def setUpTestData(cls): cls.s1 = Season.objects.create(year=1942, gt=1942) - cls.s2 = Season.objects.create(year=1842, gt=1942) + cls.s2 = Season.objects.create(year=1842, gt=1942, nulled_text_field="text") cls.s3 = Season.objects.create(year=2042, gt=1942) def test_annotate(self): @@ -1366,6 +1367,16 @@ class LookupQueryingTests(TestCase): qs = Season.objects.filter(GreaterThan(F("year"), 1910)) self.assertCountEqual(qs, [self.s1, self.s3]) + def test_isnull_lookup_in_filter(self): + self.assertSequenceEqual( + Season.objects.filter(IsNull(F("nulled_text_field"), False)), + [self.s2], + ) + self.assertCountEqual( + Season.objects.filter(IsNull(F("nulled_text_field"), True)), + [self.s1, self.s3], + ) + def test_filter_lookup_lhs(self): qs = Season.objects.annotate(before_20=LessThan(F("year"), 2000)).filter( before_20=LessThan(F("year"), 1900),