Fixed #27709 -- Fixed get_for_models() for proxies with an empty cache.
Thanks Peter Inglesby for the report and tests.
This commit is contained in:
parent
ede59ef6f3
commit
4e48cfc108
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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.utils.encoding import force_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
|
@ -65,12 +67,12 @@ class ContentTypeManager(models.Manager):
|
||||||
Given *models, returns a dictionary mapping {model: content_type}.
|
Given *models, returns a dictionary mapping {model: content_type}.
|
||||||
"""
|
"""
|
||||||
for_concrete_models = kwargs.pop('for_concrete_models', True)
|
for_concrete_models = kwargs.pop('for_concrete_models', True)
|
||||||
# Final results
|
|
||||||
results = {}
|
results = {}
|
||||||
# models that aren't already in the cache
|
# Models that aren't already in the cache.
|
||||||
needed_app_labels = set()
|
needed_app_labels = set()
|
||||||
needed_models = set()
|
needed_models = set()
|
||||||
needed_opts = set()
|
# Mapping of opts to the list of models requiring it.
|
||||||
|
needed_opts = defaultdict(list)
|
||||||
for model in models:
|
for model in models:
|
||||||
opts = self._get_opts(model, for_concrete_models)
|
opts = self._get_opts(model, for_concrete_models)
|
||||||
try:
|
try:
|
||||||
|
@ -78,28 +80,30 @@ class ContentTypeManager(models.Manager):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
needed_app_labels.add(opts.app_label)
|
needed_app_labels.add(opts.app_label)
|
||||||
needed_models.add(opts.model_name)
|
needed_models.add(opts.model_name)
|
||||||
needed_opts.add(opts)
|
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.
|
||||||
cts = self.filter(
|
cts = self.filter(
|
||||||
app_label__in=needed_app_labels,
|
app_label__in=needed_app_labels,
|
||||||
model__in=needed_models
|
model__in=needed_models
|
||||||
)
|
)
|
||||||
for ct in cts:
|
for ct in cts:
|
||||||
model = ct.model_class()
|
model = ct.model_class()
|
||||||
if model._meta in needed_opts:
|
opts_models = needed_opts.pop(ct.model_class()._meta, [])
|
||||||
|
for model in opts_models:
|
||||||
results[model] = ct
|
results[model] = ct
|
||||||
needed_opts.remove(model._meta)
|
|
||||||
self._add_to_cache(self.db, ct)
|
self._add_to_cache(self.db, ct)
|
||||||
for opts in needed_opts:
|
# Create content types that weren't in the cache or DB.
|
||||||
# These weren't in the cache, or the DB, create them.
|
for opts, opts_models in needed_opts.items():
|
||||||
ct = self.create(
|
ct = self.create(
|
||||||
app_label=opts.app_label,
|
app_label=opts.app_label,
|
||||||
model=opts.model_name,
|
model=opts.model_name,
|
||||||
)
|
)
|
||||||
self._add_to_cache(self.db, ct)
|
self._add_to_cache(self.db, ct)
|
||||||
results[ct.model_class()] = ct
|
for model in opts_models:
|
||||||
|
results[model] = ct
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_for_id(self, id):
|
def get_for_id(self, id):
|
||||||
|
|
|
@ -54,13 +54,26 @@ class ContentTypesTests(TestCase):
|
||||||
with self.assertNumQueries(0):
|
with self.assertNumQueries(0):
|
||||||
ContentType.objects.get_by_natural_key('contenttypes', 'contenttype')
|
ContentType.objects.get_by_natural_key('contenttypes', 'contenttype')
|
||||||
|
|
||||||
def test_get_for_models_empty_cache(self):
|
def test_get_for_models_creation(self):
|
||||||
# Empty cache.
|
ContentType.objects.all().delete()
|
||||||
with self.assertNumQueries(1):
|
with self.assertNumQueries(4):
|
||||||
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
|
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl, ProxyModel, ConcreteModel)
|
||||||
self.assertEqual(cts, {
|
self.assertEqual(cts, {
|
||||||
ContentType: ContentType.objects.get_for_model(ContentType),
|
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||||
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
||||||
|
ProxyModel: ContentType.objects.get_for_model(ProxyModel),
|
||||||
|
ConcreteModel: ContentType.objects.get_for_model(ConcreteModel),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_get_for_models_empty_cache(self):
|
||||||
|
# Empty cache.
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl, ProxyModel, ConcreteModel)
|
||||||
|
self.assertEqual(cts, {
|
||||||
|
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||||
|
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
||||||
|
ProxyModel: ContentType.objects.get_for_model(ProxyModel),
|
||||||
|
ConcreteModel: ContentType.objects.get_for_model(ConcreteModel),
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_get_for_models_partial_cache(self):
|
def test_get_for_models_partial_cache(self):
|
||||||
|
|
Loading…
Reference in New Issue