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:
Marc Tamlyn 2013-10-15 13:15:02 +01:00
parent dd1ab8982b
commit 349c12d3f5
5 changed files with 55 additions and 50 deletions

View File

@ -1712,7 +1712,10 @@ class Query(object):
certain related models (as opposed to all models, when
self.select_related=True).
"""
field_dict = {}
if isinstance(self.select_related, bool):
field_dict = {}
else:
field_dict = self.select_related
for field in fields:
d = field_dict
for part in field.split(LOOKUP_SEP):

View File

@ -688,13 +688,12 @@ manager or a ``QuerySet`` and do further filtering on the result. After calling
select_related
~~~~~~~~~~~~~~
.. method:: select_related()
.. method:: select_related(*fields)
Returns a ``QuerySet`` that will automatically "follow" foreign-key
relationships, selecting that additional related-object data when it executes
its query. This is a performance booster which results in (sometimes much)
larger queries but means later use of foreign-key relationships won't require
database queries.
Returns a ``QuerySet`` that will "follow" foreign-key relationships, selecting
additional related-object data when it executes its query. This is a
performance booster which results in a single more complex query but means
later use of foreign-key relationships won't require database queries.
The following examples illustrate the difference between plain lookups and
``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::
# 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
# in the previous query.
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::
from django.db import models
@ -731,10 +730,11 @@ following models::
# ...
author = models.ForeignKey(Person)
...then a call to ``Book.objects.select_related().get(id=4)`` will cache the
related ``Person`` *and* the related ``City``::
...then a call to ``Book.objects.select_related('person',
'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.
c = p.hometown # Doesn't hit the database.
@ -742,45 +742,9 @@ related ``Person`` *and* the related ``City``::
p = b.author # 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
:class:`~django.db.models.OneToOneField` relation in the list of fields
passed to ``select_related()``. This includes foreign keys that have
``null=True`` (which are omitted in a no-parameter ``select_related()`` call).
passed to ``select_related()``.
You can also refer to the reverse direction of a
: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
<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
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)
.. 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
~~~~~~~~~~~~~~~~

View File

@ -533,6 +533,12 @@ Miscellaneous
you relied on the default field ordering while having fields defined on both
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
==========================

View File

@ -66,3 +66,12 @@ class Species(models.Model):
genus = models.ForeignKey(Genus)
def __str__(self):
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

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
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):
@ -144,3 +144,12 @@ class SelectRelatedTests(TestCase):
def test_none_clears_list(self):
queryset = Species.objects.select_related('genus').select_related(None)
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)