[4.0.x] Fixed #27936 -- Rewrote spanning multi-valued relationships docs.
Backport of 6174814dbe
from main
This commit is contained in:
parent
e9b023b8e4
commit
c46e996307
|
@ -530,55 +530,77 @@ those latter objects, you could write::
|
||||||
Spanning multi-valued relationships
|
Spanning multi-valued relationships
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
When you are filtering an object based on a
|
When spanning a :class:`~django.db.models.ManyToManyField` or a reverse
|
||||||
:class:`~django.db.models.ManyToManyField` or a reverse
|
:class:`~django.db.models.ForeignKey` (such as from ``Blog`` to ``Entry``),
|
||||||
:class:`~django.db.models.ForeignKey`, there are two different sorts of filter
|
filtering on multiple attributes raises the question of whether to require each
|
||||||
you may be interested in. Consider the ``Blog``/``Entry`` relationship
|
attribute to coincide in the same related object. We might seek blogs that have
|
||||||
(``Blog`` to ``Entry`` is a one-to-many relation). We might be interested in
|
an entry from 2008 with *“Lennon”* in its headline, or we might seek blogs that
|
||||||
finding blogs that have an entry which has both *"Lennon"* in the headline and
|
merely have any entry from 2008 as well as some newer or older entry with
|
||||||
was published in 2008. Or we might want to find blogs that have an entry with
|
*“Lennon”* in its headline.
|
||||||
*"Lennon"* in the headline as well as an entry that was published
|
|
||||||
in 2008. 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
|
To select all blogs containing at least one entry from 2008 having *"Lennon"*
|
||||||
:class:`~django.db.models.ManyToManyField`. For example, if an ``Entry`` has a
|
in its headline (the same entry satisfying both conditions), we would write::
|
||||||
:class:`~django.db.models.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
|
|
||||||
:meth:`~django.db.models.query.QuerySet.filter` calls. Everything inside a
|
|
||||||
single :meth:`~django.db.models.query.QuerySet.filter` call is applied
|
|
||||||
simultaneously to filter out items matching all those requirements. Successive
|
|
||||||
:meth:`~django.db.models.query.QuerySet.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 :meth:`~django.db.models.query.QuerySet.filter` call.
|
|
||||||
|
|
||||||
That may sound a bit confusing, so hopefully an example will clarify. To
|
|
||||||
select all blogs that contain entries with both *"Lennon"* in the headline
|
|
||||||
and that were published in 2008 (the same entry satisfying both conditions),
|
|
||||||
we would write::
|
|
||||||
|
|
||||||
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
|
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
|
||||||
|
|
||||||
To select all blogs that contain an entry with *"Lennon"* in the headline
|
Otherwise, to perform a more permissive query selecting any blogs with merely
|
||||||
**as well as** an entry that was published in 2008, we would write::
|
*some* entry with *"Lennon"* in its headline and *some* entry from 2008, we
|
||||||
|
would write::
|
||||||
|
|
||||||
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
|
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
|
||||||
|
|
||||||
Suppose there is only one blog that had both entries containing *"Lennon"* and
|
Suppose there is only one blog that has both entries containing *"Lennon"* and
|
||||||
entries from 2008, but that none of the entries from 2008 contained *"Lennon"*.
|
entries from 2008, but that none of the entries from 2008 contained *"Lennon"*.
|
||||||
The first query would not return any blogs, but the second query would return
|
The first query would not return any blogs, but the second query would return
|
||||||
that one blog.
|
that one blog. (This is because the entries selected 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.) In short,
|
||||||
|
if each condition needs to match the same related object, then each should be
|
||||||
|
contained in a single :meth:`~django.db.models.query.QuerySet.filter` call.
|
||||||
|
|
||||||
In the second example, the first filter restricts the queryset to all those
|
.. note::
|
||||||
blogs linked to entries with *"Lennon"* in the headline. The second filter
|
|
||||||
restricts the set of blogs *further* to those that are also linked to entries
|
As the second (more permissive) query chains multiple filters, it performs
|
||||||
that were published in 2008. The entries selected by the second filter may or
|
multiple joins to the primary model, potentially yielding duplicates.
|
||||||
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.
|
>>> from datetime import date
|
||||||
|
>>> beatles = Blog.objects.create(name='Beatles Blog')
|
||||||
|
>>> pop = Blog.objects.create(name='Pop Music Blog')
|
||||||
|
>>> Entry.objects.create(
|
||||||
|
... blog=beatles,
|
||||||
|
... headline='New Lennon Biography',
|
||||||
|
... pub_date=date(2008, 6, 1),
|
||||||
|
... )
|
||||||
|
<Entry: New Lennon Biography>
|
||||||
|
>>> Entry.objects.create(
|
||||||
|
... blog=beatles,
|
||||||
|
... headline='New Lennon Biography in Paperback',
|
||||||
|
... pub_date=date(2009, 6, 1),
|
||||||
|
... )
|
||||||
|
<Entry: New Lennon Biography in Paperback>
|
||||||
|
>>> Entry.objects.create(
|
||||||
|
... blog=pop,
|
||||||
|
... headline='Best Albums of 2008',
|
||||||
|
... pub_date=date(2008, 12, 15),
|
||||||
|
... )
|
||||||
|
<Entry: Best Albums of 2008>
|
||||||
|
>>> Entry.objects.create(
|
||||||
|
... blog=pop,
|
||||||
|
... headline='Lennon Would Have Loved Hip Hop',
|
||||||
|
... pub_date=date(2020, 4, 1),
|
||||||
|
... )
|
||||||
|
<Entry: Lennon Would Have Loved Hip Hop>
|
||||||
|
>>> Blog.objects.filter(
|
||||||
|
... entry__headline__contains='Lennon',
|
||||||
|
... entry__pub_date__year=2008,
|
||||||
|
... )
|
||||||
|
<QuerySet [<Blog: Beatles Blog>]>
|
||||||
|
>>> Blog.objects.filter(
|
||||||
|
... entry__headline__contains='Lennon',
|
||||||
|
... ).filter(
|
||||||
|
... entry__pub_date__year=2008,
|
||||||
|
... )
|
||||||
|
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue