summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob <tienrobertnguyenn@gmail.com>2019-05-07 00:42:56 +1000
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-05-07 14:47:50 +0200
commit6b736dd0747dc77473f1f7b691c196ef5912d7dd (patch)
treec4b9e8256905ddf6cd5a08c1ab4ad8b338e662ab
parent21aa2a5e785eef1f47beb1c3760fdd7d8915ae09 (diff)
Fixed #30349 -- Fixed QuerySet.exclude() on FilteredRelation.
Using annotated FilteredRelations raised a FieldError when coupled with exclude(). This is due to not passing filtered relation fields to the subquery created in split_exclude(). We fixed this issue by passing the filtered relation data to the newly created subquery. Secondly, in the case where an INNER JOIN is used in the excluded subquery, the ORM would trim the filtered relation INNER JOIN in attempt to simplify the query. This will also remove the ON clause filters generated by the FilteredRelation. We added logic to not trim the INNER JOIN if it is from FilteredRelation.
-rw-r--r--django/db/models/sql/query.py11
-rw-r--r--tests/filtered_relation/tests.py8
2 files changed, 16 insertions, 3 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index d69c24419b..08d7faf194 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1666,6 +1666,7 @@ class Query(BaseExpression):
filter_expr = (filter_lhs, OuterRef(filter_rhs.name))
# Generate the inner query.
query = Query(self.model)
+ query._filtered_relations = self._filtered_relations
query.add_filter(filter_expr)
query.clear_ordering(True)
# Try to have as simple as possible subquery -> trim leading joins from
@@ -2140,9 +2141,13 @@ class Query(BaseExpression):
join_field.foreign_related_fields[0].name)
trimmed_prefix = LOOKUP_SEP.join(trimmed_prefix)
# Lets still see if we can trim the first join from the inner query
- # (that is, self). We can't do this for LEFT JOINs because we would
- # miss those rows that have nothing on the outer side.
- if self.alias_map[lookup_tables[trimmed_paths + 1]].join_type != LOUTER:
+ # (that is, self). We can't do this for:
+ # - LEFT JOINs because we would miss those rows that have nothing on
+ # the outer side,
+ # - INNER JOINs from filtered relations because we would miss their
+ # filters.
+ first_join = self.alias_map[lookup_tables[trimmed_paths + 1]]
+ if first_join.join_type != LOUTER and not first_join.filtered_relation:
select_fields = [r[0] for r in join_field.related_fields]
select_alias = lookup_tables[trimmed_paths + 1]
self.unref_alias(lookup_tables[trimmed_paths])
diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py
index 5e85dff16c..52fe64dfa5 100644
--- a/tests/filtered_relation/tests.py
+++ b/tests/filtered_relation/tests.py
@@ -98,6 +98,14 @@ class FilteredRelationTests(TestCase):
[self.author1]
)
+ def test_with_exclude(self):
+ self.assertSequenceEqual(
+ Author.objects.annotate(
+ book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')),
+ ).exclude(book_alice__isnull=False),
+ [self.author2],
+ )
+
def test_with_join_and_complex_condition(self):
self.assertSequenceEqual(
Author.objects.annotate(