mirror of
https://github.com/django/django.git
synced 2025-08-04 10:59:45 +00:00
Fixed #35575 -- Added support for constraint validation on GeneratedFields.
This commit is contained in:
parent
f883bef054
commit
228128618b
7 changed files with 273 additions and 44 deletions
|
@ -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 Abs, Lower, Upper
|
||||
from django.db.models.functions import Abs, Lower, Sqrt, Upper
|
||||
from django.db.transaction import atomic
|
||||
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
from django.test.utils import ignore_warnings
|
||||
|
@ -13,6 +13,8 @@ from django.utils.deprecation import RemovedInDjango60Warning
|
|||
from .models import (
|
||||
ChildModel,
|
||||
ChildUniqueConstraintProduct,
|
||||
GeneratedFieldStoredProduct,
|
||||
GeneratedFieldVirtualProduct,
|
||||
JSONFieldModel,
|
||||
ModelWithDatabaseDefault,
|
||||
Product,
|
||||
|
@ -384,6 +386,29 @@ class CheckConstraintTests(TestCase):
|
|||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data))
|
||||
|
||||
@skipUnlessDBFeature("supports_stored_generated_columns")
|
||||
def test_validate_generated_field_stored(self):
|
||||
self.assertGeneratedFieldIsValidated(model=GeneratedFieldStoredProduct)
|
||||
|
||||
@skipUnlessDBFeature("supports_virtual_generated_columns")
|
||||
def test_validate_generated_field_virtual(self):
|
||||
self.assertGeneratedFieldIsValidated(model=GeneratedFieldVirtualProduct)
|
||||
|
||||
def assertGeneratedFieldIsValidated(self, model):
|
||||
constraint = models.CheckConstraint(
|
||||
condition=models.Q(rebate__range=(0, 100)), name="bounded_rebate"
|
||||
)
|
||||
constraint.validate(model, model(price=50, discounted_price=20))
|
||||
|
||||
invalid_product = model(price=1200, discounted_price=500)
|
||||
msg = f"Constraint “{constraint.name}” is violated."
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
constraint.validate(model, invalid_product)
|
||||
|
||||
# Excluding referenced or generated fields should skip validation.
|
||||
constraint.validate(model, invalid_product, exclude={"price"})
|
||||
constraint.validate(model, invalid_product, exclude={"rebate"})
|
||||
|
||||
def test_check_deprecation(self):
|
||||
msg = "CheckConstraint.check is deprecated in favor of `.condition`."
|
||||
condition = models.Q(foo="bar")
|
||||
|
@ -1062,6 +1087,90 @@ class UniqueConstraintTests(TestCase):
|
|||
exclude={"name"},
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_stored_generated_columns")
|
||||
def test_validate_expression_generated_field_stored(self):
|
||||
self.assertGeneratedFieldWithExpressionIsValidated(
|
||||
model=GeneratedFieldStoredProduct
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_virtual_generated_columns")
|
||||
def test_validate_expression_generated_field_virtual(self):
|
||||
self.assertGeneratedFieldWithExpressionIsValidated(
|
||||
model=GeneratedFieldVirtualProduct
|
||||
)
|
||||
|
||||
def assertGeneratedFieldWithExpressionIsValidated(self, model):
|
||||
constraint = UniqueConstraint(Sqrt("rebate"), name="unique_rebate_sqrt")
|
||||
model.objects.create(price=100, discounted_price=84)
|
||||
|
||||
valid_product = model(price=100, discounted_price=75)
|
||||
constraint.validate(model, valid_product)
|
||||
|
||||
invalid_product = model(price=20, discounted_price=4)
|
||||
with self.assertRaisesMessage(
|
||||
ValidationError, f"Constraint “{constraint.name}” is violated."
|
||||
):
|
||||
constraint.validate(model, invalid_product)
|
||||
|
||||
# Excluding referenced or generated fields should skip validation.
|
||||
constraint.validate(model, invalid_product, exclude={"rebate"})
|
||||
constraint.validate(model, invalid_product, exclude={"price"})
|
||||
|
||||
@skipUnlessDBFeature("supports_stored_generated_columns")
|
||||
def test_validate_fields_generated_field_stored(self):
|
||||
self.assertGeneratedFieldWithFieldsIsValidated(
|
||||
model=GeneratedFieldStoredProduct
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_virtual_generated_columns")
|
||||
def test_validate_fields_generated_field_virtual(self):
|
||||
self.assertGeneratedFieldWithFieldsIsValidated(
|
||||
model=GeneratedFieldVirtualProduct
|
||||
)
|
||||
|
||||
def assertGeneratedFieldWithFieldsIsValidated(self, model):
|
||||
constraint = models.UniqueConstraint(
|
||||
fields=["lower_name"], name="lower_name_unique"
|
||||
)
|
||||
model.objects.create(name="Box")
|
||||
constraint.validate(model, model(name="Case"))
|
||||
|
||||
invalid_product = model(name="BOX")
|
||||
msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
constraint.validate(model, invalid_product)
|
||||
|
||||
# Excluding referenced or generated fields should skip validation.
|
||||
constraint.validate(model, invalid_product, exclude={"lower_name"})
|
||||
constraint.validate(model, invalid_product, exclude={"name"})
|
||||
|
||||
@skipUnlessDBFeature("supports_stored_generated_columns")
|
||||
def test_validate_fields_generated_field_stored_nulls_distinct(self):
|
||||
self.assertGeneratedFieldNullsDistinctIsValidated(
|
||||
model=GeneratedFieldStoredProduct
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_virtual_generated_columns")
|
||||
def test_validate_fields_generated_field_virtual_nulls_distinct(self):
|
||||
self.assertGeneratedFieldNullsDistinctIsValidated(
|
||||
model=GeneratedFieldVirtualProduct
|
||||
)
|
||||
|
||||
def assertGeneratedFieldNullsDistinctIsValidated(self, model):
|
||||
constraint = models.UniqueConstraint(
|
||||
fields=["lower_name"],
|
||||
name="lower_name_unique_nulls_distinct",
|
||||
nulls_distinct=False,
|
||||
)
|
||||
model.objects.create(name=None)
|
||||
valid_product = model(name="Box")
|
||||
constraint.validate(model, valid_product)
|
||||
|
||||
invalid_product = model(name=None)
|
||||
msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
constraint.validate(model, invalid_product)
|
||||
|
||||
@skipUnlessDBFeature("supports_table_check_constraints")
|
||||
def test_validate_nullable_textfield_with_isnull_true(self):
|
||||
is_null_constraint = models.UniqueConstraint(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue