1
0
mirror of https://github.com/django/django.git synced 2025-04-01 12:06:43 +00:00

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.
This commit is contained in:
Simon Charette 2012-05-29 17:55:20 -04:00 committed by Anssi Kääriäinen
parent 90985048fc
commit b6d533af4d
4 changed files with 124 additions and 8 deletions

View File

@ -16,20 +16,24 @@ class ContentTypeManager(models.Manager):
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
return ct return ct
def _get_opts(self, model): def _get_opts(self, model, for_concrete_model):
return model._meta.concrete_model._meta 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): def _get_from_cache(self, opts):
key = (opts.app_label, opts.object_name.lower()) key = (opts.app_label, opts.object_name.lower())
return self.__class__._cache[self.db][key] 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 Returns the ContentType object for a given model, creating the
ContentType if necessary. Lookups are cached so that subsequent lookups ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database. for the same model don't hit the database.
""" """
opts = self._get_opts(model) opts = self._get_opts(model, for_concrete_model)
try: try:
ct = self._get_from_cache(opts) ct = self._get_from_cache(opts)
except KeyError: except KeyError:
@ -45,10 +49,11 @@ class ContentTypeManager(models.Manager):
return ct return ct
def get_for_models(self, *models): def get_for_models(self, *models, **kwargs):
""" """
Given *models, returns a dictionary mapping {model: content_type}. Given *models, returns a dictionary mapping {model: content_type}.
""" """
for_concrete_models = kwargs.pop('for_concrete_models', True)
# Final results # Final results
results = {} results = {}
# models that aren't already in the cache # models that aren't already in the cache
@ -56,7 +61,7 @@ class ContentTypeManager(models.Manager):
needed_models = set() needed_models = set()
needed_opts = set() needed_opts = set()
for model in models: for model in models:
opts = self._get_opts(model) opts = self._get_opts(model, for_concrete_models)
try: try:
ct = self._get_from_cache(opts) ct = self._get_from_cache(opts)
except KeyError: except KeyError:

View File

@ -11,6 +11,13 @@ from django.test import TestCase
from django.utils.encoding import smart_str 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): class FooWithoutUrl(models.Model):
""" """
Fake model not defining ``get_absolute_url`` for Fake model not defining ``get_absolute_url`` for
@ -114,6 +121,87 @@ class ContentTypesTests(TestCase):
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), 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): def test_shortcut_view(self):
""" """
Check that the shortcut view (used for the admin "view on site" Check that the shortcut view (used for the admin "view on site"

View File

@ -187,13 +187,13 @@ The ``ContentTypeManager``
probably won't ever need to call this method yourself; Django will call probably won't ever need to call this method yourself; Django will call
it automatically when it's needed. 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 Takes either a model class or an instance of a model, and returns the
:class:`~django.contrib.contenttypes.models.ContentType` instance :class:`~django.contrib.contenttypes.models.ContentType` instance
representing that model. 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 Takes a variadic number of model classes, and returns a dictionary
mapping the model classes to the mapping the model classes to the
@ -224,6 +224,19 @@ lookup::
.. _generic-relations: .. _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 Generic relations
================= =================

View File

@ -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 syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the
tag's content. 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 Minor features
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~