mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #35560 -- Made Model.full_clean() ignore GeneratedFields for constraints.
Accessing generated field values on unsaved models caused a crash when validating CheckConstraints and UniqueConstraints with expressions.
This commit is contained in:
parent
53e674d574
commit
1005c2abd1
@ -1340,7 +1340,7 @@ class Model(AltersData, metaclass=ModelBase):
|
||||
field_map = {
|
||||
field.name: Value(getattr(self, field.attname), field)
|
||||
for field in meta.local_concrete_fields
|
||||
if field.name not in exclude
|
||||
if field.name not in exclude and not field.generated
|
||||
}
|
||||
if "pk" not in exclude:
|
||||
field_map["pk"] = Value(self.pk, meta.pk)
|
||||
|
@ -10,4 +10,7 @@ issues with severity "low", and several bugs in 5.0.6.
|
||||
Bugfixes
|
||||
========
|
||||
|
||||
* ...
|
||||
* Fixed a bug in Django 5.0 that caused a crash of ``Model.full_clean()`` on
|
||||
unsaved model instances with a ``GeneratedField`` and certain defined
|
||||
:attr:`Meta.constraints <django.db.models.Options.constraints>`
|
||||
(:ticket:`35560`).
|
||||
|
@ -609,3 +609,79 @@ class GeneratedModelNullVirtual(models.Model):
|
||||
|
||||
class Meta:
|
||||
required_db_features = {"supports_virtual_generated_columns"}
|
||||
|
||||
|
||||
class GeneratedModelBase(models.Model):
|
||||
a = models.IntegerField()
|
||||
a_squared = models.GeneratedField(
|
||||
expression=F("a") * F("a"),
|
||||
output_field=models.IntegerField(),
|
||||
db_persist=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class GeneratedModelVirtualBase(models.Model):
|
||||
a = models.IntegerField()
|
||||
a_squared = models.GeneratedField(
|
||||
expression=F("a") * F("a"),
|
||||
output_field=models.IntegerField(),
|
||||
db_persist=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class GeneratedModelCheckConstraint(GeneratedModelBase):
|
||||
class Meta:
|
||||
required_db_features = {
|
||||
"supports_stored_generated_columns",
|
||||
"supports_table_check_constraints",
|
||||
}
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
condition=models.Q(a__gt=0),
|
||||
name="Generated model check constraint a > 0",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class GeneratedModelCheckConstraintVirtual(GeneratedModelVirtualBase):
|
||||
class Meta:
|
||||
required_db_features = {
|
||||
"supports_virtual_generated_columns",
|
||||
"supports_table_check_constraints",
|
||||
}
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
condition=models.Q(a__gt=0),
|
||||
name="Generated model check constraint virtual a > 0",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class GeneratedModelUniqueConstraint(GeneratedModelBase):
|
||||
class Meta:
|
||||
required_db_features = {
|
||||
"supports_stored_generated_columns",
|
||||
"supports_table_check_constraints",
|
||||
}
|
||||
constraints = [
|
||||
models.UniqueConstraint(F("a"), name="Generated model unique constraint a"),
|
||||
]
|
||||
|
||||
|
||||
class GeneratedModelUniqueConstraintVirtual(GeneratedModelVirtualBase):
|
||||
class Meta:
|
||||
required_db_features = {
|
||||
"supports_virtual_generated_columns",
|
||||
"supports_expression_indexes",
|
||||
}
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
F("a"), name="Generated model unique constraint virtual a"
|
||||
),
|
||||
]
|
||||
|
@ -2,6 +2,7 @@ import uuid
|
||||
from decimal import Decimal
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError, connection
|
||||
from django.db.models import (
|
||||
CharField,
|
||||
@ -18,6 +19,8 @@ from django.test.utils import isolate_apps
|
||||
from .models import (
|
||||
Foo,
|
||||
GeneratedModel,
|
||||
GeneratedModelCheckConstraint,
|
||||
GeneratedModelCheckConstraintVirtual,
|
||||
GeneratedModelFieldWithConverters,
|
||||
GeneratedModelNull,
|
||||
GeneratedModelNullVirtual,
|
||||
@ -25,6 +28,8 @@ from .models import (
|
||||
GeneratedModelOutputFieldDbCollationVirtual,
|
||||
GeneratedModelParams,
|
||||
GeneratedModelParamsVirtual,
|
||||
GeneratedModelUniqueConstraint,
|
||||
GeneratedModelUniqueConstraintVirtual,
|
||||
GeneratedModelVirtual,
|
||||
)
|
||||
|
||||
@ -186,6 +191,42 @@ class GeneratedFieldTestMixin:
|
||||
m = self._refresh_if_needed(m)
|
||||
self.assertEqual(m.field, 3)
|
||||
|
||||
@skipUnlessDBFeature("supports_table_check_constraints")
|
||||
def test_full_clean_with_check_constraint(self):
|
||||
model_name = self.check_constraint_model._meta.verbose_name.capitalize()
|
||||
|
||||
m = self.check_constraint_model(a=2)
|
||||
m.full_clean()
|
||||
m.save()
|
||||
m = self._refresh_if_needed(m)
|
||||
self.assertEqual(m.a_squared, 4)
|
||||
|
||||
m = self.check_constraint_model(a=-1)
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
m.full_clean()
|
||||
self.assertEqual(
|
||||
cm.exception.message_dict,
|
||||
{"__all__": [f"Constraint “{model_name} a > 0” is violated."]},
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_full_clean_with_unique_constraint_expression(self):
|
||||
model_name = self.unique_constraint_model._meta.verbose_name.capitalize()
|
||||
|
||||
m = self.unique_constraint_model(a=2)
|
||||
m.full_clean()
|
||||
m.save()
|
||||
m = self._refresh_if_needed(m)
|
||||
self.assertEqual(m.a_squared, 4)
|
||||
|
||||
m = self.unique_constraint_model(a=2)
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
m.full_clean()
|
||||
self.assertEqual(
|
||||
cm.exception.message_dict,
|
||||
{"__all__": [f"Constraint “{model_name} a” is violated."]},
|
||||
)
|
||||
|
||||
def test_create(self):
|
||||
m = self.base_model.objects.create(a=1, b=2)
|
||||
m = self._refresh_if_needed(m)
|
||||
@ -305,6 +346,8 @@ class GeneratedFieldTestMixin:
|
||||
class StoredGeneratedFieldTests(GeneratedFieldTestMixin, TestCase):
|
||||
base_model = GeneratedModel
|
||||
nullable_model = GeneratedModelNull
|
||||
check_constraint_model = GeneratedModelCheckConstraint
|
||||
unique_constraint_model = GeneratedModelUniqueConstraint
|
||||
output_field_db_collation_model = GeneratedModelOutputFieldDbCollation
|
||||
params_model = GeneratedModelParams
|
||||
|
||||
@ -318,5 +361,7 @@ class StoredGeneratedFieldTests(GeneratedFieldTestMixin, TestCase):
|
||||
class VirtualGeneratedFieldTests(GeneratedFieldTestMixin, TestCase):
|
||||
base_model = GeneratedModelVirtual
|
||||
nullable_model = GeneratedModelNullVirtual
|
||||
check_constraint_model = GeneratedModelCheckConstraintVirtual
|
||||
unique_constraint_model = GeneratedModelUniqueConstraintVirtual
|
||||
output_field_db_collation_model = GeneratedModelOutputFieldDbCollationVirtual
|
||||
params_model = GeneratedModelParamsVirtual
|
||||
|
Loading…
Reference in New Issue
Block a user