From 42712048d363532469cbeff1b0b6a1734c5be5fe Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 10 Mar 2008 23:32:28 +0000 Subject: [PATCH] Beefed up caching of ContentType lookups by adding ContentType.objects.get_for_id(). This shares the same cache as get_for_model(), and thus things that do lots of ctype lookups by ID will benefit. Refs #5570. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7216 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/contenttypes/models.py | 55 +++++++++++++++++++++------ django/contrib/contenttypes/tests.py | 47 +++++++++++++++++++++++ 2 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 django/contrib/contenttypes/tests.py diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 1413586254..4df1edcc75 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -2,25 +2,49 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode -CONTENT_TYPE_CACHE = {} class ContentTypeManager(models.Manager): + + # Cache to avoid re-looking up ContentType objects all over the place. + # This cache is shared by all the get_for_* methods. + _cache = {} + def get_for_model(self, model): """ - Returns the ContentType object for the given model, creating the - ContentType if necessary. + 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 key = (opts.app_label, opts.object_name.lower()) try: - ct = CONTENT_TYPE_CACHE[key] + ct = self.__class__._cache[key] except KeyError: - # The smart_unicode() is needed around opts.verbose_name_raw because it might - # be a django.utils.functional.__proxy__ object. - ct, created = self.model._default_manager.get_or_create(app_label=key[0], - model=key[1], defaults={'name': smart_unicode(opts.verbose_name_raw)}) - CONTENT_TYPE_CACHE[key] = ct + # Load or create the ContentType entry. The smart_unicode() is + # needed around opts.verbose_name_raw because name_raw might be a + # django.utils.functional.__proxy__ object. + ct, created = self.get_or_create( + app_label = opts.app_label, + model = opts.object_name.lower(), + defaults = {'name': smart_unicode(opts.verbose_name_raw)}, + ) + self._add_to_cache(ct) + return ct - + + def get_for_id(self, id): + """ + Lookup a ContentType by ID. Uses the same shared cache as get_for_model + (though ContentTypes are obviously not created on-the-fly by get_by_id). + """ + try: + ct = self.__class__._cache[id] + except KeyError: + # This could raise a DoesNotExist; that's correct behavior and will + # make sure that only correct ctypes get stored in the cache dict. + ct = self.get(pk=id) + self._add_to_cache(ct) + return ct + def clear_cache(self): """ Clear out the content-type cache. This needs to happen during database @@ -28,14 +52,21 @@ class ContentTypeManager(models.Manager): django.contrib.contenttypes.management.update_contenttypes for where this gets called). """ - global CONTENT_TYPE_CACHE - CONTENT_TYPE_CACHE = {} + self.__class__._cache.clear() + + def _add_to_cache(self, ct): + """Insert a ContentType into the cache.""" + model = ct.model_class() + key = (model._meta.app_label, model._meta.object_name.lower()) + self.__class__._cache[key] = ct + self.__class__._cache[ct.id] = ct class ContentType(models.Model): name = models.CharField(max_length=100) app_label = models.CharField(max_length=100) model = models.CharField(_('python model class name'), max_length=100) objects = ContentTypeManager() + class Meta: verbose_name = _('content type') verbose_name_plural = _('content types') diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py new file mode 100644 index 0000000000..148edfcbbb --- /dev/null +++ b/django/contrib/contenttypes/tests.py @@ -0,0 +1,47 @@ +""" +Make sure that the content type cache (see ContentTypeManager) works correctly. +Lookups for a particular content type -- by model or by ID -- should hit the +database only on the first lookup. + +First, let's make sure we're dealing with a blank slate (and that DEBUG is on so +that queries get logged):: + + >>> from django.conf import settings + >>> settings.DEBUG = True + + >>> from django.contrib.contenttypes.models import ContentType + >>> ContentType.objects.clear_cache() + + >>> from django import db + >>> db.reset_queries() + +At this point, a lookup for a ContentType should hit the DB:: + + >>> ContentType.objects.get_for_model(ContentType) + + + >>> len(db.connection.queries) + 1 + +A second hit, though, won't hit the DB, nor will a lookup by ID:: + + >>> ct = ContentType.objects.get_for_model(ContentType) + >>> len(db.connection.queries) + 1 + >>> ContentType.objects.get_for_id(ct.id) + + >>> len(db.connection.queries) + 1 + +Once we clear the cache, another lookup will again hit the DB:: + + >>> ContentType.objects.clear_cache() + >>> ContentType.objects.get_for_model(ContentType) + + >>> len(db.connection.queries) + 2 + +Don't forget to reset DEBUG! + + >>> settings.DEBUG = False +""" \ No newline at end of file