From ca4067e2797759d1a12676cb756ada2c32df5a8e Mon Sep 17 00:00:00 2001 From: Diederik van der Boor Date: Fri, 5 Apr 2013 13:14:36 +0200 Subject: [PATCH 1/2] Add proxy model test that fails in Django 1.5 --- polymorphic/tests.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/polymorphic/tests.py b/polymorphic/tests.py index 7d62d33..c6188e0 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -178,6 +178,17 @@ class UUIDPlainB(UUIDPlainA): class UUIDPlainC(UUIDPlainB): field3 = models.CharField(max_length=10) +# base -> proxy -> real models +class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel): + name = models.CharField(max_length=10) +class ProxyModelBase(ProxiedBase): + class Meta: + proxy = True +class ProxyModelA(ProxyModelBase): + field1 = models.CharField(max_length=10) +class ProxyModelB(ProxyModelBase): + field2 = models.CharField(max_length=10) + # test bad field name #class TestBadFieldModel(ShowFieldType, PolymorphicModel): @@ -194,7 +205,6 @@ class PolymorphicTests(TestCase): """ The test suite """ - def test_diamond_inheritance(self): # Django diamond problem o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y') @@ -613,6 +623,37 @@ class PolymorphicTests(TestCase): self.assertEqual(repr(type(MROBase2._default_manager)), "") + def test_proxy_model_inheritance(self): + """ + Polymorphic abilities should also work when the base model is a proxy object. + """ + # The managers should point to the proper objects. + # otherwise, the whole excersise is pointless. + self.assertEqual(ProxiedBase.objects.model, ProxiedBase) + self.assertEqual(ProxyModelBase.objects.model, ProxyModelBase) + self.assertEqual(ProxyModelA.objects.model, ProxyModelA) + self.assertEqual(ProxyModelB.objects.model, ProxyModelB) + + # Create objects + ProxyModelA.objects.create(name="object1") + ProxyModelB.objects.create(name="object2", field2="bb") + + # Getting single objects + object1 = ProxyModelBase.objects.get(name='object1') + object2 = ProxyModelBase.objects.get(name='object2') + self.assertEqual(repr(object1), '') + self.assertEqual(repr(object2), '') + self.assertIsInstance(object1, ProxyModelA) + self.assertIsInstance(object2, ProxyModelB) + + # Same for lists + objects = list(ProxyModelBase.objects.all().order_by('name')) + self.assertEqual(repr(objects[0]), '') + self.assertEqual(repr(objects[1]), '') + self.assertIsInstance(objects[0], ProxyModelA) + self.assertIsInstance(objects[1], ProxyModelB) + + def test_fix_getattribute(self): ### fixed issue in PolymorphicModel.__getattribute__: field name same as model name o = ModelFieldNameTest.objects.create(modelfieldnametest='1') From 1f263026322d452322d220f665848e3d15676e5f Mon Sep 17 00:00:00 2001 From: Diederik van der Boor Date: Sun, 7 Apr 2013 00:28:57 +0200 Subject: [PATCH 2/2] Fix Django 1.5 support, tests pass again. The reason polymorphic broke was because it couldn't find some managers anymore in the inheritance tree. Django 1.5 removes these and replaces them with an `AbstractManagerDescriptor`. This patch restores those objects --- polymorphic/base.py | 29 ++++++++++++++++++++++++++--- polymorphic/polymorphic_model.py | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/polymorphic/base.py b/polymorphic/base.py index 334f542..a7db9b1 100644 --- a/polymorphic/base.py +++ b/polymorphic/base.py @@ -16,6 +16,11 @@ from query import PolymorphicQuerySet # These are forbidden as field names (a descriptive exception is raised) POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of'] +try: + from django.db.models.manager import AbstractManagerDescriptor # Django 1.5 +except ImportError: + AbstractManagerDescriptor = None + ################################################################################### ### PolymorphicModel meta class @@ -91,6 +96,7 @@ class PolymorphicModelBase(ModelBase): use correct mro, only use managers with _inherited==False (they are of no use), skip managers that are overwritten by the user with same-named class attributes (in attrs) """ + #print "** ", self.__name__ add_managers = [] add_managers_keys = set() for base in self.__mro__[1:]: @@ -102,9 +108,23 @@ class PolymorphicModelBase(ModelBase): for key, manager in base.__dict__.items(): if type(manager) == models.manager.ManagerDescriptor: manager = manager.manager + + if AbstractManagerDescriptor is not None: + # Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however, + # the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute. + # Pretend that the manager is still there, so all code works like it used to. + if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel': + model = manager.model + if key == 'objects': + manager = PolymorphicManager() + manager.model = model + elif key == 'base_objects': + manager = models.Manager() + manager.model = model + if not isinstance(manager, models.Manager): continue - if key in ['_base_manager']: + if key == '_base_manager': continue # let Django handle _base_manager if key in attrs: continue @@ -112,7 +132,8 @@ class PolymorphicModelBase(ModelBase): continue # manager with that name already added, skip if manager._inherited: continue # inherited managers (on the bases) have no significance, they are just copies - #print >>sys.stderr,'##',self.__name__, key + #print '## {0} {1}'.format(self.__name__, key) + if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers self.validate_model_manager(manager, self.__name__, key) add_managers.append((base.__name__, key, manager)) @@ -121,16 +142,18 @@ class PolymorphicModelBase(ModelBase): @classmethod def get_first_user_defined_manager(self): + # See if there is a manager attribute directly stored at this inheritance level. mgr_list = [] for key, val in self.__dict__.items(): item = getattr(self, key) if not isinstance(item, models.Manager): continue mgr_list.append((item.creation_counter, key, item)) + # if there are user defined managers, use first one as _default_manager if mgr_list: _, manager_name, manager = sorted(mgr_list)[0] #sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n' - # .format( model=model_name, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) ) + # .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) ) return manager return None diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 76cecf9..baf986c 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -67,6 +67,8 @@ class PolymorphicModel(models.Model): # some applications want to know the name of the fields that are added to its models polymorphic_internal_model_fields = ['polymorphic_ctype'] + # Note that Django 1.5 removes these managers because the model is abstract. + # They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers() objects = PolymorphicManager() base_objects = models.Manager()