mirror of https://github.com/django/django.git
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,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')
|
||||
|
|
|
@ -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