jMyles
jMyles

Reputation: 12172

Move a python / django object from a parent model to a child (subclass)

I am subclassing an existing model. I want many of the members of the parent class to now, instead, be members of the child class.

For example, I have a model Swallow. Now, I am making EuropeanSwallow(Swallow) and AfricanSwallow(Swallow). I want to take some but not all Swallow objects make them either EuropeanSwallow or AfricanSwallow, depending on whether they are migratory.

How can I move them?

Upvotes: 9

Views: 4065

Answers (5)

rasca
rasca

Reputation: 354

I suggest using django-model-utils's InheritanceCastModel. This is one implementation I like. You can find many more in djangosnippets and some blogs, but after going trough them all I chose this one. Hope it helps.

Upvotes: 0

serguitus
serguitus

Reputation: 526

Another (outdated) approach: If you don't mind keeping parent's id you can just create brand new child instances from parent's attrs. This is what I did:

ids = [s.pk for s in Swallow.objects.all()]
# I get ids list to avoid memory leak with long lists
for i in ids:
    p = Swallow.objects.get(pk=i)
    c = AfricanSwallow(att1=p.att1, att2=p.att2.....)
    p.delete()
    c.save()

Once this runs, a new AfricanSwallow instance will be created replacing each initial Swallow instance Maybe this will help someone :)

Upvotes: 0

shapiromatron
shapiromatron

Reputation: 627

I know this is much later, but I needed to do something similar and couldn't find much. I found the answer buried in some source code here, but also wrote an example class-method that would suffice.

class AfricanSwallow(Swallow):

    @classmethod
    def save_child_from_parent(cls, swallow, new_attrs):
        """
        Inputs:
        - swallow: instance of Swallow we want to create into AfricanSwallow
        - new_attrs: dictionary of new attributes for AfricanSwallow

        Adapted from: 
        https://github.com/lsaffre/lino/blob/master/lino/utils/mti.py
        """
        parent_link_field = AfricanSwallow._meta.parents.get(swallow.__class__, None)
        new_attrs[parent_link_field.name] = swallow
        for field in swallow._meta.fields:
            new_attrs[field.name] = getattr(swallow, field.name)
        s = AfricanSwallow(**new_attrs)
        s.save()
        return s

I couldn't figure out how to get my form validation to work with this method however; so it certainly could be improved more; probably means a database refactoring might be the best long-term solution...

Upvotes: 11

Leopd
Leopd

Reputation: 42777

Depends on what kind of model inheritance you'll use. See http://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance for the three classic kinds. Since it sounds like you want Swallow objects that rules out Abstract Base Class.

If you want to store different information in the db for Swallow vs AfricanSwallow vs EuropeanSwallow, then you'll want to use MTI. The biggest problem with MTI as the official django model recommends is that polymorphism doesn't work properly. That is, if you fetch a Swallow object from the DB which is actually an AfricanSwallow object, you won't get an instance o AfricanSwallow. (See this question.) Something like django-model-utils InheritanceManager can help overcome that.

If you have actual data you need to preserve through this change, use South migrations. Make two migrations -- first one that changes the schema and another that copies the appropriate objects' data into subclasses.

Upvotes: 1

Wogan
Wogan

Reputation: 72727

It's a bit of a hack, but this works:

swallow = Swallow.objects.get(id=1)
swallow.__class__ = AfricanSwallow
# set any required AfricanSwallow fields here
swallow.save()

Upvotes: 11

Related Questions