Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Beefed up caching of ContentType lookups by adding ContentType.object…

…s.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
  • Loading branch information...
commit 42712048d363532469cbeff1b0b6a1734c5be5fe 1 parent 50bf567
Jacob Kaplan-Moss jacobian authored
55 django/contrib/contenttypes/models.py
View
@@ -2,25 +2,49 @@
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 @@ def clear_cache(self):
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')
47 django/contrib/contenttypes/tests.py
View
@@ -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)
+ <ContentType: content type>
+
+ >>> 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)
+ <ContentType: content type>
+ >>> 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)
+ <ContentType: content type>
+ >>> len(db.connection.queries)
+ 2
+
+Don't forget to reset DEBUG!
+
+ >>> settings.DEBUG = False
+"""
Please sign in to comment.
Something went wrong with that request. Please try again.