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:
parent
90985048fc
commit
b6d533af4d
@ -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:
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
@ -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
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user