Fixed #33004 -- Made saving objects with unsaved GenericForeignKey raise ValueError.
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 556e25510cf21..9aa6d7596ab0b 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1072,8 +1072,9 @@ def _do_insert(self, manager, using, fields, returning_fields, raw):
 
     def _prepare_related_fields_for_save(self, operation_name, fields=None):
         # Ensure that a model instance without a PK hasn't been assigned to
-        # a ForeignKey or OneToOneField on this model. If the field is
-        # nullable, allowing the save would result in silent data loss.
+        # a ForeignKey, GenericForeignKey or OneToOneField on this model. If
+        # the field is nullable, allowing the save would result in silent data
+        # loss.
         for field in self._meta.concrete_fields:
             if fields and field not in fields:
                 continue
@@ -1107,6 +1108,21 @@ def _prepare_related_fields_for_save(self, operation_name, fields=None):
                     self, field.attname
                 ):
                     field.delete_cached_value(self)
+        # GenericForeignKeys are private.
+        for field in self._meta.private_fields:
+            if fields and field not in fields:
+                continue
+            if (
+                field.is_relation
+                and field.is_cached(self)
+                and hasattr(field, "fk_field")
+            ):
+                obj = field.get_cached_value(self, default=None)
+                if obj and obj.pk is None:
+                    raise ValueError(
+                        f"{operation_name}() prohibited to prevent data loss due to "
+                        f"unsaved related object '{field.name}'."
+                    )
 
     def delete(self, using=None, keep_parents=False):
         if self.pk is None:
diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py
index 7c49e218ddb37..1ee14f5381fe7 100644
--- a/tests/generic_relations/tests.py
+++ b/tests/generic_relations/tests.py
@@ -1,8 +1,7 @@
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import FieldError
-from django.db import IntegrityError
 from django.db.models import Q
-from django.test import SimpleTestCase, TestCase
+from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
 
 from .models import (
     AllowsNullGFK,
@@ -501,14 +500,26 @@ def test_query_content_type(self):
         with self.assertRaisesMessage(FieldError, msg):
             TaggedItem.objects.get(content_object="")
 
-    def test_unsaved_instance_on_generic_foreign_key(self):
-        """
-        Assigning an unsaved object to GenericForeignKey should raise an
-        exception on model.save().
-        """
+    def test_unsaved_generic_foreign_key_parent_save(self):
+        quartz = Mineral(name="Quartz", hardness=7)
+        tagged_item = TaggedItem(tag="shiny", content_object=quartz)
+        msg = (
+            "save() prohibited to prevent data loss due to unsaved related object "
+            "'content_object'."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
+            tagged_item.save()
+
+    @skipUnlessDBFeature("has_bulk_insert")
+    def test_unsaved_generic_foreign_key_parent_bulk_create(self):
         quartz = Mineral(name="Quartz", hardness=7)
-        with self.assertRaises(IntegrityError):
-            TaggedItem.objects.create(tag="shiny", content_object=quartz)
+        tagged_item = TaggedItem(tag="shiny", content_object=quartz)
+        msg = (
+            "bulk_create() prohibited to prevent data loss due to unsaved related "
+            "object 'content_object'."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
+            TaggedItem.objects.bulk_create([tagged_item])
 
     def test_cache_invalidation_for_content_type_id(self):
         # Create a Vegetable and Mineral with the same id.
diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py
index a028ff01d80e9..dc55b2a83b3c9 100644
--- a/tests/generic_relations_regress/models.py
+++ b/tests/generic_relations_regress/models.py
@@ -104,11 +104,6 @@ class Company(models.Model):
     links = GenericRelation(Link)
 
 
-# For testing #13085 fix, we also use Note model defined above
-class Developer(models.Model):
-    name = models.CharField(max_length=15)
-
-
 class Team(models.Model):
     name = models.CharField(max_length=15)
 
diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py
index 16705131a70e6..6c708fefbbafd 100644
--- a/tests/generic_relations_regress/tests.py
+++ b/tests/generic_relations_regress/tests.py
@@ -1,4 +1,3 @@
-from django.db import IntegrityError
 from django.db.models import ProtectedError, Q, Sum
 from django.forms.models import modelform_factory
 from django.test import TestCase, skipIfDBFeature
@@ -15,7 +14,6 @@
     Contact,
     Content,
     D,
-    Developer,
     Guild,
     HasLinkThing,
     Link,
@@ -140,14 +138,6 @@ def count_places(place):
         self.assertEqual(count_places(p1), 1)
         self.assertEqual(count_places(p2), 1)
 
-    def test_target_model_is_unsaved(self):
-        """Test related to #13085"""
-        # Fails with another, ORM-level error
-        dev1 = Developer(name="Joe")
-        note = Note(note="Deserves promotion", content_object=dev1)
-        with self.assertRaises(IntegrityError):
-            note.save()
-
     def test_target_model_len_zero(self):
         """
         Saving a model with a GenericForeignKey to a model instance whose

Tracker ticket: https://code.djangoproject.com/ticket/33004
GFK fields would raise an IntegrityError on save/bulk_create when they have an unsaved related model
Changed to a ValueError to allign with the behaviour of OneToOneField and ForeignKey fields.

Thank you @jonnythebard  for the initial patch: https://github.com/django/django/pull/14807 :muscle: 

Tried to address the review comments