[4.0.x] Fixed #33335 -- Made model validation ignore functional unique constraints.
Regression in3aa545281e
. Thanks Hervé Le Roy for the report. Backport of1eaf38fa87
from main
This commit is contained in:
parent
7bde53a7ae
commit
fed7f992ac
|
@ -870,7 +870,11 @@ class Options:
|
||||||
return [
|
return [
|
||||||
constraint
|
constraint
|
||||||
for constraint in self.constraints
|
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
|
@cached_property
|
||||||
|
|
|
@ -35,10 +35,12 @@ option.
|
||||||
not raise ``ValidationError``\s. Rather you'll get a database integrity
|
not raise ``ValidationError``\s. Rather you'll get a database integrity
|
||||||
error on ``save()``. ``UniqueConstraint``\s without a
|
error on ``save()``. ``UniqueConstraint``\s without a
|
||||||
:attr:`~UniqueConstraint.condition` (i.e. non-partial unique constraints)
|
:attr:`~UniqueConstraint.condition` (i.e. non-partial unique constraints)
|
||||||
are different in this regard, in that they leverage the existing
|
and :attr:`~UniqueConstraint.expressions` (i.e. non-functional unique
|
||||||
``validate_unique()`` logic, and thus enable two-stage validation. In
|
constraints) are different in this regard, in that they leverage the
|
||||||
addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is also
|
existing ``validate_unique()`` logic, and thus enable two-stage validation.
|
||||||
raised during model validation when the ``UniqueConstraint`` is violated.
|
In addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is
|
||||||
|
also raised during model validation when the ``UniqueConstraint`` is
|
||||||
|
violated.
|
||||||
|
|
||||||
``CheckConstraint``
|
``CheckConstraint``
|
||||||
===================
|
===================
|
||||||
|
|
|
@ -2,6 +2,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.functions import Lower
|
||||||
|
|
||||||
|
|
||||||
def validate_answer_to_universe(value):
|
def validate_answer_to_universe(value):
|
||||||
|
@ -125,3 +126,13 @@ class GenericIPAddressTestModel(models.Model):
|
||||||
|
|
||||||
class GenericIPAddrUnpackUniqueTest(models.Model):
|
class GenericIPAddrUnpackUniqueTest(models.Model):
|
||||||
generic_v4unpack_ip = models.GenericIPAddressField(null=True, blank=True, unique=True, unpack_ipv4=True)
|
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'),
|
||||||
|
]
|
||||||
|
|
|
@ -8,7 +8,8 @@ from django.test import TestCase
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
CustomPKModel, FlexibleDatePost, ModelToValidate, Post, UniqueErrorsModel,
|
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')
|
), 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):
|
class PerformUniqueChecksTest(TestCase):
|
||||||
def test_primary_key_unique_check_not_performed_when_adding_and_pk_not_specified(self):
|
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 = ModelToValidate(number=10, name='Some Name')
|
||||||
mtv.full_clean()
|
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):
|
def test_unique_for_date(self):
|
||||||
Post.objects.create(
|
Post.objects.create(
|
||||||
title="Django 1.0 is released", slug="Django 1.0",
|
title="Django 1.0 is released", slug="Django 1.0",
|
||||||
|
|
Loading…
Reference in New Issue