Author: Alex
Date: 2011-10-12 13:51:59 -0700 (Wed, 12 Oct 2011)
New Revision: 16963

Modified:
   django/trunk/django/contrib/auth/management/__init__.py
   django/trunk/django/contrib/contenttypes/models.py
   django/trunk/django/contrib/contenttypes/tests.py
   django/trunk/docs/ref/contrib/contenttypes.txt
Log:
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.

Modified: django/trunk/django/contrib/auth/management/__init__.py
===================================================================
--- django/trunk/django/contrib/auth/management/__init__.py     2011-10-12 
19:34:34 UTC (rev 16962)
+++ django/trunk/django/contrib/auth/management/__init__.py     2011-10-12 
20:51:59 UTC (rev 16963)
@@ -31,8 +31,8 @@
     searched_perms = list()
     # The codenames and ctypes that should exist.
     ctypes = set()
-    for klass in app_models:
-        ctype = ContentType.objects.get_for_model(klass)
+    ctypes_for_models = ContentType.objects.get_for_models(*app_models)
+    for klass, ctype in ctypes_for_models.iteritems():
         ctypes.add(ctype)
         for perm in _get_all_permissions(klass._meta):
             searched_perms.append((ctype, perm))

Modified: django/trunk/django/contrib/contenttypes/models.py
===================================================================
--- django/trunk/django/contrib/contenttypes/models.py  2011-10-12 19:34:34 UTC 
(rev 16962)
+++ django/trunk/django/contrib/contenttypes/models.py  2011-10-12 20:51:59 UTC 
(rev 16963)
@@ -15,19 +15,26 @@
             ct = self.get(app_label=app_label, model=model)
         return ct
 
+    def _get_opts(self, model):
+        opts = model._meta
+        while opts.proxy:
+            model = opts.proxy_for_model
+            opts = model._meta
+        return opts
+
+    def _get_from_cache(self, opts):
+        key = (opts.app_label, opts.object_name.lower())
+        return self.__class__._cache[self.db][key]
+
     def get_for_model(self, model):
         """
         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
-        while opts.proxy:
-            model = opts.proxy_for_model
-            opts = model._meta
-        key = (opts.app_label, opts.object_name.lower())
+        opts = self._get_opts(model)
         try:
-            ct = self.__class__._cache[self.db][key]
+            ct = self._get_from_cache(opts)
         except KeyError:
             # Load or create the ContentType entry. The smart_unicode() is
             # needed around opts.verbose_name_raw because name_raw might be a
@@ -41,6 +48,48 @@
 
         return ct
 
+    def get_for_models(self, *models):
+        """
+        Given *models, returns a dictionary mapping {model: content_type}.
+        """
+        # Final results
+        results = {}
+        # models that aren't already in the cache
+        needed_app_labels = set()
+        needed_models = set()
+        needed_opts = set()
+        for model in models:
+            opts = self._get_opts(model)
+            try:
+                ct = self._get_from_cache(opts)
+            except KeyError:
+                needed_app_labels.add(opts.app_label)
+                needed_models.add(opts.object_name.lower())
+                needed_opts.add(opts)
+            else:
+                results[model] = ct
+        if needed_opts:
+            cts = self.filter(
+                app_label__in=needed_app_labels,
+                model__in=needed_models
+            )
+            for ct in cts:
+                model = ct.model_class()
+                if model._meta in needed_opts:
+                    results[model] = ct
+                    needed_opts.remove(model._meta)
+                self._add_to_cache(self.db, ct)
+        for opts in needed_opts:
+            # These weren't in the cache, or the DB, create them.
+            ct = self.create(
+                app_label=opts.app_label,
+                model=opts.object_name.lower(),
+                name=smart_unicode(opts.verbose_name_raw),
+            )
+            self._add_to_cache(self.db, ct)
+            results[ct.model_class()] = ct
+        return results
+
     def get_for_id(self, id):
         """
         Lookup a ContentType by ID. Uses the same shared cache as get_for_model

Modified: django/trunk/django/contrib/contenttypes/tests.py
===================================================================
--- django/trunk/django/contrib/contenttypes/tests.py   2011-10-12 19:34:34 UTC 
(rev 16962)
+++ django/trunk/django/contrib/contenttypes/tests.py   2011-10-12 20:51:59 UTC 
(rev 16963)
@@ -63,6 +63,36 @@
         with self.assertNumQueries(1):
             ContentType.objects.get_for_model(ContentType)
 
+    def test_get_for_models_empty_cache(self):
+        # Empty cache.
+        with self.assertNumQueries(1):
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
+        self.assertEqual(cts, {
+            ContentType: ContentType.objects.get_for_model(ContentType),
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
+        })
+
+    def test_get_for_models_partial_cache(self):
+        # Partial cache
+        ContentType.objects.get_for_model(ContentType)
+        with self.assertNumQueries(1):
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
+        self.assertEqual(cts, {
+            ContentType: ContentType.objects.get_for_model(ContentType),
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
+        })
+
+    def test_get_for_models_full_cache(self):
+        # Full cache
+        ContentType.objects.get_for_model(ContentType)
+        ContentType.objects.get_for_model(FooWithUrl)
+        with self.assertNumQueries(0):
+            cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
+        self.assertEqual(cts, {
+            ContentType: ContentType.objects.get_for_model(ContentType),
+            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
+        })
+
     def test_shortcut_view(self):
         """
         Check that the shortcut view (used for the admin "view on site"

Modified: django/trunk/docs/ref/contrib/contenttypes.txt
===================================================================
--- django/trunk/docs/ref/contrib/contenttypes.txt      2011-10-12 19:34:34 UTC 
(rev 16962)
+++ django/trunk/docs/ref/contrib/contenttypes.txt      2011-10-12 20:51:59 UTC 
(rev 16963)
@@ -193,6 +193,13 @@
         :class:`~django.contrib.contenttypes.models.ContentType` instance
         representing that model.
 
+    .. method:: get_for_models(*models)
+
+        Takes a variadic number of model classes, and returns a dictionary
+        mapping the model classes to the
+        :class:`~django.contrib.contenttypes.models.ContentType` instances
+        representing them.
+
     .. method:: get_by_natural_key(app_label, model)
 
         Returns the :class:`~django.contrib.contenttypes.models.ContentType`
@@ -319,8 +326,8 @@
 
 Due to the way :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
 is implemented, you cannot use such fields directly with filters (``filter()``
-and ``exclude()``, for example) via the database API. Because a 
-:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a 
+and ``exclude()``, for example) via the database API. Because a
+:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a
 normal field objects, these examples will *not* work::
 
     # This will fail

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to