pkcmeq
Last Updated: February 25, 2016
·
2.881K
· celc

Migrating ForeignKey to GenericForeignKey with South

Say you have models like:

class Game(models.Model):
    pass


class Entry(models.Model):
    game = models.ForeignKey(Game)

And you are planning on adding OtherGame and so want to move Entry to a generic relationship like:

class Entry(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    game = generic.GenericForeignKey('content_type', 'object_id')

First add NULL ContentType and object_id (but not game) and run ./migrate.py schemamigration --auto game on:

class Entry(models.Model):
    game = models.ForeignKey(Game)
    content_type = models.ForeignKey(ContentType, null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)

Then create a datamigration with ./manage.py datamigration migrate_foreign_key --freeze=game --freeze contenttypes adding:

class Migration(DataMigration):
    def forwards(self, orm):
        content_type = orm['contenttypes.contenttype'].objects\
            .get(model='game')
        for entry in orm.Entry.objects.all():
            entry.content_type = content_type
            entry.object_id = entry.game.pk
            entry.save()

Then create another schemamigration on the model after removing the foreignkey and dissalowing null.

class Entry(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    game = generic.GenericForeignKey('content_type', 'object_id')

Set the defaults to 1 then open the migration up and remove the defaults, and disable backwards migration migration, you should end up with:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_column(u'game_entry', 'game_id')

        db.alter_column(u'game_entry',
                        'object_id',
                        self.gf('django.db.models.fields.PositiveIntegerField')())

        db.alter_column(u'game_entry',
                        'content_type_id',
                        self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType']))

    def backwards(self, orm):
        raise RuntimeError("Cannot reverse this migration")

2 Responses
Add your response

13653
update_all_contenttypes()

Add this to the datamigration or django will throw a contenttype not found

over 1 year ago ·
19953

Thank you!

over 1 year ago ·