diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 77f24fcf24a..ad82b167a68 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -702,6 +702,11 @@ class Query(object): aliases = list(aliases) while aliases: alias = aliases.pop(0) + if self.alias_map[alias].rhs_join_col is None: + # This is the base table (first FROM entry) - this table + # isn't really joined at all in the query, so we should not + # alter its join type. + continue parent_alias = self.alias_map[alias].lhs_alias parent_louter = (parent_alias and self.alias_map[parent_alias].join_type == self.LOUTER) @@ -1188,6 +1193,9 @@ class Query(object): for alias in join_list: if self.alias_map[alias].join_type == self.LOUTER: j_col = self.alias_map[alias].rhs_join_col + # The join promotion logic should never produce + # a LOUTER join for the base join - assert that. + assert j_col is not None entry = self.where_class() entry.add( (Constraint(alias, j_col, None), 'isnull', True), diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index b9f3ab27eb9..af0f421502e 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -878,3 +878,14 @@ class AggregationTests(TestCase): connection.ops.convert_values(testData, testField), testData ) + + def test_annotate_joins(self): + """ + Test that the base table's join isn't promoted to LOUTER. This could + cause the query generation to fail if there is an exclude() for fk-field + in the query, too. Refs #19087. + """ + qs = Book.objects.annotate(n=Count('pk')) + self.assertIs(qs.query.alias_map['aggregation_regress_book'].join_type, None) + # Check that the query executes without problems. + self.assertEqual(len(qs.exclude(publisher=-1)), 6)