Skip to content

Commit 3c005b5

Browse files
annalaurawjacobtylerwalls
authored andcommitted
Fixed #26379 -- Doc'd that the first filter() on a many-to-many relation is sticky.
1 parent 1ce6e78 commit 3c005b5

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

docs/topics/db/queries.txt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,8 @@ contained in a single :meth:`~django.db.models.query.QuerySet.filter` call.
655655
... )
656656
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>
657657

658+
.. _exclude-implementation:
659+
658660
.. note::
659661

660662
The behavior of :meth:`~django.db.models.query.QuerySet.filter` for queries
@@ -1966,6 +1968,71 @@ relationships accept primary key values. For example, if ``e1`` and ``e2`` are
19661968
a.entry_set.set([e1, e2])
19671969
a.entry_set.set([e1.pk, e2.pk])
19681970

1971+
Filtering on many-to-many relationships
1972+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1973+
1974+
When calling ``filter()`` on a many-to-many relationship, be aware that the
1975+
join between ``Entry`` and the intermediary model to ``Author`` is performed
1976+
only once, resulting in a restrictive, or "sticky", filter. Consider the
1977+
following example:
1978+
1979+
.. code-block:: pycon
1980+
1981+
>>> from datetime import date
1982+
>>> batucada = Blog.objects.create(name="Batucada Blog")
1983+
>>> e = Entry.objects.create(
1984+
... blog=batucada,
1985+
... headline="Supporting social movements with drums",
1986+
... pub_date=date(2019, 6, 14),
1987+
... )
1988+
1989+
>>> gloria = Author.objects.create(name="Gloria")
1990+
>>> anna = Author.objects.create(name="Anna")
1991+
>>> e.authors.add(gloria, anna)
1992+
1993+
>>> anna.entry_set.filter(authors__name="Gloria")
1994+
<QuerySet []>
1995+
1996+
This filtered query is looking for blog entries that are co-authored by
1997+
``anna`` and ``gloria``. You would expect it to return the entry ``e``.
1998+
However, the filter condition, which traverses the many-to-many
1999+
relationship between ``Entry`` and ``Author``, yields an empty
2000+
``QuerySet``.
2001+
2002+
Since the join between ``Entry`` and the intermediary model to ``Author``
2003+
happens only once, no single object of the joined models - i.e., a relation
2004+
between one author and one entry - can fulfill the query condition (entries
2005+
that are co-authored by ``anna`` and ``gloria``). You can circumvent this
2006+
behavior by chaining two consecutive ``filter()`` calls, resulting in two
2007+
separate joins and thus a more permissive filter:
2008+
2009+
.. code-block:: pycon
2010+
2011+
>>> anna.entry_set.filter().filter(authors__name="Gloria")
2012+
<QuerySet [<Entry: Supporting social movements with drums>]>
2013+
2014+
.. admonition:: exclude() is also sticky
2015+
2016+
Please note that for this example,
2017+
:meth:`~django.db.models.query.QuerySet.exclude` behaves similarly
2018+
to :meth:`~django.db.models.query.QuerySet.filter` despite being
2019+
implemented differently. When traversing the many-to-many relationship,
2020+
it does not exclude the entry ``e`` despite being co-authored by Gloria:
2021+
2022+
>>> anna.entry_set.exclude(authors__name="Gloria")
2023+
<QuerySet [<Entry: Supporting social movements with drums>]>
2024+
2025+
When chaining a second ``exclude()`` call, an empty ``QuerySet`` is
2026+
returned, as expected:
2027+
2028+
>>> anna.entry_set.exclude().exclude(authors__name="Gloria")
2029+
<QuerySet []>
2030+
2031+
However, in other cases, :meth:`~django.db.models.query.QuerySet.exclude`
2032+
behaves differently from :meth:`~django.db.models.query.QuerySet.filter`.
2033+
See the :ref:`note <exclude-implementation>` in the "Spanning multi-valued
2034+
relationships" section above.
2035+
19692036
One-to-one relationships
19702037
------------------------
19712038

0 commit comments

Comments
 (0)