Commit 42712048 authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

Beefed up caching of ContentType lookups by adding...

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
parent 50bf5676
Loading
Loading
Loading
Loading
+43 −12
Original line number Diff line number Diff line
@@ -2,23 +2,47 @@ 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):
@@ -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')
+47 −0
Original line number Diff line number Diff line
"""
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
"""
 No newline at end of file