From 3e718d305e153800a1700b627169e7940ea49504 Mon Sep 17 00:00:00 2001 From: hottwaj Date: Tue, 18 Feb 2014 18:00:18 +0000 Subject: [PATCH 1/4] Changed methods used for traversing subclasses and superclasses of a given model. Now uses model._meta.parents to determine superclasses. _meta.parents is a dict of superclass: field_to_superclass pairs. By using the field name of field_to_superclass, we can work out the field to use the django_polymorphic accessor on, even if a user-specified OneToOneField to parent is is used (with parent_link=True) to get to the parent in the inheritance hierarachy. For subclasses, the path to the subclasses can be manually specified by the user if they use a 'related_name' on the OneToOneField from the subclass to the superclass. I have changed the code to also support 'related_name' if present --- polymorphic/polymorphic_model.py | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 5b20751..c283841 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -173,27 +173,34 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): """helper function for __init__: determine names of all Django inheritance accessor member functions for type(self)""" - def add_model(model, as_ptr, result): - name = model.__name__.lower() - if as_ptr: - name += '_ptr' - result[name] = model + def add_model(model, field_name, result): + result[field_name] = model - def add_model_if_regular(model, as_ptr, result): + def add_model_if_regular(model, field_name, result): if (issubclass(model, models.Model) and model != models.Model and model != self.__class__ and model != PolymorphicModel): - add_model(model, as_ptr, result) + add_model(model, field_name, result) - def add_all_super_models(model, result): - add_model_if_regular(model, True, result) - for b in model.__bases__: - add_all_super_models(b, result) + def add_all_super_models(model, result): + for super_cls, field_to_super in model._meta.parents.iteritems(): + field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' + add_model_if_regular(super_cls, field_name, result) + add_all_super_models(super_cls, result) - def add_all_sub_models(model, result): - for b in model.__subclasses__(): - add_model_if_regular(b, False, result) + def add_all_sub_models(super_cls, result): + for sub_cls in super_cls.__subclasses__(): #go through all subclasses of model + field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls + super_to_sub_related_field = field_to_super.rel + if super_to_sub_related_field.related_name is None: + #if related name is None the related field is the name of the subclass + to_subclass_fieldname = sub_cls.__name__.lower() + else: + #otherwise use the given related name + to_subclass_fieldname = super_to_sub_related_field.related_name + + add_model_if_regular(sub_cls, to_subclass_fieldname, result) result = {} add_all_super_models(self.__class__, result) From fd0ed96c1ad4a09713dea74c1a29cac4ade4c127 Mon Sep 17 00:00:00 2001 From: hottwaj Date: Wed, 19 Feb 2014 10:56:02 +0000 Subject: [PATCH 2/4] Fixed bugs in superclass/subclass field link processingso that this can cope with proxy models, as well as parent_link and related_name links to parents. Added a test case for testing parent_link and related_name links to parent. All tests pass should pass now (at least they do for me on django 1.5 and python 2.7) --- polymorphic/polymorphic_model.py | 31 +++++++++++++++++-------------- polymorphic/tests.py | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index c283841..10e95a2 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -22,7 +22,7 @@ from django.utils import six from .base import PolymorphicModelBase from .manager import PolymorphicManager from .query_translate import translate_polymorphic_Q_object - +import pdb ################################################################################### ### PolymorphicModel @@ -185,22 +185,25 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): def add_all_super_models(model, result): for super_cls, field_to_super in model._meta.parents.iteritems(): - field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' - add_model_if_regular(super_cls, field_name, result) - add_all_super_models(super_cls, result) + if field_to_super is not None: #if not a link to a proxy model + field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' + add_model_if_regular(super_cls, field_name, result) + add_all_super_models(super_cls, result) def add_all_sub_models(super_cls, result): for sub_cls in super_cls.__subclasses__(): #go through all subclasses of model - field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls - super_to_sub_related_field = field_to_super.rel - if super_to_sub_related_field.related_name is None: - #if related name is None the related field is the name of the subclass - to_subclass_fieldname = sub_cls.__name__.lower() - else: - #otherwise use the given related name - to_subclass_fieldname = super_to_sub_related_field.related_name - - add_model_if_regular(sub_cls, to_subclass_fieldname, result) + if super_cls in sub_cls._meta.parents: #super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model + field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls + if field_to_super is not None: # if filed_to_super is not a link to a proxy model + super_to_sub_related_field = field_to_super.rel + if super_to_sub_related_field.related_name is None: + #if related name is None the related field is the name of the subclass + to_subclass_fieldname = sub_cls.__name__.lower() + else: + #otherwise use the given related name + to_subclass_fieldname = super_to_sub_related_field.related_name + + add_model_if_regular(sub_cls, to_subclass_fieldname, result) result = {} add_all_super_models(self.__class__, result) diff --git a/polymorphic/tests.py b/polymorphic/tests.py index 376f0f3..6177a49 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -245,6 +245,10 @@ class ProxyModelB(ProxyModelBase): class RelatedNameClash(ShowFieldType, PolymorphicModel): ctype = models.ForeignKey(ContentType, null=True, editable=False) +#class with a parent_link to superclass, and a related_name back to subclass +class TestParentLinkAndRelatedName(ModelShow1_plain): + superclass = models.OneToOneField(ModelShow1_plain, parent_link=True, related_name = 'related_name_subclass') + class PolymorphicTests(TestCase): """ @@ -767,6 +771,24 @@ class PolymorphicTests(TestCase): o = InitTestModelSubclass.objects.create() self.assertEqual(o.bar, 'XYZ') + def test_parent_link_and_related_name(self): + t = TestParentLinkAndRelatedName(field1 = "TestParentLinkAndRelatedName") + t.save() + p = ModelShow1_plain.objects.get(field1 = "TestParentLinkAndRelatedName") + + #check that p is equal to the + self.assertIsInstance(p, TestParentLinkAndRelatedName) + self.assertEqual(p, t) + + #check that the accessors to parent and sublass work correctly and return the right object + p = ModelShow1_plain.objects.non_polymorphic().get(field1 = "TestParentLinkAndRelatedName") + self.assertNotEqual(p, t) #p should be Plain1 and t TestParentLinkAndRelatedName, so not equal + self.assertEqual(p, t.superclass) + self.assertEqual(p.related_name_subclass, t) + + #test that we can delete t + t.delete() + class RegressionTests(TestCase): From 578aa8f1dd14027f2b37651f342b9f905f741485 Mon Sep 17 00:00:00 2001 From: hottwaj Date: Wed, 19 Feb 2014 11:00:19 +0000 Subject: [PATCH 3/4] removed pdb import which I used for testing... --- polymorphic/polymorphic_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 10e95a2..3256b0d 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -22,7 +22,6 @@ from django.utils import six from .base import PolymorphicModelBase from .manager import PolymorphicManager from .query_translate import translate_polymorphic_Q_object -import pdb ################################################################################### ### PolymorphicModel From 02221d7d10ef9c75aafbda1a9ed90e23cf1ce8e6 Mon Sep 17 00:00:00 2001 From: hottwaj Date: Wed, 19 Feb 2014 11:05:57 +0000 Subject: [PATCH 4/4] changed dict.iteritems() call to dict.items() for python 3 compatibility... --- polymorphic/polymorphic_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 3256b0d..ce1aeee 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -183,7 +183,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): add_model(model, field_name, result) def add_all_super_models(model, result): - for super_cls, field_to_super in model._meta.parents.iteritems(): + for super_cls, field_to_super in model._meta.parents.items(): if field_to_super is not None: #if not a link to a proxy model field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' add_model_if_regular(super_cls, field_name, result)