diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 05c19f3..b8e0e88 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -395,8 +395,7 @@ class SQLCompiler(object):
             self.query.ref_alias(alias)
 
         # Must use left outer joins for nullable fields and their relations.
-        self.query.promote_alias_chain(joins,
-            self.query.alias_map[joins[0]][JOIN_TYPE] == self.query.LOUTER)
+        self.query.promote_joins(joins)
 
         # If we get to this point and the field is a relation to another model,
         # append the default ordering for that model.
@@ -577,8 +576,7 @@ class SQLCompiler(object):
                     alias_chain.append(alias)
                     for (dupe_opts, dupe_col) in dupe_set:
                         self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias)
-                if self.query.alias_map[root_alias][JOIN_TYPE] == self.query.LOUTER:
-                    self.query.promote_alias_chain(alias_chain, True)
+                self.query.promote_joins(alias_chain, True)
             else:
                 alias = root_alias
 
@@ -595,8 +593,7 @@ class SQLCompiler(object):
             columns, aliases = self.get_default_columns(start_alias=alias,
                     opts=f.rel.to._meta, as_pairs=True)
             self.query.related_select_cols.extend(columns)
-            if self.query.alias_map[alias][JOIN_TYPE] == self.query.LOUTER:
-                self.query.promote_alias_chain(aliases, True)
+            self.query.promote_joins(aliases, True)
             self.query.related_select_fields.extend(f.rel.to._meta.fields)
             if restricted:
                 next = requested.get(f.name, {})
@@ -669,7 +666,9 @@ class SQLCompiler(object):
                 self.query.related_select_fields.extend(model._meta.fields)
 
                 next = requested.get(f.related_query_name(), {})
-                new_nullable = f.null or None
+                # Use True here because we are looking at the _reverse_ side of
+                # the relation, which is always nullable.
+                new_nullable = True
 
                 self.fill_related_selections(model._meta, table, cur_depth+1,
                     used, next, restricted, new_nullable)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 110e317..2d68a9d 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -479,8 +479,7 @@ class Query(object):
             # Find aliases that are exclusive to rhs or lhs.
             # These are promoted to outer joins.
             outer_aliases = (l_tables | r_tables) - (l_tables & r_tables)
-            for alias in outer_aliases:
-                self.promote_alias(alias, True)
+            self.promote_joins(outer_aliases, True)
 
         # Now relabel a copy of the rhs where-clause and add it to the current
         # one.
@@ -661,32 +660,24 @@ class Query(object):
         """ Decreases the reference count for this alias. """
         self.alias_refcount[alias] -= 1
 
-    def promote_alias(self, alias, unconditional=False):
-        """
-        Promotes the join type of an alias to an outer join if it's possible
-        for the join to contain NULL values on the left. If 'unconditional' is
-        False, the join is only promoted if it is nullable, otherwise it is
-        always promoted.
-
-        Returns True if the join was promoted.
-        """
-        if ((unconditional or self.alias_map[alias][NULLABLE]) and
-                self.alias_map[alias][JOIN_TYPE] != self.LOUTER):
-            data = list(self.alias_map[alias])
-            data[JOIN_TYPE] = self.LOUTER
-            self.alias_map[alias] = tuple(data)
-            return True
-        return False
-
-    def promote_alias_chain(self, chain, must_promote=False):
-        """
-        Walks along a chain of aliases, promoting the first nullable join and
-        any joins following that. If 'must_promote' is True, all the aliases in
-        the chain are promoted.
-        """
-        for alias in chain:
-            if self.promote_alias(alias, must_promote):
-                must_promote = True
+    def promote_joins(self, aliases, unconditional=False):
+        """
+        Promotes recursively the join type of given aliases and its childs to an outer
+        join if it's possible for the join to contain NULL values on the left. 
+        If 'unconditional' is False, the join is only promoted if it is nullable or the
+        parent join is an outer join. otherwise it is always promoted.
+        """
+        for alias in aliases:
+            if alias not in ('auth_permission', 'django_content_type'):
+                pass
+                # import ipdb; ipdb.set_trace()
+            parent_alias = self.alias_map[alias][LHS_ALIAS]
+            parent_louter = parent_alias and self.alias_map[parent_alias][JOIN_TYPE] == self.LOUTER or False
+            if ((unconditional or self.alias_map[alias][NULLABLE] or parent_louter) and
+                    self.alias_map[alias][JOIN_TYPE] != self.LOUTER):
+                data = list(self.alias_map[alias])
+                data[JOIN_TYPE] = self.LOUTER
+                self.alias_map[alias] = tuple(data)
 
     def promote_unused_aliases(self, initial_refcounts, used_aliases):
         """
@@ -696,19 +687,10 @@ class Query(object):
         then and which ones haven't been used and promotes all of those
         aliases, plus any children of theirs in the alias tree, to outer joins.
         """
