From 35911078fa40eb35859832987fedada76963c01e Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Wed, 10 Aug 2022 07:51:07 -0400 Subject: [PATCH] Replaced Expression.replace_references() with .replace_expressions(). The latter allows for more generic use cases beyond the currently limited ones constraints validation has. Refs #28333, #30581. --- django/contrib/postgres/constraints.py | 3 ++- django/db/models/constraints.py | 11 +++++++---- django/db/models/expressions.py | 13 ++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py index c67292f9f0..8b76de3c42 100644 --- a/django/contrib/postgres/constraints.py +++ b/django/contrib/postgres/constraints.py @@ -198,6 +198,7 @@ class ExclusionConstraint(BaseConstraint): replacement_map = instance._get_field_value_map( meta=model._meta, exclude=exclude ) + replacements = {F(field): value for field, value in replacement_map.items()} lookups = [] for idx, (expression, operator) in enumerate(self.expressions): if isinstance(expression, str): @@ -210,7 +211,7 @@ class ExclusionConstraint(BaseConstraint): for expr in expression.flatten(): if isinstance(expr, F) and expr.name in exclude: return - rhs_expression = expression.replace_references(replacement_map) + rhs_expression = expression.replace_expressions(replacements) # Remove OpClass because it only has sense during the constraint # creation. if isinstance(expression, OpClass): diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 8cf1f0ff20..49c7c91de9 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -332,11 +332,14 @@ class UniqueConstraint(BaseConstraint): return elif isinstance(expression, F) and expression.name in exclude: return - replacement_map = instance._get_field_value_map( - meta=model._meta, exclude=exclude - ) + replacements = { + F(field): value + for field, value in instance._get_field_value_map( + meta=model._meta, exclude=exclude + ).items() + } expressions = [ - Exact(expr, expr.replace_references(replacement_map)) + Exact(expr, expr.replace_expressions(replacements)) for expr in self.expressions ] queryset = queryset.filter(*expressions) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 6f635f565a..7d21285118 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -389,12 +389,15 @@ class BaseExpression: ) return clone - def replace_references(self, references_map): + def replace_expressions(self, replacements): + if replacement := replacements.get(self): + return replacement clone = self.copy() + source_expressions = clone.get_source_expressions() clone.set_source_expressions( [ - expr.replace_references(references_map) - for expr in self.get_source_expressions() + expr.replace_expressions(replacements) if expr else None + for expr in source_expressions ] ) return clone @@ -808,8 +811,8 @@ class F(Combinable): ): return query.resolve_ref(self.name, allow_joins, reuse, summarize) - def replace_references(self, references_map): - return references_map.get(self.name, self) + def replace_expressions(self, replacements): + return replacements.get(self, self) def asc(self, **kwargs): return OrderBy(self, **kwargs)