mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	[5.1.x] Fixed #35594 -- Added unique nulls distinct validation for expressions.
Thanks Mark Gensler for the report.
Backport of adc0b6aac3 from main.
			
			
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						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.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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user