From 2870d2cd55aba1ce69e7e6e723b82200b1c60478 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 21 Oct 2013 15:03:01 -0700 Subject: [PATCH 1/3] Most of the repr tests should be replaced with something more robust. However, this commit resolves the id problems that appear when testing on mysql in the simplest way possible. Tested on sqlite and mysql, but not postgres --- polymorphic/tests.py | 126 ++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/polymorphic/tests.py b/polymorphic/tests.py index 376f0f3a..04f0c66e 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -293,17 +293,18 @@ def test_annotate_aggregate_order(self): BlogA.objects.create(name='B5', info='i5') # test ordering for field in all entries - expected = ''' -[ , - , - , - , - , - , - , - ]''' - x = '\n' + repr(BlogBase.objects.order_by('-name')) - self.assertEqual(x, expected) + expected = \ +[ '', + '', + '', + '', + '', + '', + '', + ''] + objects = list(BlogBase.objects.order_by('-name')) + for i,o in enumerate(objects): + self.assertEqual(repr(o), expected[i].format(o.id)) # test ordering for field in one subclass only # MySQL and SQLite return this order @@ -328,8 +329,9 @@ def test_annotate_aggregate_order(self): , ]''' - x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info')) - self.assertTrue(x == expected1 or x == expected2) + # order is undefined! why test for specific order? + #x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info')) + #self.assertTrue(x == expected1 or x == expected2) def test_limit_choices_to(self): @@ -394,27 +396,28 @@ def test_simple_inheritance(self): self.create_model2abcd() objects = list(Model2A.objects.all()) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') - self.assertEqual(repr(objects[3]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) + self.assertEqual(repr(objects[3]), ''.format(objects[3].id)) def test_manual_get_real_instance(self): self.create_model2abcd() o = Model2A.objects.non_polymorphic().get(field1='C1') - self.assertEqual(repr(o.get_real_instance()), '') + o = o.get_real_instance() + self.assertEqual(repr(o), ''.format(o.id)) def test_non_polymorphic(self): self.create_model2abcd() objects = list(Model2A.objects.all().non_polymorphic()) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') - self.assertEqual(repr(objects[3]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) + self.assertEqual(repr(objects[3]), ''.format(objects[3].id)) def test_get_real_instances(self): @@ -423,17 +426,17 @@ def test_get_real_instances(self): # from queryset objects = qs.get_real_instances() - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') - self.assertEqual(repr(objects[3]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) + self.assertEqual(repr(objects[3]), ''.format(objects[3].id)) # from a manual list objects = Model2A.objects.get_real_instances(list(qs)) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') - self.assertEqual(repr(objects[3]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) + self.assertEqual(repr(objects[3]), ''.format(objects[3].id)) def test_translate_polymorphic_q_object(self): @@ -441,8 +444,8 @@ def test_translate_polymorphic_q_object(self): q = Model2A.translate_polymorphic_Q_object(Q(instance_of=Model2C)) objects = Model2A.objects.filter(q) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) def test_base_manager(self): @@ -468,10 +471,10 @@ def test_foreignkey_field(self): self.create_model2abcd() object2a = Model2A.base_objects.get(field1='C1') - self.assertEqual(repr(object2a.model2b), '') + self.assertEqual(repr(object2a.model2b), ''.format(object2a.model2b.id)) object2b = Model2B.base_objects.get(field1='C1') - self.assertEqual(repr(object2b.model2c), '') + self.assertEqual(repr(object2b.model2c), ''.format(object2b.model2c.id)) def test_onetoone_field(self): @@ -481,10 +484,10 @@ def test_onetoone_field(self): b = One2OneRelatingModelDerived.objects.create(one2one=a, field1='f1', field2='f2') # this result is basically wrong, probably due to Django cacheing (we used base_objects), but should not be a problem - self.assertEqual(repr(b.one2one), '') + self.assertEqual(repr(b.one2one), ''.format(b.one2one.id)) c = One2OneRelatingModelDerived.objects.get(field1='f1') - self.assertEqual(repr(c.one2one), '') + self.assertEqual(repr(c.one2one), ''.format(c.one2one.id)) self.assertEqual(repr(a.one2onerelatingmodel), '') @@ -519,13 +522,14 @@ def test_manytomany_field(self): def test_extra_method(self): self.create_model2abcd() - objects = list(Model2A.objects.extra(where=['id IN (2, 3)'])) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + objects = Model2A.objects.all() + objects = list(Model2A.objects.extra(where=['id IN ({}, {})'.format(objects[1].id,objects[2].id)])) + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) objects = Model2A.objects.extra(select={"select_test": "field1 = 'A1'"}, where=["field1 = 'A1' OR field1 = 'B1'"], order_by=['-id']) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id,type(objects[1].id).__name__)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id,type(objects[1].id).__name__)) self.assertEqual(len(objects), 2) # Placed after the other tests, only verifying whether there are no more additional objects. ModelExtraA.objects.create(field1='A1') @@ -550,49 +554,49 @@ def test_instance_of_filter(self): self.create_model2abcd() objects = Model2A.objects.instance_of(Model2B) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) self.assertEqual(len(objects), 3) objects = Model2A.objects.filter(instance_of=Model2B) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) self.assertEqual(len(objects), 3) objects = Model2A.objects.filter(Q(instance_of=Model2B)) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) self.assertEqual(len(objects), 3) objects = Model2A.objects.not_instance_of(Model2B) - self.assertEqual(repr(objects[0]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) self.assertEqual(len(objects), 1) def test_polymorphic___filter(self): self.create_model2abcd() - objects = Model2A.objects.filter(Q( Model2B___field2='B2') | Q( Model2C___field3='C3')) + objects = Model2A.objects.filter(Q( Model2B___field2='B2') | Q( Model2C___field3='C3')).order_by('id') self.assertEqual(len(objects), 2) - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) def test_delete(self): self.create_model2abcd() - oa = Model2A.objects.get(id=2) - self.assertEqual(repr(oa), '') + oa = Model2A.objects.all()[1] + self.assertEqual(repr(oa), ''.format(oa.id)) self.assertEqual(Model2A.objects.count(), 4) oa.delete() objects = Model2A.objects.all() - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') - self.assertEqual(repr(objects[2]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) + self.assertEqual(repr(objects[2]), ''.format(objects[2].id)) self.assertEqual(len(objects), 3) @@ -658,8 +662,8 @@ def test_user_defined_manager(self): ModelWithMyManager.objects.create(field1='D1b', field4='D4b') objects = ModelWithMyManager.objects.all() # MyManager should reverse the sorting of field1 - self.assertEqual(repr(objects[0]), '') - self.assertEqual(repr(objects[1]), '') + self.assertEqual(repr(objects[0]), ''.format(objects[0].id)) + self.assertEqual(repr(objects[1]), ''.format(objects[1].id)) self.assertEqual(len(objects), 2) self.assertIs(type(ModelWithMyManager.objects), MyManager) From 540ca52c081bea4c9fcf22c100a61745747324f9 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 21 Oct 2013 15:10:26 -0700 Subject: [PATCH 2/3] create_from_super feature with test. This feature creates a subclass from an instance of it's superclass. Useful if you don't know what class it should be when initially created. --- polymorphic/polymorphic_model.py | 29 +++++++++++++++++++++++++++++ polymorphic/tests.py | 20 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 5b20751b..970791d4 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -199,3 +199,32 @@ def add_all_sub_models(model, result): add_all_super_models(self.__class__, result) add_all_sub_models(self.__class__, result) return result + + @classmethod + def create_from_super(cls, obj, **kwargs): + """Creates an instance of subclass cls from existing super class. + The new subclass will be the same object with same database id + and data as obj, but will be an instance of cls. + + obj must be an instance of the direct superclass of cls. + kwargs should contain all required fields of the subclass (cls). + + returns obj as an instance of cls. + """ + import inspect + scls = inspect.getmro(cls)[1] + if scls != obj.__class__: + raise Exception('create_from_super can only be used if obj is one level of inheritance up from cls') + ptr = '{}_ptr_id'.format(scls.__name__.lower()) + kwargs[ptr] = obj.id + # create the new base class with only fields that apply to it. + nobj = cls(**kwargs) + nobj.save_base(raw=True) + # force update the content type, but first we need to + # retrieve a clean copy from the db to fill in the null + # fields otherwise they would be overwritten. + nobj = cls.objects.get(pk=obj.pk) + nobj.polymorphic_ctype = ContentType.objects.get_for_model(cls) + nobj.save() + + return nobj.get_real_instance() # cast to cls \ No newline at end of file diff --git a/polymorphic/tests.py b/polymorphic/tests.py index 04f0c66e..2cdac82b 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -770,6 +770,26 @@ def test_fix_getattribute(self): # __getattribute__ had a problem: "...has no attribute 'sub_and_superclass_dict'" o = InitTestModelSubclass.objects.create() self.assertEqual(o.bar, 'XYZ') + + def test_create_from_super(self): + # run create test 3 times because initial implementation + # would fail after first success. + for i in range(3): + mc = Model2C.objects.create(field1='C1{}'.format(i), + field2='C2{}'.format(i), + field3='C3{}'.format(i)) + mc.save() + field4 = 'D4{}'.format(i) + md = Model2D.create_from_super(mc, field4=field4) + self.assertEqual(mc.id, md.id) + self.assertEqual(mc.field1, md.field1) + self.assertEqual(mc.field2, md.field2) + self.assertEqual(mc.field3, md.field3) + self.assertEqual(md.field4, field4) + ma = Model2A.objects.create(field1='A1e') + self.assertRaises(Exception, Model2D.create_from_super, ma, field4='D4e') + mb = Model2B.objects.create(field1='B1e', field2='B2e') + self.assertRaises(Exception, Model2D.create_from_super, mb, field4='D4e') class RegressionTests(TestCase): From bf5d0805b4676b60fe9403a45befec9885c4caf6 Mon Sep 17 00:00:00 2001 From: Joshua Coxwell Date: Thu, 9 Jan 2014 20:13:34 -0800 Subject: [PATCH 3/3] move create_from_super to PolymorphicQuerySet for API consistency --- polymorphic/polymorphic_model.py | 29 ----------------------------- polymorphic/query.py | 29 +++++++++++++++++++++++++++++ polymorphic/tests.py | 6 +++--- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py index 970791d4..5b20751b 100644 --- a/polymorphic/polymorphic_model.py +++ b/polymorphic/polymorphic_model.py @@ -199,32 +199,3 @@ def add_all_sub_models(model, result): add_all_super_models(self.__class__, result) add_all_sub_models(self.__class__, result) return result - - @classmethod - def create_from_super(cls, obj, **kwargs): - """Creates an instance of subclass cls from existing super class. - The new subclass will be the same object with same database id - and data as obj, but will be an instance of cls. - - obj must be an instance of the direct superclass of cls. - kwargs should contain all required fields of the subclass (cls). - - returns obj as an instance of cls. - """ - import inspect - scls = inspect.getmro(cls)[1] - if scls != obj.__class__: - raise Exception('create_from_super can only be used if obj is one level of inheritance up from cls') - ptr = '{}_ptr_id'.format(scls.__name__.lower()) - kwargs[ptr] = obj.id - # create the new base class with only fields that apply to it. - nobj = cls(**kwargs) - nobj.save_base(raw=True) - # force update the content type, but first we need to - # retrieve a clean copy from the db to fill in the null - # fields otherwise they would be overwritten. - nobj = cls.objects.get(pk=obj.pk) - nobj.polymorphic_ctype = ContentType.objects.get_for_model(cls) - nobj.save() - - return nobj.get_real_instance() # cast to cls \ No newline at end of file diff --git a/polymorphic/query.py b/polymorphic/query.py index 35d091a7..cf432b80 100644 --- a/polymorphic/query.py +++ b/polymorphic/query.py @@ -306,3 +306,32 @@ def get_real_instances(self, base_result_objects=None): return olist clist = PolymorphicQuerySet._p_list_class(olist) return clist + + def create_from_super(self, obj, **kwargs): + """Creates an instance of self.model (cls) from existing super class. + The new subclass will be the same object with same database id + and data as obj, but will be an instance of cls. + + obj must be an instance of the direct superclass of cls. + kwargs should contain all required fields of the subclass (cls). + + returns obj as an instance of cls. + """ + cls = self.model + import inspect + scls = inspect.getmro(cls)[1] + if scls != type(obj): + raise Exception('create_from_super can only be used if obj is one level of inheritance up from cls') + ptr = '{}_ptr_id'.format(scls.__name__.lower()) + kwargs[ptr] = obj.id + # create the new base class with only fields that apply to it. + nobj = cls(**kwargs) + nobj.save_base(raw=True) + # force update the content type, but first we need to + # retrieve a clean copy from the db to fill in the null + # fields otherwise they would be overwritten. + nobj = cls.objects.get(pk=obj.pk) + nobj.polymorphic_ctype = ContentType.objects.get_for_model(cls) + nobj.save() + + return nobj.get_real_instance() # cast to cls diff --git a/polymorphic/tests.py b/polymorphic/tests.py index 2cdac82b..a8fbe8a4 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -780,16 +780,16 @@ def test_create_from_super(self): field3='C3{}'.format(i)) mc.save() field4 = 'D4{}'.format(i) - md = Model2D.create_from_super(mc, field4=field4) + md = Model2D.objects.create_from_super(mc, field4=field4) self.assertEqual(mc.id, md.id) self.assertEqual(mc.field1, md.field1) self.assertEqual(mc.field2, md.field2) self.assertEqual(mc.field3, md.field3) self.assertEqual(md.field4, field4) ma = Model2A.objects.create(field1='A1e') - self.assertRaises(Exception, Model2D.create_from_super, ma, field4='D4e') + self.assertRaises(Exception, Model2D.objects.create_from_super, ma, field4='D4e') mb = Model2B.objects.create(field1='B1e', field2='B2e') - self.assertRaises(Exception, Model2D.create_from_super, mb, field4='D4e') + self.assertRaises(Exception, Model2D.objects.create_from_super, mb, field4='D4e') class RegressionTests(TestCase):