diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 8fffce5dec..e3e6100f24 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1444,7 +1444,7 @@ class Query(BaseExpression): # DEFAULT_DB_ALIAS isn't nice but it's the best that can be done here. # A similar thing is done in is_nullable(), too. if ( - lookup_name == "exact" + lookup_name in ("exact", "iexact") and lookup.rhs == "" and connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls ): diff --git a/tests/null_queries/models.py b/tests/null_queries/models.py index 2ed7fef2cc..6259ab55c6 100644 --- a/tests/null_queries/models.py +++ b/tests/null_queries/models.py @@ -7,7 +7,7 @@ class Poll(models.Model): class Choice(models.Model): poll = models.ForeignKey(Poll, models.CASCADE) - choice = models.CharField(max_length=200) + choice = models.CharField(max_length=200, null=True) # A set of models with an inner one pointing to two outer ones. diff --git a/tests/null_queries/tests.py b/tests/null_queries/tests.py index 828c68d921..9c061ae323 100644 --- a/tests/null_queries/tests.py +++ b/tests/null_queries/tests.py @@ -1,5 +1,5 @@ from django.core.exceptions import FieldError -from django.test import TestCase +from django.test import TestCase, skipUnlessDBFeature from .models import Choice, Inner, OuterA, OuterB, Poll @@ -44,6 +44,18 @@ class NullQueriesTests(TestCase): with self.assertRaisesMessage(ValueError, "Cannot use None as a query value"): Choice.objects.filter(id__gt=None) + @skipUnlessDBFeature("interprets_empty_strings_as_nulls") + def test_empty_string_is_null(self): + p = Poll.objects.create(question="?") + c1 = Choice.objects.create(poll=p, choice=None) + c2 = Choice.objects.create(poll=p, choice="") + cases = [{"choice__exact": ""}, {"choice__iexact": ""}] + for lookup in cases: + with self.subTest(lookup): + self.assertSequenceEqual( + Choice.objects.filter(**lookup).order_by("id"), [c1, c2] + ) + def test_unsaved(self): poll = Poll(question="How?") msg = (