From f1705c8780c0a7587654fc736542d55fe4a7f29b Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Fri, 21 Jun 2024 14:24:25 +0200
Subject: [PATCH] Fixed #35545, Refs #32833 -- Fixed
 ContentTypeManager.get_for_models() crash in CreateModel migrations.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Thank you to Csirmaz Bendegúz for the report and Simon Charettes for the review.
---
 django/contrib/contenttypes/models.py   | 13 ++++---------
 tests/contenttypes_tests/test_models.py | 21 ++++++++++++++++++++-
 2 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 4f16e6eb69..1ae45dea95 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -75,7 +75,7 @@ class ContentTypeManager(models.Manager):
                 ct = self._get_from_cache(opts)
             except KeyError:
                 needed_models[opts.app_label].add(opts.model_name)
-                needed_opts[opts].append(model)
+                needed_opts[(opts.app_label, opts.model_name)].append(model)
             else:
                 results[model] = ct
         if needed_opts:
@@ -89,18 +89,13 @@ class ContentTypeManager(models.Manager):
             )
             cts = self.filter(condition)
             for ct in cts:
-                opts_models = needed_opts.pop(
-                    ct._meta.apps.get_model(ct.app_label, ct.model)._meta, []
-                )
+                opts_models = needed_opts.pop((ct.app_label, ct.model), [])
                 for model in opts_models:
                     results[model] = ct
                 self._add_to_cache(self.db, ct)
         # Create content types that weren't in the cache or DB.
-        for opts, opts_models in needed_opts.items():
-            ct = self.create(
-                app_label=opts.app_label,
-                model=opts.model_name,
-            )
+        for (app_label, model_name), opts_models in needed_opts.items():
+            ct = self.create(app_label=app_label, model=model_name)
             self._add_to_cache(self.db, ct)
             for model in opts_models:
                 results[model] = ct
diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py
index 799f1cc58c..b63c57ef09 100644
--- a/tests/contenttypes_tests/test_models.py
+++ b/tests/contenttypes_tests/test_models.py
@@ -2,7 +2,7 @@ from django.apps import apps
 from django.contrib.contenttypes.models import ContentType, ContentTypeManager
 from django.contrib.contenttypes.prefetch import GenericPrefetch
 from django.db import models
-from django.db.migrations.state import ProjectState
+from django.db.migrations.state import ModelState, ProjectState
 from django.test import TestCase, override_settings
 from django.test.utils import isolate_apps
 
@@ -99,6 +99,25 @@ class ContentTypesTests(TestCase):
             cts, {ContentType: ContentType.objects.get_for_model(ContentType)}
         )
 
+    @isolate_apps("contenttypes_tests")
+    def test_get_for_models_migrations_create_model(self):
+        state = ProjectState.from_apps(apps.get_app_config("contenttypes"))
+
+        class Foo(models.Model):
+            class Meta:
+                app_label = "contenttypes_tests"
+
+        state.add_model(ModelState.from_model(Foo))
+        ContentType = state.apps.get_model("contenttypes", "ContentType")
+        cts = ContentType.objects.get_for_models(FooWithUrl, Foo)
+        self.assertEqual(
+            cts,
+            {
+                Foo: ContentType.objects.get_for_model(Foo),
+                FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
+            },
+        )
+
     def test_get_for_models_full_cache(self):
         # Full cache
         ContentType.objects.get_for_model(ContentType)