Fixes #2737 -- Added code to allow None as a query value for __exact queries, raising an error otherwise. __exact=None is interpreted as the SQL 'value = NULL'. This fixes some minor problems with queries on unsaved objects with related object sets, and stops queries with a value of None being outright ignored (even if they reference an unknown attribute).
git-svn-id: http://code.djangoproject.com/svn/django/trunk@3902 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
73a6eb8ed0
commit
fbbbf8b9a1
|
@ -707,30 +707,35 @@ def parse_lookup(kwarg_items, opts):
|
||||||
joins, where, params = SortedDict(), [], []
|
joins, where, params = SortedDict(), [], []
|
||||||
|
|
||||||
for kwarg, value in kwarg_items:
|
for kwarg, value in kwarg_items:
|
||||||
if value is not None:
|
path = kwarg.split(LOOKUP_SEPARATOR)
|
||||||
path = kwarg.split(LOOKUP_SEPARATOR)
|
# Extract the last elements of the kwarg.
|
||||||
# Extract the last elements of the kwarg.
|
# The very-last is the lookup_type (equals, like, etc).
|
||||||
# The very-last is the lookup_type (equals, like, etc).
|
# The second-last is the table column on which the lookup_type is
|
||||||
# The second-last is the table column on which the lookup_type is
|
# to be performed. If this name is 'pk', it will be substituted with
|
||||||
# to be performed. If this name is 'pk', it will be substituted with
|
# the name of the primary key.
|
||||||
# the name of the primary key.
|
# If there is only one part, or the last part is not a query
|
||||||
# If there is only one part, or the last part is not a query
|
# term, assume that the query is an __exact
|
||||||
# term, assume that the query is an __exact
|
lookup_type = path.pop()
|
||||||
lookup_type = path.pop()
|
if lookup_type == 'pk':
|
||||||
if lookup_type == 'pk':
|
lookup_type = 'exact'
|
||||||
lookup_type = 'exact'
|
path.append(None)
|
||||||
path.append(None)
|
elif len(path) == 0 or lookup_type not in QUERY_TERMS:
|
||||||
elif len(path) == 0 or lookup_type not in QUERY_TERMS:
|
path.append(lookup_type)
|
||||||
path.append(lookup_type)
|
lookup_type = 'exact'
|
||||||
lookup_type = 'exact'
|
|
||||||
|
|
||||||
if len(path) < 1:
|
if len(path) < 1:
|
||||||
raise TypeError, "Cannot parse keyword query %r" % kwarg
|
raise TypeError, "Cannot parse keyword query %r" % kwarg
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
# Interpret '__exact=None' as the sql '= NULL'; otherwise, reject
|
||||||
|
# all uses of None as a query value.
|
||||||
|
if lookup_type != 'exact':
|
||||||
|
raise ValueError, "Cannot use None as a query value"
|
||||||
|
|
||||||
joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None)
|
joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None)
|
||||||
joins.update(joins2)
|
joins.update(joins2)
|
||||||
where.extend(where2)
|
where.extend(where2)
|
||||||
params.extend(params2)
|
params.extend(params2)
|
||||||
return joins, where, params
|
return joins, where, params
|
||||||
|
|
||||||
class FieldFound(Exception):
|
class FieldFound(Exception):
|
||||||
|
|
|
@ -876,15 +876,18 @@ The database API supports the following lookup types:
|
||||||
exact
|
exact
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
Exact match.
|
Exact match. If the value provided for comparison is ``None``, it will
|
||||||
|
be interpreted as an SQL ``NULL`` (See isnull_ for more details).
|
||||||
|
|
||||||
Example::
|
Examples::
|
||||||
|
|
||||||
Entry.objects.get(id__exact=14)
|
Entry.objects.get(id__exact=14)
|
||||||
|
Entry.objects.get(id__exact=None)
|
||||||
|
|
||||||
SQL equivalent::
|
SQL equivalents::
|
||||||
|
|
||||||
SELECT ... WHERE id = 14;
|
SELECT ... WHERE id = 14;
|
||||||
|
SELECT ... WHERE id = NULL;
|
||||||
|
|
||||||
iexact
|
iexact
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
@ -1103,8 +1106,8 @@ such as January 3, July 3, etc.
|
||||||
isnull
|
isnull
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
``NULL`` or ``IS NOT NULL`` match. Takes either ``True`` or ``False``, which
|
Takes either ``True`` or ``False``, which correspond to SQL queries of
|
||||||
correspond to ``IS NULL`` and ``IS NOT NULL``, respectively.
|
``IS NULL`` and ``IS NOT NULL``, respectively.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
@ -1114,6 +1117,14 @@ SQL equivalent::
|
||||||
|
|
||||||
SELECT ... WHERE pub_date IS NULL;
|
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
|
search
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Poll(models.Model):
|
||||||
|
question = models.CharField(maxlength=200)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Q: %s " % self.question
|
||||||
|
|
||||||
|
class Choice(models.Model):
|
||||||
|
poll = models.ForeignKey(Poll)
|
||||||
|
choice = models.CharField(maxlength=200)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Choice: %s in poll %s" % (self.choice, self.poll)
|
||||||
|
|
||||||
|
__test__ = {'API_TESTS':"""
|
||||||
|
# Regression test for the use of None as a query value. None is interpreted as
|
||||||
|
# an SQL NULL, but only in __exact queries.
|
||||||
|
# Set up some initial polls and choices
|
||||||
|
>>> p1 = Poll(question='Why?')
|
||||||
|
>>> p1.save()
|
||||||
|
>>> c1 = Choice(poll=p1, choice='Because.')
|
||||||
|
>>> c1.save()
|
||||||
|
>>> c2 = Choice(poll=p1, choice='Why Not?')
|
||||||
|
>>> c2.save()
|
||||||
|
|
||||||
|
# Exact query with value None returns nothing (=NULL in sql)
|
||||||
|
>>> Choice.objects.filter(id__exact=None)
|
||||||
|
[]
|
||||||
|
|
||||||
|
# Valid query, but fails because foo isn't a keyword
|
||||||
|
>>> Choice.objects.filter(foo__exact=None)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: Cannot resolve keyword 'foo' into field
|
||||||
|
|
||||||
|
# Can't use None on anything other than __exact
|
||||||
|
>>> Choice.objects.filter(id__gt=None)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: Cannot use None as a query value
|
||||||
|
|
||||||
|
# Can't use None on anything other than __exact
|
||||||
|
>>> Choice.objects.filter(foo__gt=None)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: Cannot use None as a query value
|
||||||
|
|
||||||
|
# Related managers use __exact=None implicitly if the object hasn't been saved.
|
||||||
|
>>> p2 = Poll(question="How?")
|
||||||
|
>>> p2.choice_set.all()
|
||||||
|
[]
|
||||||
|
|
||||||
|
"""}
|
Loading…
Reference in New Issue