1
0
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:
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 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):

View File

@ -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
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View File

@ -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
========================== ==========================

View File

@ -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

View File

@ -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)