diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py index c67292f9f02..8b76de3c420 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 8cf1f0ff20e..49c7c91de95 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 6f635f565a8..7d212851188 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)