summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/base.py2
-rw-r--r--docs/releases/5.0.7.txt5
-rw-r--r--tests/model_fields/models.py76
-rw-r--r--tests/model_fields/test_generatedfield.py45
4 files changed, 126 insertions, 2 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 97fcdb81a4..b118b675cc 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -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)
diff --git a/docs/releases/5.0.7.txt b/docs/releases/5.0.7.txt
index a0e3acd640..0bee6d57ce 100644
--- a/docs/releases/5.0.7.txt
+++ b/docs/releases/5.0.7.txt
@@ -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`).
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index 652c808b40..d9811ba164 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -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"
+ ),
+ ]
diff --git a/tests/model_fields/test_generatedfield.py b/tests/model_fields/test_generatedfield.py
index 2fbfe3c82a..c185e19d8b 100644
--- a/tests/model_fields/test_generatedfield.py
+++ b/tests/model_fields/test_generatedfield.py
@@ -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