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:
parent
50bf567675
commit
42712048d3
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
"""
|
Loading…
Reference in New Issue