Support proxy models (still requires one query per proxied model, not optimal)

fix_request_path_info
Jedediah Smith 2012-11-14 17:24:52 -05:00 committed by Diederik van der Boor
parent 12e6278741
commit e2cfbf3898
5 changed files with 60 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
)