When doing an exclude against any kind of N-to-many relation, we need to use a subquery. This method constructs the nested query, given the original exclude filter (filter_expr) and the portion up to the first N-to-many relation field. For example, if the or
(self, filter_expr, can_reuse, names_with_path)
| 2110 | return transform |
| 2111 | |
| 2112 | def split_exclude(self, filter_expr, can_reuse, names_with_path): |
| 2113 | """ |
| 2114 | When doing an exclude against any kind of N-to-many relation, we need |
| 2115 | to use a subquery. This method constructs the nested query, given the |
| 2116 | original exclude filter (filter_expr) and the portion up to the first |
| 2117 | N-to-many relation field. |
| 2118 | |
| 2119 | For example, if the origin filter is ~Q(child__name='foo'), filter_expr |
| 2120 | is ('child__name', 'foo') and can_reuse is a set of joins usable for |
| 2121 | filters in the original query. |
| 2122 | |
| 2123 | We will turn this into equivalent of: |
| 2124 | WHERE NOT EXISTS( |
| 2125 | SELECT 1 |
| 2126 | FROM child |
| 2127 | WHERE name = 'foo' AND child.parent_id = parent.id |
| 2128 | LIMIT 1 |
| 2129 | ) |
| 2130 | """ |
| 2131 | # Generate the inner query. |
| 2132 | query = self.__class__(self.model) |
| 2133 | query._filtered_relations = self._filtered_relations |
| 2134 | filter_lhs, filter_rhs = filter_expr |
| 2135 | if isinstance(filter_rhs, OuterRef): |
| 2136 | filter_rhs = OuterRef(filter_rhs) |
| 2137 | elif isinstance(filter_rhs, F): |
| 2138 | filter_rhs = OuterRef(filter_rhs.name) |
| 2139 | query.add_filter(filter_lhs, filter_rhs) |
| 2140 | query.clear_ordering(force=True) |
| 2141 | # Try to have as simple as possible subquery -> trim leading joins from |
| 2142 | # the subquery. |
| 2143 | trimmed_prefix, contains_louter = query.trim_start(names_with_path) |
| 2144 | |
| 2145 | col = query.select[0] |
| 2146 | select_field = col.target |
| 2147 | alias = col.alias |
| 2148 | if alias in can_reuse: |
| 2149 | pk = select_field.model._meta.pk |
| 2150 | # Need to add a restriction so that outer query's filters are in |
| 2151 | # effect for the subquery, too. |
| 2152 | query.bump_prefix(self) |
| 2153 | lookup_class = select_field.get_lookup("exact") |
| 2154 | # Note that the query.select[0].alias is different from alias |
| 2155 | # due to bump_prefix above. |
| 2156 | lookup = lookup_class(pk.get_col(query.select[0].alias), pk.get_col(alias)) |
| 2157 | query.where.add(lookup, AND) |
| 2158 | query.external_aliases[alias] = True |
| 2159 | else: |
| 2160 | lookup_class = select_field.get_lookup("exact") |
| 2161 | lookup = lookup_class(col, ResolvedOuterRef(trimmed_prefix)) |
| 2162 | query.where.add(lookup, AND) |
| 2163 | |
| 2164 | condition, needed_inner = self.build_filter(Exists(query)) |
| 2165 | |
| 2166 | if contains_louter: |
| 2167 | or_null_condition, _ = self.build_filter( |
| 2168 | ("%s__isnull" % trimmed_prefix, True), |
| 2169 | current_negated=True, |
no test coverage detected