mirror of
https://github.com/django/django.git
synced 2025-04-01 03:56:42 +00:00
[5.1.x] Fixed #35594 -- Added unique nulls distinct validation for expressions.
Thanks Mark Gensler for the report. Backport of adc0b6aac3f8a5c96e1ca282bc9f46e28d20281c from main.
This commit is contained in:
parent
a2791f5ea2
commit
4d8e574379
@ -8,7 +8,7 @@ from django.db import connections
|
|||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.expressions import Exists, ExpressionList, F, RawSQL
|
from django.db.models.expressions import Exists, ExpressionList, F, RawSQL
|
||||||
from django.db.models.indexes import IndexExpression
|
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.query_utils import Q
|
||||||
from django.db.models.sql.query import Query
|
from django.db.models.sql.query import Query
|
||||||
from django.db.utils import DEFAULT_DB_ALIAS
|
from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
@ -642,12 +642,16 @@ class UniqueConstraint(BaseConstraint):
|
|||||||
meta=model._meta, exclude=exclude
|
meta=model._meta, exclude=exclude
|
||||||
).items()
|
).items()
|
||||||
}
|
}
|
||||||
expressions = []
|
filters = []
|
||||||
for expr in self.expressions:
|
for expr in self.expressions:
|
||||||
if hasattr(expr, "get_expression_for_validation"):
|
if hasattr(expr, "get_expression_for_validation"):
|
||||||
expr = expr.get_expression_for_validation()
|
expr = expr.get_expression_for_validation()
|
||||||
expressions.append(Exact(expr, expr.replace_expressions(replacements)))
|
rhs = expr.replace_expressions(replacements)
|
||||||
queryset = queryset.filter(*expressions)
|
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)
|
model_class_pk = instance._get_pk_val(model._meta)
|
||||||
if not instance._state.adding and model_class_pk is not None:
|
if not instance._state.adding and model_class_pk is not None:
|
||||||
queryset = queryset.exclude(pk=model_class_pk)
|
queryset = queryset.exclude(pk=model_class_pk)
|
||||||
|
@ -9,4 +9,5 @@ Django 5.0.8 fixes several bugs in 5.0.7.
|
|||||||
Bugfixes
|
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 import IntegrityError, connection, models
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.db.models.constraints import BaseConstraint, UniqueConstraint
|
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.db.transaction import atomic
|
||||||
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||||
from django.test.utils import ignore_warnings
|
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=4, discounted_price=3))
|
||||||
is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1))
|
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):
|
def test_name(self):
|
||||||
constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
|
constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
|
||||||
expected_name = "name_color_uniq"
|
expected_name = "name_color_uniq"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user