I'm using south
to manage migrations and I've hit a corner. Basically I have the following setup:
App1:
class A(models.Model):
# bunch of attributes
App2:
class B(models.Models):
instance_a = models.OneToOneField(A, null=True, blank=True,
editable=False)
Now, I want to go from this to this:
App1:
class A(models.Model):
instance_b = models.ForeignKey(B, null=True, blank=True)
App2:
class B(models.Models):
# other attributes
My main issue is that I can't loose data. So basically at the end of the migration(s) all objects A that mapped previously to objects B should keep that mapping. As an example, if object A with id 7 was mapped to object B with id 8, by the end of this procedure this mapping should be retained.
I tried several things from schema migrations mixed with temporary place holders and data migrations. However I end up always in the same place, which is by the time the data migration is performed I no longer have the previous relations in order to access the correct attributes. So for example, B.instance_a is no longer available.
I would like your opinion on two things:
Thanks
Finally after some time I got a procedure with django-south
that might help others. The key was in south's depends_on
feature (http://south.aeracode.org/wiki/Dependencies). I did it in 4 steps:
First:
A
.So model A
becomes:
class A(models.Model):
instance_b_placeholder = models.ForeignKey(A, null=True, blank=True)
Now just run manage.py schemamigration app1 --auto
.
Second
manage.py datamigration app1 update_fields
. I chose to keep the datamigration in app1
. If you don't do this just make sure it runs after the previous migration.Here's the datamigration coded:
# Forwards:
for b in orm['app2.B'].objects.filter(instance_b__isnull=False):
b.instance_a.instance_b_placeholder = b
b.instance_a.save()
# Backwards:
for r in orm['app1.A'].objects.filter(instance_b_placeholder__isnull=False):
r.instance_b_placeholder.instance_a = r
r.instance_b_placeholder.save()
Third:
instance_b
from model B
and be sure to make the migration run after the one created in the previous step.Model B
becomes:
class B(models.Model):
# etc...
Issue manage.py schemamigration app2 --auto
and edit the migration adding the previous migration to depends_on
:
depends_on = (
("app1", "<migration_number>_update_fields"),
)
Forth step:
Rename the place holder. This is achieved by changing the name in the code and editing the migration. Editing is necessary because south
tends to delete and add a new column, but we only want it to rename the column.
This migration should run in last place, so I made it dependent on the previous one.
Here's the code:
depends_on = (
("app2", "<previous_migration_here>"),
)
# Forwards:
db.rename_column('app1_a', 'instance_b_placeholder_id', 'instance_b_id')
# Backwards:
db.rename_column('app1_a', 'instance_b_id', 'instance_b_placeholder_id')
So that's it. I don't know if there are so many other ways to do it, but at least this helped me.