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]
|
version += ' %s' % VERSION[3]
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
# Proxied models need to have it's own ContentType
|
|
||||||
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentTypeManager
|
from django.contrib.contenttypes.models import ContentTypeManager
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
|
|
||||||
|
|
||||||
def get_for_proxied_model(self, model):
|
# 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
|
||||||
Returns the ContentType object for a given model, creating the
|
# in MonkeyPatchTests that checks for this.
|
||||||
ContentType if necessary. Lookups are cached so that subsequent lookups
|
# https://code.djangoproject.com/ticket/18399
|
||||||
for the same model don't hit the database.
|
|
||||||
"""
|
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
|
opts = model._meta
|
||||||
key = (opts.app_label, opts.object_name.lower())
|
|
||||||
try:
|
try:
|
||||||
ct = self.__class__._cache[self.db][key]
|
ct = self._get_from_cache(opts)
|
||||||
except KeyError:
|
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(
|
ct, created = self.get_or_create(
|
||||||
app_label = opts.app_label,
|
app_label = opts.app_label,
|
||||||
model = opts.object_name.lower(),
|
model = opts.object_name.lower(),
|
||||||
defaults = {'name': smart_unicode(opts.verbose_name_raw)},
|
defaults = {'name': smart_unicode(opts.verbose_name_raw)},
|
||||||
)
|
)
|
||||||
self._add_to_cache(self.db, ct)
|
self._add_to_cache(self.db, ct)
|
||||||
|
|
||||||
return 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)
|
(used by PolymorphicQuerySet._get_real_instances)
|
||||||
"""
|
"""
|
||||||
if not self.polymorphic_ctype_id:
|
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):
|
def save(self, *args, **kwargs):
|
||||||
"""Overridden model save function which supports the polymorphism
|
"""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"
|
# - also record the correct result order in "ordered_id_list"
|
||||||
# - store objects that already have the correct class into "results"
|
# - store objects that already have the correct class into "results"
|
||||||
base_result_objects_by_id = {}
|
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:
|
for base_object in base_result_objects:
|
||||||
ordered_id_list.append(base_object.pk)
|
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'
|
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
|
||||||
|
|
||||||
def q_class_with_subclasses(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__():
|
for subclass in model.__subclasses__():
|
||||||
q = q | q_class_with_subclasses(subclass)
|
q = q | q_class_with_subclasses(subclass)
|
||||||
return q
|
return q
|
||||||
|
|
|
||||||
|
|
@ -749,3 +749,37 @@ class RegressionTests(TestCase):
|
||||||
expected_queryset = [bottom]
|
expected_queryset = [bottom]
|
||||||
self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset])
|
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