summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHannes Ljungberg <hannes.ljungberg@gmail.com>2021-12-04 21:03:38 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-12-06 13:28:54 +0100
commitfed7f992ac8b2ccb5164761a609277513f10d963 (patch)
tree9b89fdfb70cd12bce9c5f1201113cb8417ddea5a
parent7bde53a7ae7db5b97bcc3ef8eed2a7195cc0f258 (diff)
[4.0.x] Fixed #33335 -- Made model validation ignore functional unique constraints.
Regression in 3aa545281e0c0f9fac93753e3769df9e0334dbaa. Thanks Hervé Le Roy for the report. Backport of 1eaf38fa87384fe26d1abf6e389d6df1600d4d8c from main
-rw-r--r--django/db/models/options.py6
-rw-r--r--docs/ref/models/constraints.txt10
-rw-r--r--tests/validation/models.py11
-rw-r--r--tests/validation/test_unique.py14
4 files changed, 35 insertions, 6 deletions
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 4028e05b99..41d79e2f9b 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -870,7 +870,11 @@ class Options:
return [
constraint
for constraint in self.constraints
- if isinstance(constraint, UniqueConstraint) and constraint.condition is None
+ if (
+ isinstance(constraint, UniqueConstraint) and
+ constraint.condition is None and
+ not constraint.contains_expressions
+ )
]
@cached_property
diff --git a/docs/ref/models/constraints.txt b/docs/ref/models/constraints.txt
index c675903db6..dd49b1cafc 100644
--- a/docs/ref/models/constraints.txt
+++ b/docs/ref/models/constraints.txt
@@ -35,10 +35,12 @@ option.
not raise ``ValidationError``\s. Rather you'll get a database integrity
error on ``save()``. ``UniqueConstraint``\s without a
:attr:`~UniqueConstraint.condition` (i.e. non-partial unique constraints)
- are different in this regard, in that they leverage the existing
- ``validate_unique()`` logic, and thus enable two-stage validation. In
- addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is also
- raised during model validation when the ``UniqueConstraint`` is violated.
+ and :attr:`~UniqueConstraint.expressions` (i.e. non-functional unique
+ constraints) are different in this regard, in that they leverage the
+ existing ``validate_unique()`` logic, and thus enable two-stage validation.
+ In addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is
+ also raised during model validation when the ``UniqueConstraint`` is
+ violated.
``CheckConstraint``
===================
diff --git a/tests/validation/models.py b/tests/validation/models.py
index 3a5a9cd354..b2d705aed2 100644
--- a/tests/validation/models.py
+++ b/tests/validation/models.py
@@ -2,6 +2,7 @@ from datetime import datetime
from django.core.exceptions import ValidationError
from django.db import models
+from django.db.models.functions import Lower
def validate_answer_to_universe(value):
@@ -125,3 +126,13 @@ class GenericIPAddressTestModel(models.Model):
class GenericIPAddrUnpackUniqueTest(models.Model):
generic_v4unpack_ip = models.GenericIPAddressField(null=True, blank=True, unique=True, unpack_ipv4=True)
+
+
+class UniqueFuncConstraintModel(models.Model):
+ field = models.CharField(max_length=255)
+
+ class Meta:
+ required_db_features = {'supports_expression_indexes'}
+ constraints = [
+ models.UniqueConstraint(Lower('field'), name='func_lower_field_uq'),
+ ]
diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py
index 88eb94a54e..8bf4ca2122 100644
--- a/tests/validation/test_unique.py
+++ b/tests/validation/test_unique.py
@@ -8,7 +8,8 @@ from django.test import TestCase
from .models import (
CustomPKModel, FlexibleDatePost, ModelToValidate, Post, UniqueErrorsModel,
- UniqueFieldsModel, UniqueForDateModel, UniqueTogetherModel,
+ UniqueFieldsModel, UniqueForDateModel, UniqueFuncConstraintModel,
+ UniqueTogetherModel,
)
@@ -86,6 +87,13 @@ class GetUniqueCheckTests(unittest.TestCase):
), m._get_unique_checks(exclude='start_date')
)
+ def test_func_unique_constraint_ignored(self):
+ m = UniqueFuncConstraintModel()
+ self.assertEqual(
+ m._get_unique_checks(),
+ ([(UniqueFuncConstraintModel, ('id',))], []),
+ )
+
class PerformUniqueChecksTest(TestCase):
def test_primary_key_unique_check_not_performed_when_adding_and_pk_not_specified(self):
@@ -108,6 +116,10 @@ class PerformUniqueChecksTest(TestCase):
mtv = ModelToValidate(number=10, name='Some Name')
mtv.full_clean()
+ def test_func_unique_check_not_performed(self):
+ with self.assertNumQueries(0):
+ UniqueFuncConstraintModel(field='some name').full_clean()
+
def test_unique_for_date(self):
Post.objects.create(
title="Django 1.0 is released", slug="Django 1.0",