Browse Source

[5.0.x] Fixed #35238 -- Fixed database serialization crash when base managers use prefetch_related().

Regression in 139135627650ed6aaaf4c755b82c3bd43f2b8f51
following deprecation in eedbf930287cb72e9afab1f7208c24b1146b0c4ec.

Backport of a084c5d35a6d00abd261338a374a4424764b4aee from main
AlexCLeduc 1 year ago
parent
commit
69e5b13c75

+ 4 - 1
django/db/backends/base/creation.py

@@ -135,7 +135,10 @@ class BaseDatabaseCreation:
                             queryset = model._base_manager.using(
                                 self.connection.alias,
                             ).order_by(model._meta.pk.name)
-                            yield from queryset.iterator()
+                            chunk_size = (
+                                2000 if queryset._prefetch_related_lookups else None
+                            )
+                            yield from queryset.iterator(chunk_size=chunk_size)
 
         # Serialize to a string
         out = StringIO()

+ 4 - 0
docs/releases/5.0.3.txt

@@ -24,3 +24,7 @@ Bugfixes
 * Fixed a regression in Django 5.0 that caused a crash of
   ``@sensitive_variables`` and ``@sensitive_post_parameters`` decorators on
   functions loaded from ``.pyc`` files (:ticket:`35187`).
+
+* Fixed a regression in Django 5.0 that caused a crash when reloading a test
+  database and a base queryset for a base manager used ``prefetch_related()``
+  (:ticket:`35238`).

+ 17 - 0
tests/backends/base/test_creation.py

@@ -14,6 +14,7 @@ from ..models import (
     Object,
     ObjectReference,
     ObjectSelfReference,
+    SchoolBus,
     SchoolClass,
 )
 
@@ -250,6 +251,22 @@ class TestDeserializeDbFromString(TransactionTestCase):
         self.assertIn('"model": "backends.schoolclass"', data)
         self.assertIn('"year": 1000', data)
 
+    def test_serialize_db_to_string_base_manager_with_prefetch_related(self):
+        sclass = SchoolClass.objects.create(
+            year=2000, last_updated=datetime.datetime.now()
+        )
+        bus = SchoolBus.objects.create(number=1)
+        bus.schoolclasses.add(sclass)
+        with mock.patch("django.db.migrations.loader.MigrationLoader") as loader:
+            # serialize_db_to_string() serializes only migrated apps, so mark
+            # the backends app as migrated.
+            loader_instance = loader.return_value
+            loader_instance.migrated_apps = {"backends"}
+            data = connection.creation.serialize_db_to_string()
+        self.assertIn('"model": "backends.schoolbus"', data)
+        self.assertIn('"model": "backends.schoolclass"', data)
+        self.assertIn(f'"schoolclasses": [{sclass.pk}]', data)
+
 
 class SkipTestClass:
     def skip_function(self):

+ 14 - 0
tests/backends/models.py

@@ -32,6 +32,20 @@ class SchoolClass(models.Model):
     objects = SchoolClassManager()
 
 
+class SchoolBusManager(models.Manager):
+    def get_queryset(self):
+        return super().get_queryset().prefetch_related("schoolclasses")
+
+
+class SchoolBus(models.Model):
+    number = models.IntegerField()
+    schoolclasses = models.ManyToManyField("SchoolClass")
+    objects = SchoolBusManager()
+
+    class Meta:
+        base_manager_name = "objects"
+
+
 class VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(models.Model):
     primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.AutoField(
         primary_key=True