mirror of
https://github.com/django/django.git
synced 2025-03-09 17:02:43 +00:00
[5.0.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
c30669821b
commit
fe9bf0cef5
@ -6,7 +6,7 @@ from django.core.exceptions import FieldError, ValidationError
|
||||
from django.db import connections
|
||||
from django.db.models.expressions import Exists, ExpressionList, F, OrderBy
|
||||
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
|
||||
@ -427,13 +427,17 @@ class UniqueConstraint(BaseConstraint):
|
||||
meta=model._meta, exclude=exclude
|
||||
).items()
|
||||
}
|
||||
expressions = []
|
||||
filters = []
|
||||
for expr in self.expressions:
|
||||
# Ignore ordering.
|
||||
if isinstance(expr, OrderBy):
|
||||
expr = expr.expression
|
||||
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
|
||||
@ -1039,6 +1039,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…
x
Reference in New Issue
Block a user