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)