Django 重命名模型和关系字段的迁移策略

216 投票
14 回答
136633 浏览
提问于 2025-04-18 15:43

我打算在一个已有的Django项目中重命名几个模型,而这些模型有很多其他模型通过外键与它们关联。我觉得这可能需要进行多次迁移,但我不太确定具体的步骤。

假设我在一个叫做 myapp 的Django应用中有以下模型:

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


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


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

我想重命名 Foo 模型,因为这个名字不太合适,导致代码中有些混乱,而 Bar 这个名字会更清晰。

根据我在Django开发文档中看到的内容,我假设可以按照以下步骤进行迁移:

第一步

修改 models.py 文件:

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


class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_ridonkulous = models.BooleanField()

注意,AnotherModel 中的 foo 字段名没有改变,但它的关系更新到了 Bar 模型。我认为不应该一次性改动太多,如果把这个字段名改成 bar,可能会导致这个列中的数据丢失。

第二步

创建一个空的迁移:

python manage.py makemigrations --empty myapp

第三步

编辑第二步中创建的迁移文件里的 Migration 类,把 RenameModel 操作添加到操作列表中:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

第四步

应用这个迁移:

python manage.py migrate

第五步

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()

第六步

再创建一个空的迁移:

python manage.py makemigrations --empty myapp

第七步

编辑第六步中创建的迁移文件里的 Migration 类,把任何相关字段名称的 RenameField 操作添加到操作列表中:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

第八步

应用第二个迁移:

python manage.py migrate

除了更新其余的代码(视图、表单等)以反映新的变量名称,这基本上就是新的迁移功能的工作方式吗?

另外,这似乎步骤有点多。迁移操作能不能简化一下?

谢谢!

14 个回答

8

对于Django 1.10,我成功地通过简单地运行Makemigrations和Migrate命令,改变了两个模型类的名字(包括一个外键,并且还有数据)。在运行Makemigrations的时候,我需要确认一下我想要更改表的名字。然后,Migrate就顺利地把表的名字改了。

接着,我把外键字段的名字也改了,以保持一致,Makemigrations又让我确认一下我想要改这个名字。Migrate随后也完成了这个改动。

所以我分了两个步骤来做,没有进行任何特别的文件编辑。一开始我确实遇到了一些错误,因为我忘了修改admin.py文件,正如@wasibigeek提到的那样。

10

我也需要做同样的事情,所以我照着做了。我一次性更改了模型(按照Fiver的回答中的第1步和第5步一起做)。然后我创建了一个模式迁移,但把它编辑成了这样:

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

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

这样做效果很好。我的所有现有数据都显示出来了,其他表格也能正常引用Bar。

来源于这里: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

33

在当前版本的Django中,你可以给模型改个名字,然后运行 python manage.py makemigrations 这个命令。Django会问你是否确认要改名字,如果你选择“是”,那么所有的改名过程都会自动完成。

47

一开始,我以为Fiver的方法对我有效,因为迁移在第4步之前都很顺利。然而,隐含的变化把'ForeignKeyField(Foo)'变成了'ForeignKeyField(Bar)',而这些变化在任何迁移中都没有相关联。这就是为什么当我想重命名关系字段时(第5-8步)迁移失败的原因。可能是因为我的'AnotherModel'和'YetAnotherModel'在其他应用中被分配。

所以我通过以下步骤成功重命名了我的模型和关系字段:

我借鉴了这个方法,特别是otranzer的技巧。

就像Fiver说的,我们假设在myapp中有:

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

而在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()

步骤 1:

把每个OneToOneField(Foo)或ForeignKeyField(Foo)都变成IntegerField()。(这样可以把相关的Foo对象的id作为整数字段的值保留下来。)

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

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

然后

python manage.py makemigrations

python manage.py migrate

步骤 2:(像Fiver的第2-4步)

更改模型名称

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

创建一个空的迁移:

python manage.py makemigrations --empty myapp

然后像这样编辑它:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

最后

python manage.py migrate

步骤 3:

把你的IntegerField()再变回之前的ForeignKeyField或OneToOneField,但这次用新的Bar模型。(之前的整数字段存储了id,所以Django会理解这一点并重新建立连接,这很酷。)

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

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

然后做:

python manage.py makemigrations 

非常重要的是,在这一步你必须修改每个新的迁移,并添加对重命名模型Foo->Bar迁移的依赖。 所以如果AnotherModel和YetAnotherModel都在myotherapp中,myotherapp中创建的迁移必须像这样:

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')
        ),
    ]

然后

python manage.py migrate

步骤 4:

最后你可以重命名你的字段

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()

然后进行自动重命名

python manage.py makemigrations

(Django会问你是否真的重命名了模型名称,回答是)

python manage.py migrate

就这样!

这个方法适用于Django1.8

178

所以当我尝试这个的时候,发现你可以把第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')
    ]

如果你没有更新导入的地方的名称,比如在admin.py文件和一些旧的迁移文件中,你可能会遇到一些错误(!)。

更新:正如ceasaro提到的,新的Django版本通常能够检测到模型是否被重命名,并会询问你。所以先试试 manage.py makemigrations,然后再检查迁移文件。

撰写回答