Django

Code

Changeset 7317

Show
Ignore:
Timestamp:
03/19/08 06:02:22 (8 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Implemented a way to differentiate between filtering on a
single instance and filtering on multiple instances when spanning a
multi-valued relationship.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r7288 r7317  
    810810 
    811811    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 
    812             merge_negated=False): 
     812            single_filter=False): 
    813813        """ 
    814814        Add a single filter to the query. The 'filter_expr' is a pair: 
     
    819819        constructing nested queries). 
    820820 
    821         If 'merge_negated' is True, this negated filter will be merged with the 
    822         existing negated where node (if it exists). This is used when 
    823         constructing an exclude filter from combined subfilters. 
     821        If 'single_filter' is True, we are processing a component of a 
     822        multi-component filter (e.g. filter(Q1, Q2)). 
    824823        """ 
    825824        arg, value = filter_expr 
     
    850849        try: 
    851850            field, target, opts, join_list, last = self.setup_joins(parts, opts, 
    852                     alias, (connector == AND), allow_many) 
     851                    alias, (connector == AND) and not single_filter, allow_many) 
    853852        except MultiJoin, e: 
    854853            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level])) 
     
    918917 
    919918        entry = (alias, col, field, lookup_type, value) 
    920         if merge_negated
     919        if negate and single_filter
    921920            # This case is when we're doing the Q2 filter in exclude(Q1, Q2). 
    922921            # It's different from exclude(Q1).exclude(Q2). 
     
    958957            subtree = False 
    959958        connector = AND 
    960         merge = False 
     959        internal = False 
    961960        for child in q_object.children: 
    962961            if isinstance(child, Node): 
     
    966965            else: 
    967966                self.add_filter(child, connector, q_object.negated, 
    968                         merge_negated=merge
    969                 merge = q_object.negated 
     967                        single_filter=internal
     968                internal = True 
    970969            connector = q_object.connector 
    971970        if subtree: 
  • django/branches/queryset-refactor/docs/db-api.txt

    r7285 r7317  
    15601560 
    15611561Lookups that span relationships 
    1562 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     1562------------------------------- 
    15631563 
    15641564Django offers a powerful and intuitive way to "follow" relationships in 
     
    15831583    Blog.objects.filter(entry__headline__contains='Lennon') 
    15841584 
     1585Spanning multi-valued relationships 
     1586~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     1587 
     1588**New in Django development version** 
     1589 
     1590.. note:: 
     1591    This is an experimental API and subject to change prior to 
     1592    queryset-refactor being merged into trunk. 
     1593 
     1594When you are filtering an object based on a ``ManyToManyField`` or a reverse 
     1595``ForeignKeyField``, there are two different sorts of filter you may be 
     1596interested in. Consider the ``Blog``/``Entry`` relationship (``Blog`` to 
     1597``Entry`` is a one-to-many relation). We might be interested in finding blogs 
     1598that have an entry which has both *"Lennon"* in the headline and was published 
     1599today. Or we might want to find blogs that have an entry with *"Lennon"* in 
     1600the headline as well as an entry that was published today. Since there are 
     1601multiple entries associated with a single ``Blog``, both of these queries are 
     1602possible and make sense in some situations. 
     1603 
     1604The same type of situation arises with a ``ManyToManyField``. For example, if 
     1605an ``Entry`` has a ``ManyToManyField`` called ``tags``, we might want to find 
     1606entries linked to tags called *"music"* and *"bands"* or we might want an 
     1607entry that contains a tag with a name of *"music"* and a status of *"public"*. 
     1608 
     1609To handle both of these situations, Django has a consistent way of processing 
     1610``filter()`` and ``exclude()`` calls. Everything inside a single ``filter()`` 
     1611call is applied simultaneously to filter out items matching all those 
     1612requirements. Successive ``filter()`` calls further restrict the set of 
     1613objects, but for multi-valued relations, they apply to any object linked to 
     1614the primary model, not necessarily those objects that were selected by an 
     1615earlier ``filter()`` call. 
     1616 
     1617That may sound a bit confusing, so hopefully an example will clarify. To 
     1618select all blogs that contains entries with *"Lennon"* in the headline and 
     1619were published today, we would write:: 
     1620 
     1621    Blog.objects.filter(entry__headline__contains='Lennon', 
     1622            entry__pub_date=datetime.date.today()) 
     1623 
     1624To select all blogs that contain an entry with *"Lennon"* in the headline 
     1625**as well as** an entry that was published today, we would write:: 
     1626 
     1627    Blog.objects.filter(entry__headline__contains='Lennon').filter( 
     1628            entry__pub_date=datetime.date.today()) 
     1629 
     1630In this second example, the first filter restricted the queryset to all those 
     1631blogs linked to that particular type of entry. The second filter restricted 
     1632the set of blogs *further* to those that are also linked to the second type of 
     1633entry. The entries select by the second filter may or may not be the same as 
     1634the entries in the first filter. We are filtering the ``Blog`` items with each 
     1635filter statement, not the ``Entry`` items. 
     1636 
     1637All of this behaviour also applies to ``exclude()``: all the conditions in a 
     1638single ``exclude()`` statement apply to a single instance (if those conditions 
     1639are talking about the same multi-valued relation). Conditions in subsequent 
     1640``filter()`` or ``exclude()`` calls that refer to the same relation may end up 
     1641filtering on different linked objects. 
     1642 
    15851643Escaping percent signs and underscores in LIKE statements 
    1586 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     1644--------------------------------------------------------- 
    15871645 
    15881646The field lookups that equate to ``LIKE`` SQL statements (``iexact``, 
  • django/branches/queryset-refactor/tests/regressiontests/queries/models.py

    r7286 r7317  
    211211>>> Item.objects.filter(Q(tags=t1)).order_by('name') 
    212212[<Item: one>, <Item: two>] 
    213 >>> Item.objects.filter(Q(tags=t1) & Q(tags=t2)) 
    214 [<Item: one>] 
    215213>>> Item.objects.filter(Q(tags=t1)).filter(Q(tags=t2)) 
    216214[<Item: one>] 
     215 
     216Each filter call is processed "at once" against a single table, so this is 
     217different from the previous example as it tries to find tags that are two 
     218things at once (rather than two tags). 
     219>>> Item.objects.filter(Q(tags=t1) & Q(tags=t2)) 
     220[] 
     221 
     222>>> qs = Author.objects.filter(ranking__rank=2, ranking__id=rank1.id) 
     223>>> list(qs) 
     224[<Author: a2>] 
     225>>> qs.query.count_active_tables() 
     2262 
     227>>> qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=rank1.id) 
     228>>> qs.query.count_active_tables() 
     2293 
    217230 
    218231Bug #4464