Support proxy models (still requires one query per proxied model, not optimal)
parent
12e6278741
commit
e2cfbf3898
|
|
@ -25,33 +25,37 @@ def get_version():
|
|||
version += ' %s' % VERSION[3]
|
||||
return version
|
||||
|
||||
|
||||
# Proxied models need to have it's own ContentType
|
||||
|
||||
|
||||
from django.contrib.contenttypes.models import ContentTypeManager
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
|
||||
def get_for_proxied_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.
|
||||
"""
|
||||
# Monkey-patch Django to allow ContentTypes for proxy models. This is compatible with an
|
||||
# upcoming change in Django 1.5 and should be removed when we upgrade. There is a test
|
||||
# in MonkeyPatchTests that checks for this.
|
||||
# https://code.djangoproject.com/ticket/18399
|
||||
|
||||
def get_for_model(self, model, for_concrete_model=True):
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
if for_concrete_model:
|
||||
model = model._meta.concrete_model
|
||||
elif model._deferred:
|
||||
model = model._meta.proxy_for_model
|
||||
|
||||
opts = model._meta
|
||||
key = (opts.app_label, opts.object_name.lower())
|
||||
|
||||
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
|
||||
# django.utils.functional.__proxy__ object.
|
||||
ct, created = self.get_or_create(
|
||||
app_label=opts.app_label,
|
||||
model=opts.object_name.lower(),
|
||||
defaults={'name': smart_unicode(opts.verbose_name_raw)},
|
||||
app_label = opts.app_label,
|
||||
model = opts.object_name.lower(),
|
||||
defaults = {'name': smart_unicode(opts.verbose_name_raw)},
|
||||
)
|
||||
self._add_to_cache(self.db, ct)
|
||||
|
||||
return ct
|
||||
ContentTypeManager.get_for_proxied_model = get_for_proxied_model
|
||||
|
||||
ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model
|
||||
ContentTypeManager.get_for_model = get_for_model
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class PolymorphicModel(models.Model):
|
|||
(used by PolymorphicQuerySet._get_real_instances)
|
||||
"""
|
||||
if not self.polymorphic_ctype_id:
|
||||
self.polymorphic_ctype = ContentType.objects.get_for_model(self)
|
||||
self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overridden model save function which supports the polymorphism
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ class PolymorphicQuerySet(QuerySet):
|
|||
# - also record the correct result order in "ordered_id_list"
|
||||
# - store objects that already have the correct class into "results"
|
||||
base_result_objects_by_id = {}
|
||||
self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk
|
||||
self_model_content_type_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk
|
||||
for base_object in base_result_objects:
|
||||
ordered_id_list.append(base_object.pk)
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ def _create_model_filter_Q(modellist, not_instance_of=False):
|
|||
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
|
||||
|
||||
def q_class_with_subclasses(model):
|
||||
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model))
|
||||
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model, for_concrete_model=False))
|
||||
for subclass in model.__subclasses__():
|
||||
q = q | q_class_with_subclasses(subclass)
|
||||
return q
|
||||
|
|
|
|||
|
|
@ -749,3 +749,37 @@ class RegressionTests(TestCase):
|
|||
expected_queryset = [bottom]
|
||||
self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset])
|
||||
|
||||
|
||||
class MonkeyPatchTests(TestCase):
|
||||
|
||||
def test_content_types_for_proxy_models_patch(self):
|
||||
from django.db.models import Model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
class Base(Model):
|
||||
pass
|
||||
|
||||
class Proxy(Base):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
ct = ContentType.objects.get_for_model(Proxy, for_concrete_model=False)
|
||||
self.assertEqual(Proxy, ct.model_class())
|
||||
|
||||
def test_content_types_for_proxy_models_patch_still_required(self):
|
||||
"""
|
||||
If this test fails then our monkey patch of ContentTypeManager.get_for_model
|
||||
is no longer required and should be removed
|
||||
"""
|
||||
from django.db.models import Model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
class MyModel(Model):
|
||||
pass
|
||||
|
||||
self.assertRaisesMessage(
|
||||
TypeError,
|
||||
"get_for_model() got an unexpected keyword argument 'for_concrete_model'",
|
||||
ContentType.objects.get_for_model__original,
|
||||
MyModel, for_concrete_model=False
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue