1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +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:
Malcolm Tredinnick 2008-03-19 11:02:22 +00:00
parent c085697858
commit d20996b58d
3 changed files with 83 additions and 13 deletions

View File

@ -809,7 +809,7 @@ class Query(object):
used, next, restricted)
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:
(filter_string, value). E.g. ('name__contains', 'fred')
@ -818,9 +818,8 @@ class Query(object):
automatically trim the final join group (used internally when
constructing nested queries).
If 'merge_negated' is True, this negated filter will be merged with the
existing negated where node (if it exists). This is used when
constructing an exclude filter from combined subfilters.
If 'single_filter' is True, we are processing a component of a
multi-component filter (e.g. filter(Q1, Q2)).
"""
arg, value = filter_expr
parts = arg.split(LOOKUP_SEP)
@ -849,7 +848,7 @@ class Query(object):
try:
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:
self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
return
@ -917,7 +916,7 @@ class Query(object):
self.promote_alias(table)
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).
# It's different from exclude(Q1).exclude(Q2).
for node in self.where.children:
@ -957,7 +956,7 @@ class Query(object):
else:
subtree = False
connector = AND
merge = False
internal = False
for child in q_object.children:
if isinstance(child, Node):
self.where.start_subtree(connector)
@ -965,8 +964,8 @@ class Query(object):
self.where.end_subtree()
else:
self.add_filter(child, connector, q_object.negated,
merge_negated=merge)
merge = q_object.negated
single_filter=internal)
internal = True
connector = q_object.connector
if subtree:
self.where.end_subtree()

View File

@ -1559,7 +1559,7 @@ equivalent::
model's primary key in queries.
Lookups that span relationships
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------
Django offers a powerful and intuitive way to "follow" relationships in
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')
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------------------------------------------------
The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``

View File

@ -210,11 +210,24 @@ True
>>> Item.objects.filter(Q(tags=t1)).order_by('name')
[<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: 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
>>> Item.objects.filter(tags=t1).filter(tags=t2)
[<Item: one>]