From 7d6916e82700896ef23c50720d8cf0de4d6b9074 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Mon, 6 Jan 2020 16:44:32 -0700 Subject: Fixed #29789 -- Added support for nested relations to FilteredRelation. --- django/db/models/sql/query.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'django/db/models/sql/query.py') diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 1623263964..b53980a68f 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1419,14 +1419,30 @@ class Query(BaseExpression): def add_filtered_relation(self, filtered_relation, alias): filtered_relation.alias = alias lookups = dict(get_children_from_q(filtered_relation.condition)) - for lookup in chain((filtered_relation.relation_name,), lookups): - lookup_parts, field_parts, _ = self.solve_lookup_type(lookup) + relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(filtered_relation.relation_name) + if relation_lookup_parts: + raise ValueError( + "FilteredRelation's relation_name cannot contain lookups " + "(got %r)." % filtered_relation.relation_name + ) + for lookup in chain(lookups): + lookup_parts, lookup_field_parts, _ = self.solve_lookup_type(lookup) shift = 2 if not lookup_parts else 1 - if len(field_parts) > (shift + len(lookup_parts)): - raise ValueError( - "FilteredRelation's condition doesn't support nested " - "relations (got %r)." % lookup - ) + lookup_field_path = lookup_field_parts[:-shift] + for idx, lookup_field_part in enumerate(lookup_field_path): + if len(relation_field_parts) > idx: + if relation_field_parts[idx] != lookup_field_part: + raise ValueError( + "FilteredRelation's condition doesn't support " + "relations outside the %r (got %r)." + % (filtered_relation.relation_name, lookup) + ) + else: + raise ValueError( + "FilteredRelation's condition doesn't support nested " + "relations deeper than the relation_name (got %r for " + "%r)." % (lookup, filtered_relation.relation_name) + ) self._filtered_relations[filtered_relation.alias] = filtered_relation def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False): @@ -1459,7 +1475,14 @@ class Query(BaseExpression): field = self.annotation_select[name].output_field elif name in self._filtered_relations and pos == 0: filtered_relation = self._filtered_relations[name] - field = opts.get_field(filtered_relation.relation_name) + if LOOKUP_SEP in filtered_relation.relation_name: + parts = filtered_relation.relation_name.split(LOOKUP_SEP) + filtered_relation_path, field, _, _ = self.names_to_path( + parts, opts, allow_many, fail_on_missing, + ) + path.extend(filtered_relation_path[:-1]) + else: + field = opts.get_field(filtered_relation.relation_name) if field is not None: # Fields that contain one-to-many relations with a generic # model (like a GenericForeignKey) cannot generate reverse -- cgit v1.3