From 2b446904bfeb8bb79762426eb5ee0b97f57e6844 Mon Sep 17 00:00:00 2001
From: David Bennett <david@dbinit.com>
Date: Mon, 30 Jan 2012 12:54:44 -0600
Subject: [PATCH] Fixed #13781 -- select_related and multiple inheritance

---
 django/db/models/query.py                          |   19 ++++++++++----
 django/db/models/sql/compiler.py                   |   12 ++++++---
 .../select_related_onetoone/models.py              |   26 ++++++++++++++++++++
 .../select_related_onetoone/tests.py               |   23 ++++++++++++++++-
 4 files changed, 70 insertions(+), 10 deletions(-)

diff --git a/django/db/models/query.py b/django/db/models/query.py
index 41c24c7..c76d6d0 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1238,7 +1238,7 @@ class EmptyQuerySet(QuerySet):
     value_annotation = False
 
 def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
-                   only_load=None, local_only=False):
+                   only_load=None, local_only=False, last_klass=None):
     """
     Helper function that recursively returns an information for a klass, to be
     used in get_cached_row.  It exists just to compute this information only
@@ -1260,6 +1260,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
        the full field list for `klass` can be assumed.
      * local_only - Only populate local fields. This is used when
        following reverse select-related relations
+     * last_klass - the last class seen when following reverse
+       select-related relations
     """
     if max_depth and requested is None and cur_depth > max_depth:
         # We've recursed deeply enough; stop now.
@@ -1305,8 +1307,14 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
         # But kwargs version of Model.__init__ is slower, so we should avoid using
         # it when it is not really neccesary.
         if local_only and len(klass._meta.local_fields) != len(klass._meta.fields):
-            field_count = len(klass._meta.local_fields)
-            field_names = [f.attname for f in klass._meta.local_fields]
+            parents = [p for p in klass._meta.get_parent_list()
+                       if p is not last_klass]
+            field_names = [f.attname for f in klass._meta.fields
+                           if f in klass._meta.local_fields
+                           or f.model in parents]
+            field_count = len(field_names)
+            if field_count == len(klass._meta.fields):
+                field_names = ()
         else:
             field_count = len(klass._meta.fields)
             field_names = ()
@@ -1330,7 +1338,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
             if o.field.unique and select_related_descend(o.field, restricted, requested, reverse=True):
                 next = requested[o.field.related_query_name()]
                 klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1,
-                                            requested=next, only_load=only_load, local_only=True)
+                                            requested=next, only_load=only_load, local_only=True,
+                                            last_klass=klass)
                 reverse_related_fields.append((o.field, klass_info))
 
     return klass, field_names, field_count, related_fields, reverse_related_fields
@@ -1416,7 +1425,7 @@ def get_cached_row(row, index_start, using,  klass_info, offset=0):
                 # Now populate all the non-local field values
                 # on the related object
                 for rel_field, rel_model in rel_obj._meta.get_fields_with_model():
