Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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 authored March 10, 2008
55  django/contrib/contenttypes/models.py
@@ -2,25 +2,49 @@
2 2
 from django.utils.translation import ugettext_lazy as _
3 3
 from django.utils.encoding import smart_unicode
4 4
 
5  
-CONTENT_TYPE_CACHE = {}
6 5
 class ContentTypeManager(models.Manager):
  6
+
  7
+    # Cache to avoid re-looking up ContentType objects all over the place.
  8
+    # This cache is shared by all the get_for_* methods.
  9
+    _cache = {}
  10
+    
7 11
     def get_for_model(self, model):
8 12
         """
9  
-        Returns the ContentType object for the given model, creating the
10  
-        ContentType if necessary.
  13
+        Returns the ContentType object for a given model, creating the
  14
+        ContentType if necessary. Lookups are cached so that subsequent lookups
  15
+        for the same model don't hit the database.
11 16
         """
12 17
         opts = model._meta
13 18
         key = (opts.app_label, opts.object_name.lower())
14 19
         try:
15  
-            ct = CONTENT_TYPE_CACHE[key]
  20
+            ct = self.__class__._cache[key]
16 21
         except KeyError:
17  
-            # The smart_unicode() is needed around opts.verbose_name_raw because it might
18  
-            # be a django.utils.functional.__proxy__ object.
19  
-            ct, created = self.model._default_manager.get_or_create(app_label=key[0],
20  
-                model=key[1], defaults={'name': smart_unicode(opts.verbose_name_raw)})
21  
-            CONTENT_TYPE_CACHE[key] = ct
  22
+            # Load or create the ContentType entry. The smart_unicode() is 
  23
+            # needed around opts.verbose_name_raw because name_raw might be a
  24
+            # django.utils.functional.__proxy__ object.
  25
+            ct, created = self.get_or_create(
  26
+                app_label = opts.app_label,
  27
+                model = opts.object_name.lower(), 
  28
+                defaults = {'name': smart_unicode(opts.verbose_name_raw)},
  29
+            )
  30
+            self._add_to_cache(ct)
  31
+            
22 32
         return ct
23  
-
  33
+        
  34
+    def get_for_id(self, id):
  35
+        """
  36
+        Lookup a ContentType by ID. Uses the same shared cache as get_for_model
  37
+        (though ContentTypes are obviously not created on-the-fly by get_by_id).
  38
+        """
  39
+        try:
  40
+            ct = self.__class__._cache[id]
  41
+        except KeyError:
  42
+            # This could raise a DoesNotExist; that's correct behavior and will
  43
+            # make sure that only correct ctypes get stored in the cache dict.
  44
+            ct = self.get(pk=id)
  45
+            self._add_to_cache(ct)
  46
+        return ct
  47
+            
24 48
     def clear_cache(self):
25 49
         """
26 50
         Clear out the content-type cache. This needs to happen during database
@@ -28,14 +52,21 @@ def clear_cache(self):
28 52
         django.contrib.contenttypes.management.update_contenttypes for where
29 53
         this gets called).
30 54
         """
31  
-        global CONTENT_TYPE_CACHE
32  
-        CONTENT_TYPE_CACHE = {}
  55
+        self.__class__._cache.clear()
  56
+        
  57
+    def _add_to_cache(self, ct):
  58
+        """Insert a ContentType into the cache."""
  59
+        model = ct.model_class()
  60
+        key = (model._meta.app_label, model._meta.object_name.lower())
  61
+        self.__class__._cache[key] = ct
  62
+        self.__class__._cache[ct.id] = ct
33 63
 
34 64
 class ContentType(models.Model):
35 65
     name = models.CharField(max_length=100)
36 66
     app_label = models.CharField(max_length=100)
37 67
     model = models.CharField(_('python model class name'), max_length=100)
38 68
     objects = ContentTypeManager()
  69
+    
39 70
     class Meta:
40 71
         verbose_name = _('content type')
41 72
         verbose_name_plural = _('content types')
47  django/contrib/contenttypes/tests.py
... ...
@@ -0,0 +1,47 @@
  1
+"""
  2
+Make sure that the content type cache (see ContentTypeManager) works correctly.
  3
+Lookups for a particular content type -- by model or by ID -- should hit the
  4
+database only on the first lookup.
  5
+
  6
+First, let's make sure we're dealing with a blank slate (and that DEBUG is on so
  7
+that queries get logged)::
  8
+
  9
+    >>> from django.conf import settings
  10
+    >>> settings.DEBUG = True
  11
+
  12
+    >>> from django.contrib.contenttypes.models import ContentType
  13
+    >>> ContentType.objects.clear_cache()
  14
+
  15
+    >>> from django import db
  16
+    >>> db.reset_queries()
  17
+    
  18
+At this point, a lookup for a ContentType should hit the DB::
  19
+
  20
+    >>> ContentType.objects.get_for_model(ContentType)
  21
+    <ContentType: content type>
  22
+    
  23
+    >>> len(db.connection.queries)
  24
+    1
  25
+
  26
+A second hit, though, won't hit the DB, nor will a lookup by ID::
  27
+
  28
+    >>> ct = ContentType.objects.get_for_model(ContentType)
  29
+    >>> len(db.connection.queries)
  30
+    1
  31
+    >>> ContentType.objects.get_for_id(ct.id)
  32
+    <ContentType: content type>
  33
+    >>> len(db.connection.queries)
  34
+    1
  35
+
  36
+Once we clear the cache, another lookup will again hit the DB::
  37
+
  38
+    >>> ContentType.objects.clear_cache()
  39
+    >>> ContentType.objects.get_for_model(ContentType)
  40
+    <ContentType: content type>
  41
+    >>> len(db.connection.queries)
  42
+    2
  43
+
  44
+Don't forget to reset DEBUG!
  45
+
  46
+    >>> settings.DEBUG = False
  47
+"""

0 notes on commit 4271204

Please sign in to comment.
Something went wrong with that request. Please try again.