Django 重命名模型和关系字段的迁移策略
我打算在一个已有的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 个回答
对于Django 1.10,我成功地通过简单地运行Makemigrations和Migrate命令,改变了两个模型类的名字(包括一个外键,并且还有数据)。在运行Makemigrations的时候,我需要确认一下我想要更改表的名字。然后,Migrate就顺利地把表的名字改了。
接着,我把外键字段的名字也改了,以保持一致,Makemigrations又让我确认一下我想要改这个名字。Migrate随后也完成了这个改动。
所以我分了两个步骤来做,没有进行任何特别的文件编辑。一开始我确实遇到了一些错误,因为我忘了修改admin.py文件,正如@wasibigeek提到的那样。
我也需要做同样的事情,所以我照着做了。我一次性更改了模型(按照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/
在当前版本的Django中,你可以给模型改个名字,然后运行 python manage.py makemigrations
这个命令。Django会问你是否确认要改名字,如果你选择“是”,那么所有的改名过程都会自动完成。
一开始,我以为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
所以当我尝试这个的时候,发现你可以把第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
,然后再检查迁移文件。