From 349c12d3f5c015c2f8b36917da21230c2ac1acb4 Mon Sep 17 00:00:00 2001
From: Marc Tamlyn <marc.tamlyn@gmail.com>
Date: Tue, 15 Oct 2013 13:15:02 +0100
Subject: [PATCH] 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()
---
 django/db/models/sql/query.py  |  5 ++-
 docs/ref/models/querysets.txt  | 74 ++++++++++++----------------------
 docs/releases/1.7.txt          |  6 +++
 tests/select_related/models.py |  9 +++++
 tests/select_related/tests.py  | 11 ++++-
 5 files changed, 55 insertions(+), 50 deletions(-)

diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 9e54b973745..8be55d7962b 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 e2b6efc93a1..170df975bdc 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 a3fdc0e9f35..aa613c45389 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 ec41957adfc..11ef7db91fc 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 f07e28df99c..8949d997888 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)