From a2418176fd22f1e4af0377c70c5b2d8955b71afe Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Fri, 30 Nov 2007 02:29:25 +0000 Subject: [PATCH] queryset-refactor: Interpret qs.filter(foo=None) to be the same as qs.filter(foo__isnull=True). Refs #2737. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6760 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 15 ++++++++------- docs/db-api.txt | 15 ++++++--------- tests/modeltests/many_to_one_null/models.py | 5 +++++ tests/regressiontests/null_queries/models.py | 7 ++++++- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c1013ce99f..703d7a18b7 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -679,13 +679,13 @@ class Query(object): else: lookup_type = parts.pop() - # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject all + # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all # uses of None as a query value. - # FIXME: Weren't we going to change this so that '__exact=None' was the - # same as '__isnull=True'? Need to check the conclusion of the mailing - # list thread. - if value is None and lookup_type != 'exact': - raise ValueError("Cannot use None as a query value") + if value is None: + if lookup_type != 'exact': + raise ValueError("Cannot use None as a query value") + lookup_type = 'isnull' + value = True elif callable(value): value = value() @@ -708,7 +708,8 @@ class Query(object): alias = join[LHS_ALIAS] col = join[LHS_JOIN_COL] - if (lookup_type == 'isnull' and value is True): + if lookup_type == 'isnull' and value is True and (len(join_list) > 1 or + len(join_list[0]) > 1): # If the comparison is against NULL, we need to use a left outer # join when connecting to the previous model. We make that # adjustment here. We don't do this unless needed because it's less diff --git a/docs/db-api.txt b/docs/db-api.txt index 9390cb0905..94ff7c1583 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -1137,7 +1137,12 @@ Examples:: SQL equivalents:: SELECT ... WHERE id = 14; - SELECT ... WHERE id = NULL; + SELECT ... WHERE id IS NULL; + +**New in Django development version:** The semantics of ``id__exact=None`` have +changed in the development version. Previously, it was (intentionally) +converted to ``WHERE id = NULL`` at the SQL level, which would never match +anything. It has now been changed to behave the same as ``id__isnull=True``. iexact ~~~~~~ @@ -1367,14 +1372,6 @@ SQL equivalent:: SELECT ... WHERE pub_date IS NULL; -.. admonition:: ``__isnull=True`` vs ``__exact=None`` - - There is an important difference between ``__isnull=True`` and - ``__exact=None``. ``__exact=None`` will *always* return an empty result - set, because SQL requires that no value is equal to ``NULL``. - ``__isnull`` determines if the field is currently holding the value - of ``NULL`` without performing a comparison. - search ~~~~~~ diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index 60c5888371..cee0e21a72 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -80,6 +80,11 @@ None >>> Article.objects.filter(reporter__isnull=True) [] +# We can achieve the same thing by filtering for the case where the reporter is +# None. +>>> Article.objects.filter(reporter=None) +[] + # Set the reporter for the Third article >>> r.article_set.add(a3) >>> r.article_set.all() diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index fc8215584e..739b75d96d 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -24,10 +24,15 @@ __test__ = {'API_TESTS':""" >>> c2 = Choice(poll=p1, choice='Why Not?') >>> c2.save() -# Exact query with value None returns nothing (=NULL in sql) +# Exact query with value None returns nothing ("is NULL" in sql, but every 'id' +# field has a value). >>> Choice.objects.filter(id__exact=None) [] +Excluding the previous result returns everything. +>>> Choice.objects.exclude(id=None).order_by('id') +[, ] + # Valid query, but fails because foo isn't a keyword >>> Choice.objects.filter(foo__exact=None) Traceback (most recent call last):