Introduce `ContentType.objects.get_for_models(*models)` and use it in the the auth permissions code. This is a solid performance gain on the test suite. Thanks to ptone for the profiling to find this hotspot, and carl for the review.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16963 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
a5e691e5de
commit
f04af7080b
|
@ -31,8 +31,8 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|||
searched_perms = list()
|
||||
# The codenames and ctypes that should exist.
|
||||
ctypes = set()
|
||||
for klass in app_models:
|
||||
ctype = ContentType.objects.get_for_model(klass)
|
||||
ctypes_for_models = ContentType.objects.get_for_models(*app_models)
|
||||
for klass, ctype in ctypes_for_models.iteritems():
|
||||
ctypes.add(ctype)
|
||||
for perm in _get_all_permissions(klass._meta):
|
||||
searched_perms.append((ctype, perm))
|
||||
|
|
|
@ -15,19 +15,26 @@ class ContentTypeManager(models.Manager):
|
|||
ct = self.get(app_label=app_label, model=model)
|
||||
return ct
|
||||
|
||||
def _get_opts(self, model):
|
||||
opts = model._meta
|
||||
while opts.proxy:
|
||||
model = opts.proxy_for_model
|
||||
opts = model._meta
|
||||
return opts
|
||||
|
||||
def _get_from_cache(self, opts):
|
||||
key = (opts.app_label, opts.object_name.lower())
|
||||
return self.__class__._cache[self.db][key]
|
||||
|
||||
def get_for_model(self, model):
|
||||
"""
|
||||
Returns the ContentType object for a given model, creating the
|
||||
ContentType if necessary. Lookups are cached so that subsequent lookups
|
||||
for the same model don't hit the database.
|
||||
"""
|
||||
opts = model._meta
|
||||
while opts.proxy:
|
||||
model = opts.proxy_for_model
|
||||
opts = model._meta
|
||||
key = (opts.app_label, opts.object_name.lower())
|
||||
opts = self._get_opts(model)
|
||||
try:
|
||||
ct = self.__class__._cache[self.db][key]
|
||||
ct = self._get_from_cache(opts)
|
||||
except KeyError:
|
||||
# Load or create the ContentType entry. The smart_unicode() is
|
||||
# needed around opts.verbose_name_raw because name_raw might be a
|
||||
|
@ -41,6 +48,48 @@ class ContentTypeManager(models.Manager):
|
|||
|
||||
return ct
|
||||
|
||||
def get_for_models(self, *models):
|
||||
"""
|
||||
Given *models, returns a dictionary mapping {model: content_type}.
|
||||
"""
|
||||
# Final results
|
||||
results = {}
|
||||
# models that aren't already in the cache
|
||||
needed_app_labels = set()
|
||||
needed_models = set()
|
||||
needed_opts = set()
|
||||
for model in models:
|
||||
opts = self._get_opts(model)
|
||||
try:
|
||||
ct = self._get_from_cache(opts)
|
||||
except KeyError:
|
||||
needed_app_labels.add(opts.app_label)
|
||||
needed_models.add(opts.object_name.lower())
|
||||
needed_opts.add(opts)
|
||||
else:
|
||||
results[model] = ct
|
||||
if needed_opts:
|
||||
cts = self.filter(
|
||||
app_label__in=needed_app_labels,
|
||||
model__in=needed_models
|
||||
)
|
||||
for ct in cts:
|
||||
model = ct.model_class()
|
||||
if model._meta in needed_opts:
|
||||
results[model] = ct
|
||||
needed_opts.remove(model._meta)
|
||||
self._add_to_cache(self.db, ct)
|
||||
for opts in needed_opts:
|
||||
# These weren't in the cache, or the DB, create them.
|
||||
ct = self.create(
|
||||
app_label=opts.app_label,
|
||||
model=opts.object_name.lower(),
|
||||
name=smart_unicode(opts.verbose_name_raw),
|
||||
)
|
||||
self._add_to_cache(self.db, ct)
|
||||
results[ct.model_class()] = ct
|
||||
return results
|
||||
|
||||
def get_for_id(self, id):
|
||||
"""
|
||||
Lookup a ContentType by ID. Uses the same shared cache as get_for_model
|
||||
|
|
|
@ -63,6 +63,36 @@ class ContentTypesTests(TestCase):
|
|||
with self.assertNumQueries(1):
|
||||
ContentType.objects.get_for_model(ContentType)
|
||||
|
||||
def test_get_for_models_empty_cache(self):
|
||||
# Empty cache.
|
||||
with self.assertNumQueries(1):
|
||||
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
|
||||
self.assertEqual(cts, {
|
||||
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
||||
})
|
||||
|
||||
def test_get_for_models_partial_cache(self):
|
||||
# Partial cache
|
||||
ContentType.objects.get_for_model(ContentType)
|
||||
with self.assertNumQueries(1):
|
||||
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
|
||||
self.assertEqual(cts, {
|
||||
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
||||
})
|
||||
|
||||
def test_get_for_models_full_cache(self):
|
||||
# Full cache
|
||||
ContentType.objects.get_for_model(ContentType)
|
||||
ContentType.objects.get_for_model(FooWithUrl)
|
||||
with self.assertNumQueries(0):
|
||||
cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
|
||||
self.assertEqual(cts, {
|
||||
ContentType: ContentType.objects.get_for_model(ContentType),
|
||||
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
|
||||
})
|
||||
|
||||
def test_shortcut_view(self):
|
||||
"""
|
||||
Check that the shortcut view (used for the admin "view on site"
|
||||
|
|
|
@ -193,6 +193,13 @@ The ``ContentTypeManager``
|
|||
:class:`~django.contrib.contenttypes.models.ContentType` instance
|
||||
representing that model.
|
||||
|
||||
.. method:: get_for_models(*models)
|
||||
|
||||
Takes a variadic number of model classes, and returns a dictionary
|
||||
mapping the model classes to the
|
||||
:class:`~django.contrib.contenttypes.models.ContentType` instances
|
||||
representing them.
|
||||
|
||||
.. method:: get_by_natural_key(app_label, model)
|
||||
|
||||
Returns the :class:`~django.contrib.contenttypes.models.ContentType`
|
||||
|
@ -319,8 +326,8 @@ creating a ``TaggedItem``::
|
|||
|
||||
Due to the way :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
|
||||
is implemented, you cannot use such fields directly with filters (``filter()``
|
||||
and ``exclude()``, for example) via the database API. Because a
|
||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a
|
||||
and ``exclude()``, for example) via the database API. Because a
|
||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a
|
||||
normal field objects, these examples will *not* work::
|
||||
|
||||
# This will fail
|
||||
|
|
Loading…
Reference in New Issue