mirror of https://github.com/django/django.git
Fixed #35594 -- Added unique nulls distinct validation for expressions.
Thanks Mark Gensler for the report.
This commit is contained in:
parent
13922580cc
commit
adc0b6aac3
|
@ -8,7 +8,7 @@ from django.db import connections
|
|||
from django.db.models.constants import LOOKUP_SEP
|
||||
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.lookups import Exact, IsNull
|
||||
from django.db.models.query_utils import Q
|
||||
from django.db.models.sql.query import Query
|
||||
from django.db.utils import DEFAULT_DB_ALIAS
|
||||
|
@ -642,12 +642,16 @@ class UniqueConstraint(BaseConstraint):
|
|||
meta=model._meta, exclude=exclude
|
||||
).items()
|
||||
}
|
||||
expressions = []
|
||||
filters = []
|
||||
for expr in self.expressions:
|
||||
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)
|
||||
rhs = expr.replace_expressions(replacements)
|
||||
condition = Exact(expr, rhs)
|
||||
if self.nulls_distinct is False:
|
||||
condition = Q(condition) | Q(IsNull(expr, True), IsNull(rhs, True))
|
||||
filters.append(condition)
|
||||
queryset = queryset.filter(*filters)
|
||||
model_class_pk = instance._get_pk_val(model._meta)
|
||||
if not instance._state.adding and model_class_pk is not None:
|
||||
queryset = queryset.exclude(pk=model_class_pk)
|
||||
|
|
|
@ -9,4 +9,5 @@ Django 5.0.8 fixes several bugs in 5.0.7.
|
|||
Bugfixes
|
||||
========
|
||||
|
||||
* ...
|
||||
* Added missing validation for ``UniqueConstraint(nulls_distinct=False)`` when
|
||||
using ``*expressions`` (:ticket:`35594`).
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
|
|||
from django.db import IntegrityError, connection, models
|
||||
from django.db.models import F
|
||||
from django.db.models.constraints import BaseConstraint, UniqueConstraint
|
||||
from django.db.models.functions import Lower
|
||||
from django.db.models.functions import Abs, Lower
|
||||
from django.db.transaction import atomic
|
||||
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
from django.test.utils import ignore_warnings
|
||||
|
@ -1070,6 +1070,19 @@ class UniqueConstraintTests(TestCase):
|
|||
is_not_null_constraint.validate(Product, Product(price=4, discounted_price=3))
|
||||
is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1))
|
||||
|
||||
def test_validate_nulls_distinct_expressions(self):
|
||||
Product.objects.create(price=42)
|
||||
constraint = models.UniqueConstraint(
|
||||
Abs("price"),
|
||||
nulls_distinct=False,
|
||||
name="uniq_prices_nulls_distinct",
|
||||
)
|
||||
constraint.validate(Product, Product(price=None))
|
||||
Product.objects.create(price=None)
|
||||
msg = f"Constraint “{constraint.name}” is violated."
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
constraint.validate(Product, Product(price=None))
|
||||
|
||||
def test_name(self):
|
||||
constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
|
||||
expected_name = "name_color_uniq"
|
||||
|
|
Loading…
Reference in New Issue