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:
parent
c085697858
commit
d20996b58d
@ -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()
|
||||
|
@ -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``
|
||||
|
@ -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>]
|
||||
|
Loading…
x
Reference in New Issue
Block a user