From 03af8fbd0f1cfc08edadda248cf5d3498e8ed2f7 Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Wed, 6 Dec 2023 23:19:09 +0100
Subject: [PATCH] [5.0.x] Fixed #35019 -- Fixed save() on models with both
 GeneratedFields and ForeignKeys.

Thanks Deb Kumar Das for the report.

Regression in f333e3513e8bdf5ffeb6eeb63021c230082e6f95.

Backport of b287af5dc954628d4b336aefc5027b2edceee64b from main
---
 django/db/models/base.py                  | 14 +++++++++++---
 docs/releases/5.0.1.txt                   |  4 ++++
 tests/model_fields/models.py              |  2 ++
 tests/model_fields/test_generatedfield.py | 15 +++++++++++++++
 4 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/django/db/models/base.py b/django/db/models/base.py
index 80503d118a..c3f6b32a5e 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -781,7 +781,11 @@ class Model(AltersData, metaclass=ModelBase):
         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 @@ class Model(AltersData, metaclass=ModelBase):
 
         # 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 a8b886c6e2..6b0b6de66c 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 aea02964e2..69b4e26145 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 056a80c294..589f78cbb0 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 import SimpleTestCase, TestCase, skipUnlessDBFeature
 from django.test.utils import isolate_apps
 
 from .models import (
+    Foo,
     GeneratedModel,
     GeneratedModelFieldWithConverters,
     GeneratedModelNull,
@@ -187,6 +189,19 @@ class GeneratedFieldTestMixin:
         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)