diff options
| author | Simon Charette <charette.s@gmail.com> | 2025-02-19 03:04:16 -0500 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-08-04 09:42:32 +0200 |
| commit | b3bb7230e1225861b5c1f08931f2d82c2b04133a (patch) | |
| tree | 0832739f5f549254b63f1c4fbeb42040e241de2b /django/db/models/query_utils.py | |
| parent | e5ccb69bc3da407ab2b0477c0cc5db27c7207225 (diff) | |
[5.2.x] Fixed #34871, #36518 -- Implemented unresolved lookups expression replacement.
This allows the proper resolving of lookups when performing constraint
validation involving Q and Case objects.
Thanks Andrew Roberts for the report and Sarah for the tests and review.
Backport of 079d31e698fa08dd92e2bc4f3fe9b4817a214419 from main.
Diffstat (limited to 'django/db/models/query_utils.py')
| -rw-r--r-- | django/db/models/query_utils.py | 41 |
1 files changed, 40 insertions, 1 deletions
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index f0ae810f47..d219c5fb0e 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -13,7 +13,7 @@ from collections import namedtuple from contextlib import nullcontext from django.core.exceptions import FieldError -from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections, transaction +from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections, models, transaction from django.db.models.constants import LOOKUP_SEP from django.utils import tree from django.utils.functional import cached_property @@ -99,6 +99,45 @@ class Q(tree.Node): query.promote_joins(joins) return clause + def replace_expressions(self, replacements): + if not replacements: + return self + clone = self.create(connector=self.connector, negated=self.negated) + for child in self.children: + child_replacement = child + if isinstance(child, tuple): + lhs, rhs = child + if LOOKUP_SEP in lhs: + path, lookup = lhs.rsplit(LOOKUP_SEP, 1) + else: + path = lhs + lookup = None + field = models.F(path) + if ( + field_replacement := field.replace_expressions(replacements) + ) is not field: + # Handle the implicit __exact case by falling back to an + # extra transform when get_lookup returns no match for the + # last component of the path. + if lookup is None: + lookup = "exact" + if (lookup_class := field_replacement.get_lookup(lookup)) is None: + if ( + transform_class := field_replacement.get_transform(lookup) + ) is not None: + field_replacement = transform_class(field_replacement) + lookup = "exact" + lookup_class = field_replacement.get_lookup(lookup) + if rhs is None and lookup == "exact": + lookup_class = field_replacement.get_lookup("isnull") + rhs = True + if lookup_class is not None: + child_replacement = lookup_class(field_replacement, rhs) + else: + child_replacement = child.replace_expressions(replacements) + clone.children.append(child_replacement) + return clone + def flatten(self): """ Recursively yield this Q object and all subexpressions, in depth-first |
