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
This commit is contained in:
Jacob Kaplan-Moss 2008-03-10 23:32:28 +00:00
parent 50bf567675
commit 42712048d3
2 changed files with 90 additions and 12 deletions

View File

@ -2,23 +2,47 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
CONTENT_TYPE_CACHE = {}
class ContentTypeManager(models.Manager): 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): def get_for_model(self, model):
""" """
Returns the ContentType object for the given model, creating the Returns the ContentType object for a given model, creating the
ContentType if necessary. ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database.
""" """
opts = model._meta opts = model._meta
key = (opts.app_label, opts.object_name.lower()) key = (opts.app_label, opts.object_name.lower())
try: try:
ct = CONTENT_TYPE_CACHE[key] ct = self.__class__._cache[key]
except KeyError: except KeyError:
# The smart_unicode() is needed around opts.verbose_name_raw because it might # Load or create the ContentType entry. The smart_unicode() is
# be a django.utils.functional.__proxy__ object. # needed around opts.verbose_name_raw because name_raw might be a
ct, created = self.model._default_manager.get_or_create(app_label=key[0], # django.utils.functional.__proxy__ object.
model=key[1], defaults={'name': smart_unicode(opts.verbose_name_raw)}) ct, created = self.get_or_create(
CONTENT_TYPE_CACHE[key] = ct 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 return ct
def clear_cache(self): def clear_cache(self):
@ -28,14 +52,21 @@ class ContentTypeManager(models.Manager):
django.contrib.contenttypes.management.update_contenttypes for where django.contrib.contenttypes.management.update_contenttypes for where
this gets called). this gets called).
""" """
global CONTENT_TYPE_CACHE self.__class__._cache.clear()
CONTENT_TYPE_CACHE = {}
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): class ContentType(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
app_label = models.CharField(max_length=100) app_label = models.CharField(max_length=100)
model = models.CharField(_('python model class name'), max_length=100) model = models.CharField(_('python model class name'), max_length=100)
objects = ContentTypeManager() objects = ContentTypeManager()
class Meta: class Meta:
verbose_name = _('content type') verbose_name = _('content type')
verbose_name_plural = _('content types') verbose_name_plural = _('content types')

View File

@ -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
"""