Django migration strategy for renaming a model and relationship fields

后端 未结 12 1475
一个人的身影
一个人的身影 2020-11-28 00:54

I\'m planning to rename several models in an existing Django project where there are many other models that have foreign key relationships to the models I would like to rena

相关标签:
12条回答
  • 2020-11-28 01:45

    I would make @ceasaro words, mine on his comment on this answer.

    Newer versions of Django can detect changes and ask about what was done. I also would add that Django might mix the order of execution of some migration commands.

    It would be wise to apply small changes and run makemigrations and migrate and if the error occurs the migration file can be edited.

    Some lines order of execution can be changed to avoid erros.

    0 讨论(0)
  • 2020-11-28 01:47

    I also faced the problem as v.thorey described and found that his approach is very useful but can be condensed into fewer steps which are actually step 5 to 8 as Fiver described without step 1 to 4 except that step 7 needs to be changed as my below step 3. The overall steps are as follow:

    Step 1: Edit the related field names in models.py

    class Bar(models.Model):
        name = models.CharField(unique=True, max_length=32)
        description = models.TextField(null=True, blank=True)
    
    
    class AnotherModel(models.Model):
        bar = models.ForeignKey(Bar)  # <-- changed field name
        is_awesome = models.BooleanField()
    
    
    class YetAnotherModel(models.Model):
        bar = models.ForeignKey(Bar)  # <-- changed field name
        is_ridonkulous = models.BooleanField()
    

    Step 2: Create an empty migration

    python manage.py makemigrations --empty myapp
    

    Step 3: Edit the Migration class in the migration file created in Step 2

    class Migration(migrations.Migration):
    
    dependencies = [
        ('myapp', '0001_initial'), 
    ]
    
    operations = [
        migrations.AlterField(
            model_name='AnotherModel',
            name='foo',
            field=models.IntegerField(),
        ),
        migrations.AlterField(
            model_name='YetAnotherModel',
            name='foo',
            field=models.IntegerField(),
        ),
        migrations.RenameModel('Foo', 'Bar'),
        migrations.AlterField(
            model_name='AnotherModel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.AlterField(
            model_name='YetAnotherModel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]
    

    Step 4: Apply the migration

    python manage.py migrate
    

    Done

    P.S. I've tried this approach on Django 1.9

    0 讨论(0)
  • 2020-11-28 01:49

    At first, I thought that Fiver's method worked for me because the migration worked well until step 4. However, the implicit changes 'ForeignKeyField(Foo)' into 'ForeignKeyField(Bar)' was not related in any migrations. This is why migration failed when I wanted to rename relationship fields (step 5-8). This might be due to the fact that my 'AnotherModel' and 'YetAnotherModel' are dispatched in other apps in my case.

    So I managed to rename my models and relationship fields doing following below steps:

    I adapted the method from this and particularly the trick of otranzer.

    So like Fiver let's say we have in myapp:

    class Foo(models.Model):
        name = models.CharField(unique=True, max_length=32)
        description = models.TextField(null=True, blank=True)
    

    And in myotherapp:

    class AnotherModel(models.Model):
        foo = models.ForeignKey(Foo)
        is_awesome = models.BooleanField()
    
    
    class YetAnotherModel(models.Model):
        foo = models.ForeignKey(Foo)
        is_ridonkulous = models.BooleanField()
    

    Step 1:

    Transform every OneToOneField(Foo) or ForeignKeyField(Foo) into IntegerField(). (This will keep the id of related Foo object as value of the integerfield).

    class AnotherModel(models.Model):
        foo = models.IntegerField()
        is_awesome = models.BooleanField()
    
    class YetAnotherModel(models.Model):
        foo = models.IntegerField()
        is_ridonkulous = models.BooleanField()
    

    Then

    python manage.py makemigrations
    
    python manage.py migrate
    

    Step 2: (Like step 2-4 from Fiver)

    Change the model name

    class Bar(models.Model):  # <-- changed model name
        name = models.CharField(unique=True, max_length=32)
        description = models.TextField(null=True, blank=True)
    

    Create an empty migration:

    python manage.py makemigrations --empty myapp
    

    Then edit it like:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('myapp', '0001_initial'),
        ]
    
        operations = [
            migrations.RenameModel('Foo', 'Bar')
        ]
    

    Eventually

    python manage.py migrate
    

    Step 3:

    Transform Back your IntegerField() into their previous ForeignKeyField or OneToOneField but with the new Bar Model. (The previous integerfield was storing the id, so django understand that and reestablish the connection, which is cool.)

    class AnotherModel(models.Model):
        foo = models.ForeignKey(Bar)
        is_awesome = models.BooleanField()
    
    class YetAnotherModel(models.Model):
        foo = models.ForeignKey(Bar)
        is_ridonkulous = models.BooleanField()
    

    Then do:

    python manage.py makemigrations 
    

    Very importantly, at this step you have to modify every new migrations and add the dependency on the RenameModel Foo-> Bar migrations. So if both AnotherModel and YetAnotherModel are in myotherapp the created migration in myotherapp must look like this:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
            ('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
        ]
    
        operations = [
            migrations.AlterField(
                model_name='anothermodel',
                name='foo',
                field=models.ForeignKey(to='myapp.Bar'),
            ),
            migrations.AlterField(
                model_name='yetanothermodel',
                name='foo',
                field=models.ForeignKey(to='myapp.Bar')
            ),
        ]
    

    Then

    python manage.py migrate
    

    Step 4:

    Eventually you can rename your fields

    class AnotherModel(models.Model):
        bar = models.ForeignKey(Bar) <------- Renamed fields
        is_awesome = models.BooleanField()
    
    
    class YetAnotherModel(models.Model):
        bar = models.ForeignKey(Bar) <------- Renamed fields
        is_ridonkulous = models.BooleanField()
    

    and then do automatic renaming

    python manage.py makemigrations
    

    (django should ask you if you actually renamed the modelname, say yes)

    python manage.py migrate
    

    And that's it!

    This works on Django1.8

    0 讨论(0)
  • 2020-11-28 01:50

    So when I tried this, it seems you can condense Step 3 - 7:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('myapp', '0001_initial'), 
        ]
    
        operations = [
            migrations.RenameModel('Foo', 'Bar'),
            migrations.RenameField('AnotherModel', 'foo', 'bar'),
            migrations.RenameField('YetAnotherModel', 'foo', 'bar')
        ]
    

    You may get some errors if you don't update the names where it's imported e.g. admin.py and even older migration files (!).

    Update: As ceasaro mentions, newer versions of Django are usually able to detect and ask if a model is renamed. So try manage.py makemigrations first and then check the migration file.

    0 讨论(0)
  • 2020-11-28 01:53

    I needed to do the same thing and follow. I changed the model all at once (Steps 1 and 5 together from Fiver's answer). Then created a schema migration but edited it to be this:

    class Migration(SchemaMigration):
        def forwards(self, orm):
            db.rename_table('Foo','Bar')
    
        def backwards(self, orm):
            db.rename_table('Bar','Foo')
    

    This worked perfectly. All my existing data showed up, all the other tables referenced Bar fine.

    from here: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

    0 讨论(0)
  • 2020-11-28 01:54

    Unfortunately, I found problems (each django 1.x) with rename migration which leave old table names in the database.

    Django doesn't even try anything on the old table, just rename his own model. The same problem with foreign keys, and indices in general - changes there are not tracked properly by Django.

    The simplest solution (workaround):

    class Foo(models.Model):
         name = models.CharField(unique=True, max_length=32)
         ...
    Bar = Foo  # and use Bar only
    

    The real solution (an easy way to switch all indices, constraints, triggers, names, etc in 2 commits, but rather for smaller tables):

    commit A:

    1. create the same model as the old one
    # deprecated - TODO: TO BE REMOVED
    class Foo(model.Model):
        ...
    
    class Bar(model.Model):
        ...
    
    1. switch code to work with new model Bar only. (including all relations on the schema)

    In migration prepare RunPython, which copy data from Foo to Bar (including id of Foo)

    1. optional optimization (if needed for greater tables)

    commit B: (no rush, do it when an entire team is migrated)

    1. safe drop of the old model Foo

    further cleanup:

    • squash on migrations

    bug in Django:

    • https://code.djangoproject.com/ticket/23577
    0 讨论(0)
提交回复
热议问题