summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Tamlyn <marc.tamlyn@gmail.com>2013-10-15 13:15:02 +0100
committerMarc Tamlyn <marc.tamlyn@gmail.com>2013-10-15 15:59:36 +0100
commit349c12d3f5c015c2f8b36917da21230c2ac1acb4 (patch)
treed1a8dfb839ecd3d9c65f55fcb93a27a7ba773b25
parentdd1ab8982be0ab8bd648b57c0bbc6a100a429695 (diff)
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()
-rw-r--r--django/db/models/sql/query.py5
-rw-r--r--docs/ref/models/querysets.txt74
-rw-r--r--docs/releases/1.7.txt6
-rw-r--r--tests/select_related/models.py9
-rw-r--r--tests/select_related/tests.py11
5 files changed, 55 insertions, 50 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 9e54b97374..8be55d7962 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -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):
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index e2b6efc93a..170df975bd 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -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
~~~~~~~~~~~~~~~~
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index a3fdc0e9f3..aa613c4538 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -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
==========================
diff --git a/tests/select_related/models.py b/tests/select_related/models.py
index ec41957adf..11ef7db91f 100644
--- a/tests/select_related/models.py
+++ b/tests/select_related/models.py
@@ -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
diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py
index f07e28df99..8949d99788 100644
--- a/tests/select_related/tests.py
+++ b/tests/select_related/tests.py
@@ -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)