-        # FIXME: There's some (a lot of!) overlap with the similar OR promotion
-        # in add_filter(). It's not quite identical, but is very similar. So
-        # pulling out the common bits is something for later.
-        considered = {}
         for alias in self.tables:
-            if alias not in used_aliases:
-                continue
-            if (alias not in initial_refcounts or
+            if alias in used_aliases and (alias not in initial_refcounts or
                     self.alias_refcount[alias] == initial_refcounts[alias]):
-                parent = self.alias_map[alias][LHS_ALIAS]
-                must_promote = considered.get(parent, False)
-                promoted = self.promote_alias(alias, must_promote)
-                considered[alias] = must_promote or promoted
+                self.promote_joins([alias])
 
     def change_aliases(self, change_map):
         """
@@ -875,7 +857,7 @@ class Query(object):
                         continue
                     self.ref_alias(alias)
                     if promote:
-                        self.promote_alias(alias)
+                        self.promote_joins([alias])
                     return alias
 
         # No reuse is possible, so we need a new alias.
@@ -984,8 +966,7 @@ class Query(object):
             # If the aggregate references a model or field that requires a join,
             # those joins must be LEFT OUTER - empty join rows must be returned
             # in order for zeros to be returned for those aggregates.
-            for column_alias in join_list:
-                self.promote_alias(column_alias, unconditional=True)
+            self.promote_joins(join_list, unconditional=True)
 
             col = (join_list[-1], col)
         else:
@@ -1081,7 +1062,7 @@ class Query(object):
             # If the comparison is against NULL, we may need to use some left
             # outer joins when creating the join chain. This is only done when
             # needed, as it's less efficient at the database level.
-            self.promote_alias_chain(join_list)
+            self.promote_joins(join_list)
 
         # Process the join list to see if we can remove any inner joins from
         # the far end (fewer tables in a query is better).
@@ -1102,13 +1083,10 @@ class Query(object):
                 table = table_it.next()
                 if join == table and self.alias_refcount[join] > 1:
                     continue
-                join_promote = self.promote_alias(join)
+                join_promote = self.promote_joins([join])
                 if table != join:
-                    table_promote = self.promote_alias(table)
+                    table_promote = self.promote_joins([table])
                 break
-            self.promote_alias_chain(join_it, join_promote)
-            self.promote_alias_chain(table_it, table_promote)
-
 
         if having_clause or force_having:
             if (alias, col) not in self.group_by:
@@ -1120,7 +1098,7 @@ class Query(object):
                 connector)
 
         if negate:
-            self.promote_alias_chain(join_list)
+            self.promote_joins(join_list)
             if lookup_type != 'isnull':
                 if len(join_list) > 1:
                     for alias in join_list:
@@ -1579,7 +1557,7 @@ class Query(object):
                         final_alias = join[LHS_ALIAS]
                         col = join[LHS_JOIN_COL]
                         joins = joins[:-1]
-                self.promote_alias_chain(joins[1:])
+                self.promote_joins(joins[1:])
                 self.select.append((final_alias, col))
                 self.select_fields.append(field)
         except MultiJoin:
diff --git a/tests/regressiontests/nested_foreign_keys/__init__.py b/tests/regressiontests/nested_foreign_keys/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/nested_foreign_keys/models.py b/tests/regressiontests/nested_foreign_keys/models.py
new file mode 100644
index 0000000..50d4479
--- /dev/null
+++ b/tests/regressiontests/nested_foreign_keys/models.py
@@ -0,0 +1,28 @@
+from django.db import models
+
+
+class Person(models.Model):
+    name = models.CharField(max_length=200)
+
+
+class Movie(models.Model):
+    title = models.CharField(max_length=200)
+    director = models.ForeignKey(Person)
+
+
+class Event(models.Model):
+    pass
+
+
+class Screening(Event):
+    movie = models.ForeignKey(Movie)
+
+class ScreeningNullFK(Event):
+    movie = models.ForeignKey(Movie, null=True)
+
+
+class Package(models.Model):
+    screening = models.ForeignKey(Screening, null=True)
+
+class PackageNullFK(models.Model):
+    screening = models.ForeignKey(ScreeningNullFK, null=True)
diff --git a/tests/regressiontests/nested_foreign_keys/tests.py b/tests/regressiontests/nested_foreign_keys/tests.py
new file mode 100644
index 0000000..65e01f2
--- /dev/null
+++ b/tests/regressiontests/nested_foreign_keys/tests.py
@@ -0,0 +1,165 @@
+from django.test import TestCase
+
+from models import Person, Movie, Event, Screening, ScreeningNullFK, Package, PackageNullFK
+
+
+# These are tests for #16715. The basic scheme is always the same: 3 models with
+# 2 relations. The first relation may be null, while the second is non-nullable.
+# In some cases, Django would pick the wrong join type for the second relation,
+# resulting in missing objects in the queryset.
+#
+#   Model A
+#   | (Relation A/B : nullable)
+#   Model B
+#   | (Relation B/C : non-nullable)
+#   Model C
+#
+# Because of the possibility of NULL rows resulting from the LEFT OUTER JOIN
+# between Model A and Model B (i.e. instances of A without reference to B),
+# the second join must also be LEFT OUTER JOIN, so that we do not ignore
+# instances of A that do not reference B.
+#
+# Relation A/B can either be an explicit foreign key or an implicit reverse
+# relation such as introduced by one-to-one relations (through multi-table
+# inheritance).
+class NestedForeignKeysTests(TestCase):
+    def setUp(self):
+        self.director = Person.objects.create(name=u'Terry Gilliam / Terry Jones')
+        self.movie = Movie.objects.create(title=u'Monty Python and the Holy Grail', director=self.director)
+
+
+    # This test failed in #16715 because in some cases INNER JOIN was selected
+    # for the second foreign key relation instead of LEFT OUTER JOIN.
+    def testInheritance(self):
+        some_event = Event.objects.create()
+        screening = Screening.objects.create(movie=self.movie)
+
+        self.assertEqual(len(Event.objects.all()), 2)
+        self.assertEqual(len(Event.objects.select_related('screening')), 2)
+        # This failed.
+        self.assertEqual(len(Event.objects.select_related('screening__movie')), 2)
+
+        self.assertEqual(len(Event.objects.values()), 2)
+        self.assertEqual(len(Event.objects.values('screening__pk')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__pk')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__title')), 2)
+        # This failed.
+        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
+
+        # Simple filter/exclude queries for good measure.
+        self.assertEqual(Event.objects.filter(screening__movie=self.movie).count(), 1)
+        self.assertEqual(Event.objects.exclude(screening__movie=self.movie).count(), 1)
+
+
+    # These all work because the second foreign key in the chain has null=True.
+    def testInheritanceNullFK(self):
+        some_event = Event.objects.create()
+        screening = ScreeningNullFK.objects.create(movie=None)
+        screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
+
+        self.assertEqual(len(Event.objects.all()), 3)
+        self.assertEqual(len(Event.objects.select_related('screeningnullfk')), 3)
+        self.assertEqual(len(Event.objects.select_related('screeningnullfk__movie')), 3)
+
+        self.assertEqual(len(Event.objects.values()), 3)
+        self.assertEqual(len(Event.objects.values('screeningnullfk__pk')), 3)
+        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk')), 3)
+        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__title')), 3)
+        self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk', 'screeningnullfk__movie__title')), 3)
+
+        self.assertEqual(Event.objects.filter(screeningnullfk__movie=self.movie).count(), 1)
+        self.assertEqual(Event.objects.exclude(screeningnullfk__movie=self.movie).count(), 2)
+
+
+    # This test failed in #16715 because in some cases INNER JOIN was selected
+    # for the second foreign key relation instead of LEFT OUTER JOIN.
+    def testExplicitForeignKey(self):
+        package = Package.objects.create()
+        screening = Screening.objects.create(movie=self.movie)
+        package_with_screening = Package.objects.create(screening=screening)
+
+        self.assertEqual(len(Package.objects.all()), 2)
+        self.assertEqual(len(Package.objects.select_related('screening')), 2)
+        self.assertEqual(len(Package.objects.select_related('screening__movie')), 2)
+
+        self.assertEqual(len(Package.objects.values()), 2)
+        self.assertEqual(len(Package.objects.values('screening__pk')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__pk')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__title')), 2)
+        # This failed.
+        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
+
+        self.assertEqual(Package.objects.filter(screening__movie=self.movie).count(), 1)
+        self.assertEqual(Package.objects.exclude(screening__movie=self.movie).count(), 1)
+
+
+    # These all work because the second foreign key in the chain has null=True.
+    def testExplicitForeignKeyNullFK(self):
+        package = PackageNullFK.objects.create()
+        screening = ScreeningNullFK.objects.create(movie=None)
+        screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
+        package_with_screening = PackageNullFK.objects.create(screening=screening)
+        package_with_screening_with_movie = PackageNullFK.objects.create(screening=screening_with_movie)
+
+        self.assertEqual(len(PackageNullFK.objects.all()), 3)
+        self.assertEqual(len(PackageNullFK.objects.select_related('screening')), 3)
+        self.assertEqual(len(PackageNullFK.objects.select_related('screening__movie')), 3)
+
+        self.assertEqual(len(PackageNullFK.objects.values()), 3)
+        self.assertEqual(len(PackageNullFK.objects.values('screening__pk')), 3)
+        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk')), 3)
+        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__title')), 3)
+        self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk', 'screening__movie__title')), 3)
+
+        self.assertEqual(PackageNullFK.objects.filter(screening__movie=self.movie).count(), 1)
+        self.assertEqual(PackageNullFK.objects.exclude(screening__movie=self.movie).count(), 2)
+
+
+# Some additional tests for #16715. The only difference is the depth of the
+# nesting as we now use 4 models instead of 3 (and thus 3 relations). This
+# checks if promotion of join types works for deeper nesting too.
+class DeeplyNestedForeignKeysTests(TestCase):
+    def setUp(self):
+        self.director = Person.objects.create(name=u'Terry Gilliam / Terry Jones')
+        self.movie = Movie.objects.create(title=u'Monty Python and the Holy Grail', director=self.director)
+
+
+    def testInheritance(self):
+        some_event = Event.objects.create()
+        screening = Screening.objects.create(movie=self.movie)
+
+        self.assertEqual(len(Event.objects.all()), 2)
+        self.assertEqual(len(Event.objects.select_related('screening__movie__director')), 2)
+
+        self.assertEqual(len(Event.objects.values()), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__director__pk')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__director__name')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
+        self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
+
+        self.assertEqual(Event.objects.filter(screening__movie__director=self.director).count(), 1)
+        self.assertEqual(Event.objects.exclude(screening__movie__director=self.director).count(), 1)
+
+
+    def testExplicitForeignKey(self):
+        package = Package.objects.create()
+        screening = Screening.objects.create(movie=self.movie)
+        package_with_screening = Package.objects.create(screening=screening)
+
+        self.assertEqual(len(Package.objects.all()), 2)
+        self.assertEqual(len(Package.objects.select_related('screening__movie__director')), 2)
+
+        self.assertEqual(len(Package.objects.values()), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__director__pk')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__director__name')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
+        self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
+
+        self.assertEqual(Package.objects.filter(screening__movie__director=self.director).count(), 1)
+        self.assertEqual(Package.objects.exclude(screening__movie__director=self.director).count(), 1)
