Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Introduce `ContentType.objects.get_for_models(*models)` and use it in…

… the the auth permissions code. This is a solid performance gain on the test suite. Thanks to ptone for the profiling to find this hotspot, and carl for the review.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16963 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f04af7080b12744c8f06fb8228ef683d556690d0 1 parent a5e691e
Alex Gaynor authored
4  django/contrib/auth/management/__init__.py
@@ -31,8 +31,8 @@ def create_permissions(app, created_models, verbosity, **kwargs):
31 31
     searched_perms = list()
32 32
     # The codenames and ctypes that should exist.
33 33
     ctypes = set()
34  
-    for klass in app_models:
35  
-        ctype = ContentType.objects.get_for_model(klass)
  34
+    ctypes_for_models = ContentType.objects.get_for_models(*app_models)
  35
+    for klass, ctype in ctypes_for_models.iteritems():
36 36
         ctypes.add(ctype)
37 37
         for perm in _get_all_permissions(klass._meta):
38 38
             searched_perms.append((ctype, perm))
61  django/contrib/contenttypes/models.py
@@ -15,19 +15,26 @@ def get_by_natural_key(self, app_label, model):
15 15
             ct = self.get(app_label=app_label, model=model)
16 16
         return ct
17 17
 
  18
+    def _get_opts(self, model):
  19
+        opts = model._meta
  20
+        while opts.proxy:
  21
+            model = opts.proxy_for_model
  22
+            opts = model._meta
  23
+        return opts
  24
+
  25
+    def _get_from_cache(self, opts):
  26
+        key = (opts.app_label, opts.object_name.lower())
  27
+        return self.__class__._cache[self.db][key]
  28
+
18 29
     def get_for_model(self, model):
19 30
         """
20 31
         Returns the ContentType object for a given model, creating the
21 32
         ContentType if necessary. Lookups are cached so that subsequent lookups
22 33
         for the same model don't hit the database.
23 34
         """
24  
-        opts = model._meta
25  
-        while opts.proxy:
26  
-            model = opts.proxy_for_model
27  
-            opts = model._meta
28  
-        key = (opts.app_label, opts.object_name.lower())
  35
+        opts = self._get_opts(model)
29 36
         try:
30  
-            ct = self.__class__._cache[self.db][key]
  37
+            ct = self._get_from_cache(opts)
31 38
         except KeyError:
32 39
             # Load or create the ContentType entry. The smart_unicode() is
33 40
             # needed around opts.verbose_name_raw because name_raw might be a
@@ -41,6 +48,48 @@ def get_for_model(self, model):
41 48
 
42 49
         return ct
43 50
 
  51
+    def get_for_models(self, *models):
  52
+        """
  53
+        Given *models, returns a dictionary mapping {model: content_type}.
  54
+        """
  55
+        # Final results
  56
+        results = {}
  57
+        # models that aren't already in the cache
  58
+        needed_app_labels = set()
  59
+        needed_models = set()
  60
+        needed_opts = set()
  61
+        for model in models:
  62
+            opts = self._get_opts(model)
  63
+            try:
  64
+                ct = self._get_from_cache(opts)
  65
+            except KeyError:
  66
+                needed_app_labels.add(opts.app_label)
  67
+                needed_models.add(opts.object_name.lower())
  68
+                needed_opts.add(opts)
  69
+            else:
  70
+                results[model] = ct
  71
+        if needed_opts:
  72
+            cts = self.filter(
  73
+                app_label__in=needed_app_labels,
  74
+                model__in=needed_models
  75
+            )
  76
+            for ct in cts:
  77
+                model = ct.model_class()
  78
+                if model._meta in needed_opts:
  79
+                    results[model] = ct
  80
+                    needed_opts.remove(model._meta)
  81
+                self._add_to_cache(self.db, ct)
  82
+        for opts in needed_opts:
  83
+            # These weren't in the cache, or the DB, create them.
  84
+            ct = self.create(
  85
+                app_label=opts.app_label,
  86
+                model=opts.object_name.lower(),
  87
+                name=smart_unicode(opts.verbose_name_raw),
  88
+            )
  89
+            self._add_to_cache(self.db, ct)
  90
+            results[ct.model_class()] = ct
  91
+        return results
  92
+
44 93
     def get_for_id(self, id):
45 94
         """
46 95
         Lookup a ContentType by ID. Uses the same shared cache as get_for_model
30  django/contrib/contenttypes/tests.py
@@ -63,6 +63,36 @@ def test_lookup_cache(self):
63 63
         with self.assertNumQueries(1):
64 64
             ContentType.objects.get_for_model(ContentType)
65 65
 
  66
+    def test_get_for_models_empty_cache(self):
  67
+        # Empty cache.
  68
+        with self.assertNumQueries(1):
  69
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
  70
+        self.assertEqual(cts, {
  71
+            ContentType: ContentType.objects.get_for_model(ContentType),
  72
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
  73
+        })
  74
+
  75
+    def test_get_for_models_partial_cache(self):
  76
+        # Partial cache
  77
+        ContentType.objects.get_for_model(ContentType)
  78
+        with self.assertNumQueries(1):
  79
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
  80
+        self.assertEqual(cts, {
  81
+            ContentType: ContentType.objects.get_for_model(ContentType),
  82
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
  83
+        })
  84
+
  85
+    def test_get_for_models_full_cache(self):
  86
+        # Full cache
  87
+        ContentType.objects.get_for_model(ContentType)
  88
+        ContentType.objects.get_for_model(FooWithUrl)
  89
+        with self.assertNumQueries(0):
  90
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
  91
+        self.assertEqual(cts, {
  92
+            ContentType: ContentType.objects.get_for_model(ContentType),
  93
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
  94
+        })
  95
+
66 96
     def test_shortcut_view(self):
67 97
         """
68 98
         Check that the shortcut view (used for the admin "view on site"
11  docs/ref/contrib/contenttypes.txt
@@ -193,6 +193,13 @@ The ``ContentTypeManager``
193 193
         :class:`~django.contrib.contenttypes.models.ContentType` instance
194 194
         representing that model.
195 195
 
  196
+    .. method:: get_for_models(*models)
  197
+
  198
+        Takes a variadic number of model classes, and returns a dictionary
  199
+        mapping the model classes to the
  200
+        :class:`~django.contrib.contenttypes.models.ContentType` instances
  201
+        representing them.
  202
+
196 203
     .. method:: get_by_natural_key(app_label, model)
197 204
 
198 205
         Returns the :class:`~django.contrib.contenttypes.models.ContentType`
@@ -319,8 +326,8 @@ creating a ``TaggedItem``::
319 326
 
320 327
 Due to the way :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
321 328
 is implemented, you cannot use such fields directly with filters (``filter()``
322  
-and ``exclude()``, for example) via the database API. Because a 
323  
-:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a 
  329
+and ``exclude()``, for example) via the database API. Because a
  330
+:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a
324 331
 normal field objects, these examples will *not* work::
325 332
 
326 333
     # This will fail

0 notes on commit f04af70

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