mirror of
https://github.com/django/django.git
synced 2025-07-06 10:49:17 +00:00
queryset-refactor: Implemented a way to differentiate between filtering on a
single instance and filtering on multiple instances when spanning a multi-valued relationship. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7317 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
c085697858
commit
d20996b58d
@ -809,7 +809,7 @@ class Query(object):
|
|||||||
used, next, restricted)
|
used, next, restricted)
|
||||||
|
|
||||||
def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
|
def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
|
||||||
merge_negated=False):
|
single_filter=False):
|
||||||
"""
|
"""
|
||||||
Add a single filter to the query. The 'filter_expr' is a pair:
|
Add a single filter to the query. The 'filter_expr' is a pair:
|
||||||
(filter_string, value). E.g. ('name__contains', 'fred')
|
(filter_string, value). E.g. ('name__contains', 'fred')
|
||||||
@ -818,9 +818,8 @@ class Query(object):
|
|||||||
automatically trim the final join group (used internally when
|
automatically trim the final join group (used internally when
|
||||||
constructing nested queries).
|
constructing nested queries).
|
||||||
|
|
||||||
If 'merge_negated' is True, this negated filter will be merged with the
|
If 'single_filter' is True, we are processing a component of a
|
||||||
existing negated where node (if it exists). This is used when
|
multi-component filter (e.g. filter(Q1, Q2)).
|
||||||
constructing an exclude filter from combined subfilters.
|
|
||||||
"""
|
"""
|
||||||
arg, value = filter_expr
|
arg, value = filter_expr
|
||||||
parts = arg.split(LOOKUP_SEP)
|
parts = arg.split(LOOKUP_SEP)
|
||||||
@ -849,7 +848,7 @@ class Query(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
field, target, opts, join_list, last = self.setup_joins(parts, opts,
|
field, target, opts, join_list, last = self.setup_joins(parts, opts,
|
||||||
alias, (connector == AND), allow_many)
|
alias, (connector == AND) and not single_filter, allow_many)
|
||||||
except MultiJoin, e:
|
except MultiJoin, e:
|
||||||
self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
|
self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
|
||||||
return
|
return
|
||||||
@ -917,7 +916,7 @@ class Query(object):
|
|||||||
self.promote_alias(table)
|
self.promote_alias(table)
|
||||||
|
|
||||||
entry = (alias, col, field, lookup_type, value)
|
entry = (alias, col, field, lookup_type, value)
|
||||||
if merge_negated:
|
if negate and single_filter:
|
||||||
# This case is when we're doing the Q2 filter in exclude(Q1, Q2).
|
# This case is when we're doing the Q2 filter in exclude(Q1, Q2).
|
||||||
# It's different from exclude(Q1).exclude(Q2).
|
# It's different from exclude(Q1).exclude(Q2).
|
||||||
for node in self.where.children:
|
for node in self.where.children:
|
||||||
@ -957,7 +956,7 @@ class Query(object):
|
|||||||
else:
|
else:
|
||||||
subtree = False
|
subtree = False
|
||||||
connector = AND
|
connector = AND
|
||||||
merge = False
|
internal = False
|
||||||
for child in q_object.children:
|
for child in q_object.children:
|
||||||
if isinstance(child, Node):
|
if isinstance(child, Node):
|
||||||
self.where.start_subtree(connector)
|
self.where.start_subtree(connector)
|
||||||
@ -965,8 +964,8 @@ class Query(object):
|
|||||||
self.where.end_subtree()
|
self.where.end_subtree()
|
||||||
else:
|
else:
|
||||||
self.add_filter(child, connector, q_object.negated,
|
self.add_filter(child, connector, q_object.negated,
|
||||||
merge_negated=merge)
|
single_filter=internal)
|
||||||
merge = q_object.negated
|
internal = True
|
||||||
connector = q_object.connector
|
connector = q_object.connector
|
||||||
if subtree:
|
if subtree:
|
||||||
self.where.end_subtree()
|
self.where.end_subtree()
|
||||||
|
@ -1559,7 +1559,7 @@ equivalent::
|
|||||||
model's primary key in queries.
|
model's primary key in queries.
|
||||||
|
|
||||||
Lookups that span relationships
|
Lookups that span relationships
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
-------------------------------
|
||||||
|
|
||||||
Django offers a powerful and intuitive way to "follow" relationships in
|
Django offers a powerful and intuitive way to "follow" relationships in
|
||||||
lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the
|
lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the
|
||||||
@ -1582,8 +1582,66 @@ whose ``headline`` contains ``'Lennon'``::
|
|||||||
|
|
||||||
Blog.objects.filter(entry__headline__contains='Lennon')
|
Blog.objects.filter(entry__headline__contains='Lennon')
|
||||||
|
|
||||||
|
Spanning multi-valued relationships
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This is an experimental API and subject to change prior to
|
||||||
|
queryset-refactor being merged into trunk.
|
||||||
|
|
||||||
|
When you are filtering an object based on a ``ManyToManyField`` or a reverse
|
||||||
|
``ForeignKeyField``, there are two different sorts of filter you may be
|
||||||
|
interested in. Consider the ``Blog``/``Entry`` relationship (``Blog`` to
|
||||||
|
``Entry`` is a one-to-many relation). We might be interested in finding blogs
|
||||||
|
that have an entry which has both *"Lennon"* in the headline and was published
|
||||||
|
today. Or we might want to find blogs that have an entry with *"Lennon"* in
|
||||||
|
the headline as well as an entry that was published today. Since there are
|
||||||
|
multiple entries associated with a single ``Blog``, both of these queries are
|
||||||
|
possible and make sense in some situations.
|
||||||
|
|
||||||
|
The same type of situation arises with a ``ManyToManyField``. For example, if
|
||||||
|
an ``Entry`` has a ``ManyToManyField`` called ``tags``, we might want to find
|
||||||
|
entries linked to tags called *"music"* and *"bands"* or we might want an
|
||||||
|
entry that contains a tag with a name of *"music"* and a status of *"public"*.
|
||||||
|
|
||||||
|
To handle both of these situations, Django has a consistent way of processing
|
||||||
|
``filter()`` and ``exclude()`` calls. Everything inside a single ``filter()``
|
||||||
|
call is applied simultaneously to filter out items matching all those
|
||||||
|
requirements. Successive ``filter()`` calls further restrict the set of
|
||||||
|
objects, but for multi-valued relations, they apply to any object linked to
|
||||||
|
the primary model, not necessarily those objects that were selected by an
|
||||||
|
earlier ``filter()`` call.
|
||||||
|
|
||||||
|
That may sound a bit confusing, so hopefully an example will clarify. To
|
||||||
|
select all blogs that contains entries with *"Lennon"* in the headline and
|
||||||
|
were published today, we would write::
|
||||||
|
|
||||||
|
Blog.objects.filter(entry__headline__contains='Lennon',
|
||||||
|
entry__pub_date=datetime.date.today())
|
||||||
|
|
||||||
|
To select all blogs that contain an entry with *"Lennon"* in the headline
|
||||||
|
**as well as** an entry that was published today, we would write::
|
||||||
|
|
||||||
|
Blog.objects.filter(entry__headline__contains='Lennon').filter(
|
||||||
|
entry__pub_date=datetime.date.today())
|
||||||
|
|
||||||
|
In this second example, the first filter restricted the queryset to all those
|
||||||
|
blogs linked to that particular type of entry. The second filter restricted
|
||||||
|
the set of blogs *further* to those that are also linked to the second type of
|
||||||
|
entry. The entries select by the second filter may or may not be the same as
|
||||||
|
the entries in the first filter. We are filtering the ``Blog`` items with each
|
||||||
|
filter statement, not the ``Entry`` items.
|
||||||
|
|
||||||
|
All of this behaviour also applies to ``exclude()``: all the conditions in a
|
||||||
|
single ``exclude()`` statement apply to a single instance (if those conditions
|
||||||
|
are talking about the same multi-valued relation). Conditions in subsequent
|
||||||
|
``filter()`` or ``exclude()`` calls that refer to the same relation may end up
|
||||||
|
filtering on different linked objects.
|
||||||
|
|
||||||
Escaping percent signs and underscores in LIKE statements
|
Escaping percent signs and underscores in LIKE statements
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
---------------------------------------------------------
|
||||||
|
|
||||||
The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
|
The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
|
||||||
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
|
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
|
||||||
|
@ -210,11 +210,24 @@ True
|
|||||||
|
|
||||||
>>> Item.objects.filter(Q(tags=t1)).order_by('name')
|
>>> Item.objects.filter(Q(tags=t1)).order_by('name')
|
||||||
[<Item: one>, <Item: two>]
|
[<Item: one>, <Item: two>]
|
||||||
>>> Item.objects.filter(Q(tags=t1) & Q(tags=t2))
|
|
||||||
[<Item: one>]
|
|
||||||
>>> Item.objects.filter(Q(tags=t1)).filter(Q(tags=t2))
|
>>> Item.objects.filter(Q(tags=t1)).filter(Q(tags=t2))
|
||||||
[<Item: one>]
|
[<Item: one>]
|
||||||
|
|
||||||
|
Each filter call is processed "at once" against a single table, so this is
|
||||||
|
different from the previous example as it tries to find tags that are two
|
||||||
|
things at once (rather than two tags).
|
||||||
|
>>> Item.objects.filter(Q(tags=t1) & Q(tags=t2))
|
||||||
|
[]
|
||||||
|
|
||||||
|
>>> qs = Author.objects.filter(ranking__rank=2, ranking__id=rank1.id)
|
||||||
|
>>> list(qs)
|
||||||
|
[<Author: a2>]
|
||||||
|
>>> qs.query.count_active_tables()
|
||||||
|
2
|
||||||
|
>>> qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=rank1.id)
|
||||||
|
>>> qs.query.count_active_tables()
|
||||||
|
3
|
||||||
|
|
||||||
Bug #4464
|
Bug #4464
|
||||||
>>> Item.objects.filter(tags=t1).filter(tags=t2)
|
>>> Item.objects.filter(tags=t1).filter(tags=t2)
|
||||||
[<Item: one>]
|
[<Item: one>]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user