-                    if rel_model is not None:
+                    if rel_model is not None and isinstance(obj, rel_model):
                         setattr(rel_obj, rel_field.attname, getattr(obj, rel_field.attname))
                         # populate the field cache for any related object
                         # that has already been retrieved
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 72948f9..c773a3b 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -246,7 +246,8 @@ class SQLCompiler(object):
         return result
 
     def get_default_columns(self, with_aliases=False, col_aliases=None,
-            start_alias=None, opts=None, as_pairs=False, local_only=False):
+            start_alias=None, opts=None, as_pairs=False, local_only=False,
+            last_opts=None):
         """
         Computes the default columns for selecting every field in the base
         model. Will sometimes be called to pull in related models (e.g. via
@@ -270,8 +271,9 @@ class SQLCompiler(object):
 
         if start_alias:
             seen = {None: start_alias}
+        parents = [p for p in opts.get_parent_list() if p._meta is not last_opts]
         for field, model in opts.get_fields_with_model():
-            if local_only and model is not None:
+            if local_only and model is not None and model not in parents:
                 continue
             if start_alias:
                 try:
@@ -282,7 +284,8 @@ class SQLCompiler(object):
                     else:
                         link_field = opts.get_ancestor_link(model)
                         alias = self.query.join((start_alias, model._meta.db_table,
-                                link_field.column, model._meta.pk.column))
+                                link_field.column, model._meta.pk.column),
+                                promote=(model in parents))
                     seen[model] = alias
             else:
                 # If we're starting from the base model of the queryset, the
@@ -728,7 +731,8 @@ class SQLCompiler(object):
                 )
                 used.add(alias)
                 columns, aliases = self.get_default_columns(start_alias=alias,
-                    opts=model._meta, as_pairs=True, local_only=True)
+                    opts=model._meta, as_pairs=True, local_only=True,
+                    last_opts=opts)
                 self.query.related_select_cols.extend(columns)
                 self.query.related_select_fields.extend(model._meta.fields)
 
diff --git a/tests/regressiontests/select_related_onetoone/models.py b/tests/regressiontests/select_related_onetoone/models.py
index 3d6da9b..4bfad1d 100644
--- a/tests/regressiontests/select_related_onetoone/models.py
+++ b/tests/regressiontests/select_related_onetoone/models.py
@@ -45,6 +45,7 @@ class StatDetails(models.Model):
 class AdvancedUserStat(UserStat):
     karma = models.IntegerField()
 
+
 class Image(models.Model):
     name = models.CharField(max_length=100)
 
@@ -52,3 +53,28 @@ class Image(models.Model):
 class Product(models.Model):
     name = models.CharField(max_length=100)
     image = models.OneToOneField(Image, null=True)
+
+
+class Parent1(models.Model):
+    name1 = models.CharField(max_length=50)
+    def __unicode__(self):
+        return self.name1
+
+
+class Parent2(models.Model):
+    name2 = models.CharField(max_length=50)
+    def __unicode__(self):
+        return self.name2
+
+
+class Child1(Parent1, Parent2):
+    other = models.CharField(max_length=50)
+    def __unicode__(self):
+        return self.name1
+
+
+class Child2(Parent1):
+    parent2 = models.OneToOneField(Parent2)
+    other = models.CharField(max_length=50)
+    def __unicode__(self):
+        return self.name1
diff --git a/tests/regressiontests/select_related_onetoone/tests.py b/tests/regressiontests/select_related_onetoone/tests.py
index 643a0ff..a57142c 100644
--- a/tests/regressiontests/select_related_onetoone/tests.py
+++ b/tests/regressiontests/select_related_onetoone/tests.py
@@ -3,7 +3,7 @@ from __future__ import with_statement, absolute_import
 from django.test import TestCase
 
 from .models import (User, UserProfile, UserStat, UserStatResult, StatDetails,
-    AdvancedUserStat, Image, Product)
+    AdvancedUserStat, Image, Product, Parent1, Parent2, Child1, Child2)
 
 
 class ReverseSelectRelatedTestCase(TestCase):
@@ -21,6 +21,14 @@ class ReverseSelectRelatedTestCase(TestCase):
         advstat = AdvancedUserStat.objects.create(user=user2, posts=200, karma=5,
                                                   results=results2)
         StatDetails.objects.create(base_stats=advstat, comments=250)
+        p1 = Parent1(name1="Only Parent1")
+        p1.save()
+        c1 = Child1(name1="Child1 Parent1", name2="Child1 Parent2")
+        c1.save()
+        p2 = Parent2(name2="Child2 Parent2")
+        p2.save()
+        c2 = Child2(name1="Child2 Parent1", parent2=p2)
+        c2.save()
 
     def test_basic(self):
         with self.assertNumQueries(1):
@@ -80,3 +88,16 @@ class ReverseSelectRelatedTestCase(TestCase):
         p2 = Product.objects.create(name="Talking Django Plushie")
 
         self.assertEqual(len(Product.objects.select_related("image")), 2)
+
+    def test_parent_only(self):
+        Parent1.objects.select_related('child1').get(name1="Only Parent1")
+
+    def test_multiple_subclass(self):
+        with self.assertNumQueries(1):
+            p = Parent1.objects.select_related('child1').get(name1="Child1 Parent1")
+            self.assertEqual(p.child1.name2, u'Child1 Parent2')
+
+    def test_onetoone_with_subclass(self):
+        with self.assertNumQueries(1):
+            p = Parent2.objects.select_related('child2').get(name2="Child2 Parent2")
+            self.assertEqual(p.child2.name1, u'Child2 Parent1')
-- 
1.7.7.3

