mirror of
https://github.com/django/django.git
synced 2025-01-03 15:06:09 +00:00
Fixed #16855 -- select_related() chains as expected.
select_related('foo').select_related('bar') is now equivalent to select_related('foo', 'bar'). Also reworded docs to recommend select_related(*fields) over select_related()
This commit is contained in:
parent
dd1ab8982b
commit
349c12d3f5
@ -1712,7 +1712,10 @@ class Query(object):
|
|||||||
certain related models (as opposed to all models, when
|
certain related models (as opposed to all models, when
|
||||||
self.select_related=True).
|
self.select_related=True).
|
||||||
"""
|
"""
|
||||||
field_dict = {}
|
if isinstance(self.select_related, bool):
|
||||||
|
field_dict = {}
|
||||||
|
else:
|
||||||
|
field_dict = self.select_related
|
||||||
for field in fields:
|
for field in fields:
|
||||||
d = field_dict
|
d = field_dict
|
||||||
for part in field.split(LOOKUP_SEP):
|
for part in field.split(LOOKUP_SEP):
|
||||||
|
@ -688,13 +688,12 @@ manager or a ``QuerySet`` and do further filtering on the result. After calling
|
|||||||
select_related
|
select_related
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. method:: select_related()
|
.. method:: select_related(*fields)
|
||||||
|
|
||||||
Returns a ``QuerySet`` that will automatically "follow" foreign-key
|
Returns a ``QuerySet`` that will "follow" foreign-key relationships, selecting
|
||||||
relationships, selecting that additional related-object data when it executes
|
additional related-object data when it executes its query. This is a
|
||||||
its query. This is a performance booster which results in (sometimes much)
|
performance booster which results in a single more complex query but means
|
||||||
larger queries but means later use of foreign-key relationships won't require
|
later use of foreign-key relationships won't require database queries.
|
||||||
database queries.
|
|
||||||
|
|
||||||
The following examples illustrate the difference between plain lookups and
|
The following examples illustrate the difference between plain lookups and
|
||||||
``select_related()`` lookups. Here's standard lookup::
|
``select_related()`` lookups. Here's standard lookup::
|
||||||
@ -708,13 +707,13 @@ The following examples illustrate the difference between plain lookups and
|
|||||||
And here's ``select_related`` lookup::
|
And here's ``select_related`` lookup::
|
||||||
|
|
||||||
# Hits the database.
|
# Hits the database.
|
||||||
e = Entry.objects.select_related().get(id=5)
|
e = Entry.objects.select_related('blog').get(id=5)
|
||||||
|
|
||||||
# Doesn't hit the database, because e.blog has been prepopulated
|
# Doesn't hit the database, because e.blog has been prepopulated
|
||||||
# in the previous query.
|
# in the previous query.
|
||||||
b = e.blog
|
b = e.blog
|
||||||
|
|
||||||
``select_related()`` follows foreign keys as far as possible. If you have the
|
You can follow foreign keys in a similar way to querying them. If you have the
|
||||||
following models::
|
following models::
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -731,10 +730,11 @@ following models::
|
|||||||
# ...
|
# ...
|
||||||
author = models.ForeignKey(Person)
|
author = models.ForeignKey(Person)
|
||||||
|
|
||||||
...then a call to ``Book.objects.select_related().get(id=4)`` will cache the
|
...then a call to ``Book.objects.select_related('person',
|
||||||
related ``Person`` *and* the related ``City``::
|
'person__city').get(id=4)`` will cache the related ``Person`` *and* the related
|
||||||
|
``City``::
|
||||||
|
|
||||||
b = Book.objects.select_related().get(id=4)
|
b = Book.objects.select_related('person__city').get(id=4)
|
||||||
p = b.author # Doesn't hit the database.
|
p = b.author # Doesn't hit the database.
|
||||||
c = p.hometown # Doesn't hit the database.
|
c = p.hometown # Doesn't hit the database.
|
||||||
|
|
||||||
@ -742,45 +742,9 @@ related ``Person`` *and* the related ``City``::
|
|||||||
p = b.author # Hits the database.
|
p = b.author # Hits the database.
|
||||||
c = p.hometown # Hits the database.
|
c = p.hometown # Hits the database.
|
||||||
|
|
||||||
Note that, by default, ``select_related()`` does not follow foreign keys that
|
|
||||||
have ``null=True``.
|
|
||||||
|
|
||||||
Usually, using ``select_related()`` can vastly improve performance because your
|
|
||||||
app can avoid many database calls. However, there are times you are only
|
|
||||||
interested in specific related models, or have deeply nested sets of
|
|
||||||
relationships, and in these cases ``select_related()`` can be optimized by
|
|
||||||
explicitly passing the related field names you are interested in. Only
|
|
||||||
the specified relations will be followed.
|
|
||||||
|
|
||||||
You can even do this for models that are more than one relation away by
|
|
||||||
separating the field names with double underscores, just as for filters. For
|
|
||||||
example, if you have this model::
|
|
||||||
|
|
||||||
class Room(models.Model):
|
|
||||||
# ...
|
|
||||||
building = models.ForeignKey(...)
|
|
||||||
|
|
||||||
class Group(models.Model):
|
|
||||||
# ...
|
|
||||||
teacher = models.ForeignKey(...)
|
|
||||||
room = models.ForeignKey(Room)
|
|
||||||
subject = models.ForeignKey(...)
|
|
||||||
|
|
||||||
...and you only needed to work with the ``room`` and ``subject`` attributes,
|
|
||||||
you could write this::
|
|
||||||
|
|
||||||
g = Group.objects.select_related('room', 'subject')
|
|
||||||
|
|
||||||
This is also valid::
|
|
||||||
|
|
||||||
g = Group.objects.select_related('room__building', 'subject')
|
|
||||||
|
|
||||||
...and would also pull in the ``building`` relation.
|
|
||||||
|
|
||||||
You can refer to any :class:`~django.db.models.ForeignKey` or
|
You can refer to any :class:`~django.db.models.ForeignKey` or
|
||||||
:class:`~django.db.models.OneToOneField` relation in the list of fields
|
:class:`~django.db.models.OneToOneField` relation in the list of fields
|
||||||
passed to ``select_related()``. This includes foreign keys that have
|
passed to ``select_related()``.
|
||||||
``null=True`` (which are omitted in a no-parameter ``select_related()`` call).
|
|
||||||
|
|
||||||
You can also refer to the reverse direction of a
|
You can also refer to the reverse direction of a
|
||||||
:class:`~django.db.models.OneToOneField` in the list of fields passed to
|
:class:`~django.db.models.OneToOneField` in the list of fields passed to
|
||||||
@ -789,6 +753,13 @@ You can also refer to the reverse direction of a
|
|||||||
is defined. Instead of specifying the field name, use the :attr:`related_name
|
is defined. Instead of specifying the field name, use the :attr:`related_name
|
||||||
<django.db.models.ForeignKey.related_name>` for the field on the related object.
|
<django.db.models.ForeignKey.related_name>` for the field on the related object.
|
||||||
|
|
||||||
|
There may be some situations where you wish to call ``select_related()`` with a
|
||||||
|
lot of related objects, or where you don't know all of the relations. In these
|
||||||
|
cases it is possible to call ``select_related()`` with no arguments. This will
|
||||||
|
follow all non-null foreign keys it can find - nullable foreign keys must be
|
||||||
|
specified. This is not recommended in most cases as it is likely to make the
|
||||||
|
underlying query more complex, and return more data, than is actually needed.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
If you need to clear the list of related fields added by past calls of
|
If you need to clear the list of related fields added by past calls of
|
||||||
@ -796,6 +767,13 @@ If you need to clear the list of related fields added by past calls of
|
|||||||
|
|
||||||
>>> without_relations = queryset.select_related(None)
|
>>> without_relations = queryset.select_related(None)
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
Chaining ``select_related`` calls now works in a similar way to other methods -
|
||||||
|
that is that ``select_related('foo', 'bar')`` is equivalent to
|
||||||
|
``select_related('foo').select_related('bar')``. Previously the latter would
|
||||||
|
have been equivalent to ``select_related('bar')``.
|
||||||
|
|
||||||
prefetch_related
|
prefetch_related
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -533,6 +533,12 @@ Miscellaneous
|
|||||||
you relied on the default field ordering while having fields defined on both
|
you relied on the default field ordering while having fields defined on both
|
||||||
the current class *and* on a parent ``Form``.
|
the current class *and* on a parent ``Form``.
|
||||||
|
|
||||||
|
* :meth:`~django.db.models.query.QuerySet.select_related` now chains in the
|
||||||
|
same way as other similar calls like ``prefetch_related``. That is,
|
||||||
|
``select_related('foo', 'bar')`` is equivalent to
|
||||||
|
``select_related('foo').select_related('bar')``. Previously the latter would
|
||||||
|
have been equivalent to ``select_related('bar')``.
|
||||||
|
|
||||||
Features deprecated in 1.7
|
Features deprecated in 1.7
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -66,3 +66,12 @@ class Species(models.Model):
|
|||||||
genus = models.ForeignKey(Genus)
|
genus = models.ForeignKey(Genus)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
# and we'll invent a new thing so we have a model with two foreign keys
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class HybridSpecies(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
parent_1 = models.ForeignKey(Species, related_name='child_1')
|
||||||
|
parent_2 = models.ForeignKey(Species, related_name='child_2')
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species
|
from .models import Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species, HybridSpecies
|
||||||
|
|
||||||
|
|
||||||
class SelectRelatedTests(TestCase):
|
class SelectRelatedTests(TestCase):
|
||||||
@ -144,3 +144,12 @@ class SelectRelatedTests(TestCase):
|
|||||||
def test_none_clears_list(self):
|
def test_none_clears_list(self):
|
||||||
queryset = Species.objects.select_related('genus').select_related(None)
|
queryset = Species.objects.select_related('genus').select_related(None)
|
||||||
self.assertEqual(queryset.query.select_related, False)
|
self.assertEqual(queryset.query.select_related, False)
|
||||||
|
|
||||||
|
def test_chaining(self):
|
||||||
|
parent_1, parent_2 = Species.objects.all()[:2]
|
||||||
|
HybridSpecies.objects.create(name='hybrid', parent_1=parent_1, parent_2=parent_2)
|
||||||
|
queryset = HybridSpecies.objects.select_related('parent_1').select_related('parent_2')
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
obj = queryset[0]
|
||||||
|
self.assertEquals(obj.parent_1, parent_1)
|
||||||
|
self.assertEquals(obj.parent_2, parent_2)
|
||||||
|
Loading…
Reference in New Issue
Block a user