Commit b6d533af authored by Simon Charette's avatar Simon Charette Committed by Anssi Kääriäinen
Browse files

Fixed #18399 – Added a way to get ContentTypes for proxy models

Added kwargs for_concrete_model and for_concrete_models to ContentType
methods get_for_model() and get_for_models(). By setting the flag to
False, it is possible to get the contenttype for proxy models.
parent 90985048
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -16,20 +16,24 @@ class ContentTypeManager(models.Manager):
            self._add_to_cache(self.db, ct)
        return ct

    def _get_opts(self, model):
        return model._meta.concrete_model._meta
    def _get_opts(self, model, for_concrete_model):
        if for_concrete_model:
            model = model._meta.concrete_model
        elif model._deferred:
            model = model._meta.proxy_for_model
        return model._meta

    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):
    def get_for_model(self, model, for_concrete_model=True):
        """
        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 = self._get_opts(model)
        opts = self._get_opts(model, for_concrete_model)
        try:
            ct = self._get_from_cache(opts)
        except KeyError:
@@ -45,10 +49,11 @@ class ContentTypeManager(models.Manager):

        return ct

    def get_for_models(self, *models):
    def get_for_models(self, *models, **kwargs):
        """
        Given *models, returns a dictionary mapping {model: content_type}.
        """
        for_concrete_models = kwargs.pop('for_concrete_models', True)
        # Final results
        results = {}
        # models that aren't already in the cache
@@ -56,7 +61,7 @@ class ContentTypeManager(models.Manager):
        needed_models = set()
        needed_opts = set()
        for model in models:
            opts = self._get_opts(model)
            opts = self._get_opts(model, for_concrete_models)
            try:
                ct = self._get_from_cache(opts)
            except KeyError:
+88 −0
Original line number Diff line number Diff line
@@ -11,6 +11,13 @@ from django.test import TestCase
from django.utils.encoding import smart_str


class ConcreteModel(models.Model):
    name = models.CharField(max_length=10)

class ProxyModel(ConcreteModel):
    class Meta:
        proxy = True

class FooWithoutUrl(models.Model):
    """
    Fake model not defining ``get_absolute_url`` for
@@ -114,6 +121,87 @@ class ContentTypesTests(TestCase):
            FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
        })

    def test_get_for_concrete_model(self):
        """
        Make sure the `for_concrete_model` kwarg correctly works
        with concrete, proxy and deferred models
        """
        concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel)

        self.assertEqual(concrete_model_ct,
            ContentType.objects.get_for_model(ProxyModel))

        self.assertEqual(concrete_model_ct,
            ContentType.objects.get_for_model(ConcreteModel,
                                              for_concrete_model=False))

        proxy_model_ct = ContentType.objects.get_for_model(ProxyModel,
                                                           for_concrete_model=False)

        self.assertNotEqual(concrete_model_ct, proxy_model_ct)

        # Make sure deferred model are correctly handled
        ConcreteModel.objects.create(name="Concrete")
        DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__
        DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__

        self.assertEqual(concrete_model_ct,
            ContentType.objects.get_for_model(DeferredConcreteModel))

        self.assertEqual(concrete_model_ct,
            ContentType.objects.get_for_model(DeferredConcreteModel,
                                              for_concrete_model=False))
        
        self.assertEqual(concrete_model_ct,
            ContentType.objects.get_for_model(DeferredProxyModel))

        self.assertEqual(proxy_model_ct,
            ContentType.objects.get_for_model(DeferredProxyModel,
                                              for_concrete_model=False))
        
    def test_get_for_concrete_models(self):
        """
        Make sure the `for_concrete_models` kwarg correctly works
        with concrete, proxy and deferred models.
        """
        concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel)

        cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel)
        self.assertEqual(cts, {
            ConcreteModel: concrete_model_ct,
            ProxyModel: concrete_model_ct,
        })

        proxy_model_ct = ContentType.objects.get_for_model(ProxyModel,
                                                           for_concrete_model=False)
        cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel,
                                                 for_concrete_models=False)
        self.assertEqual(cts, {
            ConcreteModel: concrete_model_ct,
            ProxyModel: proxy_model_ct,
        })

        # Make sure deferred model are correctly handled
        ConcreteModel.objects.create(name="Concrete")
        DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__
        DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__
        
        cts = ContentType.objects.get_for_models(DeferredConcreteModel,
                                                 DeferredProxyModel)
        self.assertEqual(cts, {
            DeferredConcreteModel: concrete_model_ct,
            DeferredProxyModel: concrete_model_ct,
        })

        cts = ContentType.objects.get_for_models(DeferredConcreteModel,
                                                 DeferredProxyModel,
                                                 for_concrete_models=False)
        self.assertEqual(cts, {
            DeferredConcreteModel: concrete_model_ct,
            DeferredProxyModel: proxy_model_ct,
        })
        

    def test_shortcut_view(self):
        """
        Check that the shortcut view (used for the admin "view on site"
+15 −2
Original line number Diff line number Diff line
@@ -187,13 +187,13 @@ The ``ContentTypeManager``
        probably won't ever need to call this method yourself; Django will call
        it automatically when it's needed.

    .. method:: get_for_model(model)
    .. method:: get_for_model(model[, for_concrete_model=True])

        Takes either a model class or an instance of a model, and returns the
        :class:`~django.contrib.contenttypes.models.ContentType` instance
        representing that model.

    .. method:: get_for_models(*models)
    .. method:: get_for_models(*models[, for_concrete_models=True])

        Takes a variadic number of model classes, and returns a dictionary
        mapping the model classes to the
@@ -224,6 +224,19 @@ lookup::

.. _generic-relations:

.. versionadded:: 1.5

Prior to Django 1.5 :meth:`~ContentTypeManager.get_for_model()` and 
:meth:`~ContentTypeManager.get_for_models()` always returned the 
:class:`~django.contrib.contenttypes.models.ContentType` associated with the 
concrete model of the specified one(s). That means there was no way to retreive 
the :class:`~django.contrib.contenttypes.models.ContentType` of a proxy model 
using those methods. As of Django 1.5 you can now pass a boolean flag – 
respectively ``for_concrete_model`` and ``for_concrete_models`` – to specify
wether or not you want to retreive the 
:class:`~django.contrib.contenttypes.models.ContentType` for the concrete or 
direct model.

Generic relations
=================

+10 −0
Original line number Diff line number Diff line
@@ -69,6 +69,16 @@ To make it easier to deal with javascript templates which collide with Django's
syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the
tag's content.

Retreival of ``ContentType`` instances associated with proxy models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The methods :meth:`ContentTypeManager.get_for_model() <django.contrib.contenttypes.models.ContentTypeManager.get_for_model()>` 
and :meth:`ContentTypeManager.get_for_models() <django.contrib.contenttypes.models.ContentTypeManager.get_for_models()>` 
have a new keyword argument – respectively ``for_concrete_model`` and ``for_concrete_models``. 
By passing ``False`` using this argument it is now possible to retreive the
:class:`ContentType <django.contrib.contenttypes.models.ContentType>` 
associated with proxy models.

Minor features
~~~~~~~~~~~~~~