1

Lets say I have classes in the form:

class A(models.Model):
   attrA = models.CharField()
class B(A):
   attrB = models.CharField()
class C(A):
   attrC = models.CharField()

And then I create a instnace of B:

b = B()

Now, based on some decisions I wanted to transform that object b an instance of the C class but with the attrC attribute available. Is that possible?

Patrick Bassut
  • 3,310
  • 5
  • 31
  • 54

2 Answers2

2

In python you can change class of an object like that:

b.__class__=C

Then all of B's attributes are available even when they are not defined for class C. Altough b is now instance of the class C it has no C's attributes. Before saving the object to database (or calling other methods of Model class) you have to add all remaining attributes of the class C. To prove it works I created a simple app. Here are my models:

class A(models.Model):
    attrA = models.CharField(max_length=128)
    class Meta:
        abstract=True
class B(A):
    attrB = models.CharField(max_length=128)
class C(A):
    attrC = models.CharField(max_length=128)

And here are my tests:

class ABCTestCase(TestCase):
    def test_changing_classes(self):
        """Changing classes"""
        a = A()
        self.assertIsInstance(a, A)
        a.attrA='bacon'
        self.assertEqual(a.attrA, 'bacon')
        a.__class__=B
        self.assertIsInstance(a, B)
        self.assertEqual(a.attrA, 'bacon')
        a.attrB='spam'
        self.assertEqual(a.attrA, 'bacon')
        self.assertEqual(a.attrB, 'spam')
        a.__class__=C
        self.assertIsInstance(a, C)
        self.assertIsInstance(a, A)
        self.assertNotIsInstance(a, B)
        a.attrC='egg'
        self.assertEqual(a.attrA, 'bacon')
        self.assertEqual(a.attrB, 'spam')
        self.assertEqual(a.attrC, 'egg')
        a.id=None
        a.save()
        self.assertIsNotNone(a.id)

Result of the test is OK.

Safer approach is to define method for each class which convert from B to C or from C to B.

Jerzy Pawlikowski
  • 1,751
  • 19
  • 21
  • 1
    It's not as simple as it seems. That works(although with some possible damages) with regular python python code, not with django. – Patrick Bassut Nov 14 '13 at 02:55
  • 1
    Actually I tested it with django 1.6 and it works. After changing class you have all attributes from previous class but none from the new one. You have to add them manually. I am not saying this is the best approach in django just it works. – Jerzy Pawlikowski Nov 14 '13 at 04:48
  • did you saved it for, lets say, a mysql database(not sure if it's just mysql, but that's the one i'm using right now)? Cause the problem occurred with me when I was save the model. – Patrick Bassut Nov 14 '13 at 14:41
  • 1
    Before saving the object to database you have to add all remaining attributes of its new class. – Jerzy Pawlikowski Nov 14 '13 at 15:39
  • Did you do that? Did it go well? – Patrick Bassut Nov 14 '13 at 21:54
  • Thank you, it works! That will be really useful for those that care with design patterns. – Patrick Bassut Nov 15 '13 at 01:12
  • Could you check if when changing from one class to another if the previous object remains on your database? Here It doesn't. – Patrick Bassut Nov 28 '13 at 03:59
  • 1
    For this test I used django 1.6, python 2.7 and sqlite3. If I get an object from database, change its class, add new classes attributes and save then I have old record in previous table and new record in the latter. If you change class of object then whatever you do later should not affect table corresponding to the previous class. – Jerzy Pawlikowski Nov 30 '13 at 10:46
  • 1
    But in case you delete that new object(don't forget that this object references another object by foreignkey. Just like the older object), the delete will cause an error since it will try to delete the parent object that's referenced by someone else in other table. – Patrick Bassut Nov 30 '13 at 18:11
  • 1
    If the problem is with referencing other tables and foreign keys then try to change this behavior with on_delete argument when specifying ForeignKey field in model: https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete – Jerzy Pawlikowski Nov 30 '13 at 23:20
  • Actually, the problem is not the ability to delete it or not. But, when I list A, it will still show the object. Just as well when I list B. Right? – Patrick Bassut Dec 01 '13 at 02:51
  • If deleting object which exists in both tables is impossible due to database constraints then once you have saved it it will persist and listing any of classes will show it. Unless you change db behavior or delete all related objects (rows). – Jerzy Pawlikowski Dec 02 '13 at 20:44
-3

b = C() would make the local variable b an instance of the C class. Not sure why you would want to do this though. Can you post more of your code?

Esteban
  • 2,444
  • 2
  • 19
  • 19