mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Fixed #35275 -- Fixed Meta.constraints validation crash on UniqueConstraint with OpClass().
This also introduces Expression.constraint_validation_compatible that allows specifying that expression should be ignored during a constraint validation.
This commit is contained in:
committed by
Sarah Boyce
parent
ceaf1e2848
commit
f030236a86
@@ -1,6 +1,5 @@
|
||||
from types import NoneType
|
||||
|
||||
from django.contrib.postgres.indexes import OpClass
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import DEFAULT_DB_ALIAS, NotSupportedError
|
||||
from django.db.backends.ddl_references import Expressions, Statement, Table
|
||||
@@ -208,12 +207,10 @@ class ExclusionConstraint(BaseConstraint):
|
||||
if isinstance(expr, F) and expr.name in exclude:
|
||||
return
|
||||
rhs_expression = expression.replace_expressions(replacements)
|
||||
# Remove OpClass because it only has sense during the constraint
|
||||
# creation.
|
||||
if isinstance(expression, OpClass):
|
||||
expression = expression.get_source_expressions()[0]
|
||||
if isinstance(rhs_expression, OpClass):
|
||||
rhs_expression = rhs_expression.get_source_expressions()[0]
|
||||
if hasattr(expression, "get_expression_for_validation"):
|
||||
expression = expression.get_expression_for_validation()
|
||||
if hasattr(rhs_expression, "get_expression_for_validation"):
|
||||
rhs_expression = rhs_expression.get_expression_for_validation()
|
||||
lookup = PostgresOperatorLookup(lhs=expression, rhs=rhs_expression)
|
||||
lookup.postgres_operator = operator
|
||||
lookups.append(lookup)
|
||||
|
||||
@@ -244,6 +244,7 @@ class SpGistIndex(PostgresIndex):
|
||||
|
||||
class OpClass(Func):
|
||||
template = "%(expressions)s %(name)s"
|
||||
constraint_validation_compatible = False
|
||||
|
||||
def __init__(self, expression, name):
|
||||
super().__init__(expression, name=name)
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.core import checks
|
||||
from django.core.exceptions import FieldDoesNotExist, FieldError, ValidationError
|
||||
from django.db import connections
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.expressions import Exists, ExpressionList, F, OrderBy, RawSQL
|
||||
from django.db.models.expressions import Exists, ExpressionList, F, RawSQL
|
||||
from django.db.models.indexes import IndexExpression
|
||||
from django.db.models.lookups import Exact
|
||||
from django.db.models.query_utils import Q
|
||||
@@ -644,9 +644,8 @@ class UniqueConstraint(BaseConstraint):
|
||||
}
|
||||
expressions = []
|
||||
for expr in self.expressions:
|
||||
# Ignore ordering.
|
||||
if isinstance(expr, OrderBy):
|
||||
expr = expr.expression
|
||||
if hasattr(expr, "get_expression_for_validation"):
|
||||
expr = expr.get_expression_for_validation()
|
||||
expressions.append(Exact(expr, expr.replace_expressions(replacements)))
|
||||
queryset = queryset.filter(*expressions)
|
||||
model_class_pk = instance._get_pk_val(model._meta)
|
||||
|
||||
@@ -180,6 +180,8 @@ class BaseExpression:
|
||||
window_compatible = False
|
||||
# Can the expression be used as a database default value?
|
||||
allowed_default = False
|
||||
# Can the expression be used during a constraint validation?
|
||||
constraint_validation_compatible = True
|
||||
|
||||
def __init__(self, output_field=None):
|
||||
if output_field is not None:
|
||||
@@ -484,6 +486,20 @@ class BaseExpression:
|
||||
return self.output_field.select_format(compiler, sql, params)
|
||||
return sql, params
|
||||
|
||||
def get_expression_for_validation(self):
|
||||
# Ignore expressions that cannot be used during a constraint validation.
|
||||
if not getattr(self, "constraint_validation_compatible", True):
|
||||
try:
|
||||
(expression,) = self.get_source_expressions()
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
"Expressions with constraint_validation_compatible set to False "
|
||||
"must have only one source expression."
|
||||
) from e
|
||||
else:
|
||||
return expression
|
||||
return self
|
||||
|
||||
|
||||
@deconstructible
|
||||
class Expression(BaseExpression, Combinable):
|
||||
@@ -1716,6 +1732,7 @@ class Exists(Subquery):
|
||||
class OrderBy(Expression):
|
||||
template = "%(expression)s %(ordering)s"
|
||||
conditional = False
|
||||
constraint_validation_compatible = False
|
||||
|
||||
def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
|
||||
if nulls_first and nulls_last:
|
||||
|
||||
Reference in New Issue
Block a user