Skip to content

Commit

Permalink
Fixed #35019 -- Fixed save() on models with both GeneratedFields and …
Browse files Browse the repository at this point in the history
…ForeignKeys.

Thanks Deb Kumar Das for the report.

Regression in f333e35.
  • Loading branch information
sarahboyce authored and felixxm committed Dec 8, 2023
1 parent eeb2119 commit b287af5
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 3 deletions.
14 changes: 11 additions & 3 deletions django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions docs/releases/5.0.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
2 changes: 2 additions & 0 deletions tests/model_fields/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand All @@ -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"}
Expand Down
15 changes: 15 additions & 0 deletions tests/model_fields/test_generatedfield.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from decimal import Decimal

from django.apps import apps
from django.db import IntegrityError, connection
Expand All @@ -15,6 +16,7 @@
from django.test.utils import isolate_apps

from .models import (
Foo,
GeneratedModel,
GeneratedModelFieldWithConverters,
GeneratedModelNull,
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b287af5

Please sign in to comment.