diff --git a/django/db/models/base.py b/django/db/models/base.py index 6eaa600f107b..4a9150bf37fa 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -781,7 +781,11 @@ def save( if force_insert and (force_update or update_fields): raise ValueError("Cannot force both insert and updating in model saving.") - deferred_fields = self.get_deferred_fields() + deferred_non_generated_fields = { + f.attname + for f in self._meta.concrete_fields + if f.attname not in self.__dict__ and f.generated is False + } if update_fields is not None: # If update_fields is empty, skip the save. We do also check for # no-op saves later on for inheritance cases. This bailout is @@ -802,12 +806,16 @@ def save( # If saving to the same database, and this model is deferred, then # automatically do an "update_fields" save on the loaded fields. - elif not force_insert and deferred_fields and using == self._state.db: + elif ( + not force_insert + and deferred_non_generated_fields + and using == self._state.db + ): field_names = set() for field in self._meta.concrete_fields: if not field.primary_key and not hasattr(field, "through"): field_names.add(field.attname) - loaded_fields = field_names.difference(deferred_fields) + loaded_fields = field_names.difference(deferred_non_generated_fields) if loaded_fields: update_fields = frozenset(loaded_fields) diff --git a/docs/releases/5.0.1.txt b/docs/releases/5.0.1.txt index a8b886c6e2f4..6b0b6de66cbf 100644 --- a/docs/releases/5.0.1.txt +++ b/docs/releases/5.0.1.txt @@ -16,3 +16,7 @@ Bugfixes * Fixed a long standing bug in handling the ``RETURNING INTO`` clause that caused a crash when creating a model instance with a ``GeneratedField`` which ``output_field`` had backend-specific converters (:ticket:`35024`). + +* Fixed a regression in Django 5.0 that caused a crash of ``Model.save()`` for + models with both ``GeneratedField`` and ``ForeignKey`` fields + (:ticket:`35019`). diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index aea02964e2d1..69b4e26145ce 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -502,6 +502,7 @@ class GeneratedModel(models.Model): output_field=models.IntegerField(), db_persist=True, ) + fk = models.ForeignKey(Foo, on_delete=models.CASCADE, null=True) class Meta: required_db_features = {"supports_stored_generated_columns"} @@ -515,6 +516,7 @@ class GeneratedModelVirtual(models.Model): output_field=models.IntegerField(), db_persist=False, ) + fk = models.ForeignKey(Foo, on_delete=models.CASCADE, null=True) class Meta: required_db_features = {"supports_virtual_generated_columns"} diff --git a/tests/model_fields/test_generatedfield.py b/tests/model_fields/test_generatedfield.py index 056a80c29430..589f78cbb042 100644 --- a/tests/model_fields/test_generatedfield.py +++ b/tests/model_fields/test_generatedfield.py @@ -1,4 +1,5 @@ import uuid +from decimal import Decimal from django.apps import apps from django.db import IntegrityError, connection @@ -15,6 +16,7 @@ from django.test.utils import isolate_apps from .models import ( + Foo, GeneratedModel, GeneratedModelFieldWithConverters, GeneratedModelNull, @@ -187,6 +189,19 @@ def test_save(self): m.refresh_from_db() self.assertEqual(m.field, 8) + def test_save_model_with_foreign_key(self): + fk_object = Foo.objects.create(a="abc", d=Decimal("12.34")) + m = self.base_model(a=1, b=2, fk=fk_object) + m.save() + m = self._refresh_if_needed(m) + self.assertEqual(m.field, 3) + + def test_generated_fields_can_be_deferred(self): + fk_object = Foo.objects.create(a="abc", d=Decimal("12.34")) + m = self.base_model.objects.create(a=1, b=2, fk=fk_object) + m = self.base_model.objects.defer("field").get(id=m.id) + self.assertEqual(m.get_deferred_fields(), {"field"}) + def test_update(self): m = self.base_model.objects.create(a=1, b=2) self.base_model.objects.update(b=3)