mirror of https://github.com/django/django.git
Fixed #31357 -- Fixed get_for_models() crash for stale content types when model with the same name exists in another app.
This commit is contained in:
parent
839d403e50
commit
859a87d873
|
@ -2,6 +2,7 @@ from collections import defaultdict
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,9 +65,8 @@ class ContentTypeManager(models.Manager):
|
||||||
Given *models, return a dictionary mapping {model: content_type}.
|
Given *models, return a dictionary mapping {model: content_type}.
|
||||||
"""
|
"""
|
||||||
results = {}
|
results = {}
|
||||||
# Models that aren't already in the cache.
|
# Models that aren't already in the cache grouped by app labels.
|
||||||
needed_app_labels = set()
|
needed_models = defaultdict(set)
|
||||||
needed_models = set()
|
|
||||||
# Mapping of opts to the list of models requiring it.
|
# Mapping of opts to the list of models requiring it.
|
||||||
needed_opts = defaultdict(list)
|
needed_opts = defaultdict(list)
|
||||||
for model in models:
|
for model in models:
|
||||||
|
@ -74,14 +74,20 @@ class ContentTypeManager(models.Manager):
|
||||||
try:
|
try:
|
||||||
ct = self._get_from_cache(opts)
|
ct = self._get_from_cache(opts)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
needed_app_labels.add(opts.app_label)
|
needed_models[opts.app_label].add(opts.model_name)
|
||||||
needed_models.add(opts.model_name)
|
|
||||||
needed_opts[opts].append(model)
|
needed_opts[opts].append(model)
|
||||||
else:
|
else:
|
||||||
results[model] = ct
|
results[model] = ct
|
||||||
if needed_opts:
|
if needed_opts:
|
||||||
# Lookup required content types from the DB.
|
# Lookup required content types from the DB.
|
||||||
cts = self.filter(app_label__in=needed_app_labels, model__in=needed_models)
|
condition = Q(
|
||||||
|
*(
|
||||||
|
Q(("app_label", app_label), ("model__in", models), _connector=Q.AND)
|
||||||
|
for app_label, models in needed_models.items()
|
||||||
|
),
|
||||||
|
_connector=Q.OR,
|
||||||
|
)
|
||||||
|
cts = self.filter(condition)
|
||||||
for ct in cts:
|
for ct in cts:
|
||||||
opts_models = needed_opts.pop(ct.model_class()._meta, [])
|
opts_models = needed_opts.pop(ct.model_class()._meta, [])
|
||||||
for model in opts_models:
|
for model in opts_models:
|
||||||
|
|
|
@ -248,6 +248,26 @@ class ContentTypesTests(TestCase):
|
||||||
ct_fetched = ContentType.objects.get_for_id(ct.pk)
|
ct_fetched = ContentType.objects.get_for_id(ct.pk)
|
||||||
self.assertIsNone(ct_fetched.model_class())
|
self.assertIsNone(ct_fetched.model_class())
|
||||||
|
|
||||||
|
def test_missing_model_with_existing_model_name(self):
|
||||||
|
"""
|
||||||
|
Displaying content types in admin (or anywhere) doesn't break on
|
||||||
|
leftover content type records in the DB for which no model is defined
|
||||||
|
anymore, even if a model with the same name exists in another app.
|
||||||
|
"""
|
||||||
|
# Create a stale ContentType that matches the name of an existing
|
||||||
|
# model.
|
||||||
|
ContentType.objects.create(app_label="contenttypes", model="author")
|
||||||
|
ContentType.objects.clear_cache()
|
||||||
|
# get_for_models() should work as expected for existing models.
|
||||||
|
cts = ContentType.objects.get_for_models(ContentType, Author)
|
||||||
|
self.assertEqual(
|
||||||
|
cts,
|
||||||
|
{
|
||||||
|
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||||
|
Author: ContentType.objects.get_for_model(Author),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_str(self):
|
def test_str(self):
|
||||||
ct = ContentType.objects.get(app_label="contenttypes_tests", model="site")
|
ct = ContentType.objects.get(app_label="contenttypes_tests", model="site")
|
||||||
self.assertEqual(str(ct), "contenttypes_tests | site")
|
self.assertEqual(str(ct), "contenttypes_tests | site")
|
||||||
|
|
Loading…
Reference in New Issue