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


152

我打算重命名现有Django项目中的多个模型,在该项目中,还有许多其他模型与我要重命名的模型具有外键关系。我相当确定这将需要多次迁移,但是我不确定确切的过程。

假设我从Django应用程序中的以下模型开始myapp

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开发文档中阅读的内容,我假设采用以下迁移策略:

第1步

修改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可能会丢失该列中的数据。

第2步

创建一个空迁移:

python manage.py makemigrations --empty myapp

第三步

编辑Migration在步骤2中创建的迁移文件中的类,RenameModel以将该操作添加到操作列表中:

class Migration(migrations.Migration):

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

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

第4步

应用迁移:

python manage.py migrate

第5步

在中编辑相关字段名称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()

第6步

创建另一个空迁移:

python manage.py makemigrations --empty myapp

步骤7

编辑Migration在步骤6中创建的迁移文件中的类,以将RenameField任何相关字段名称的操作添加到操作列表中:

class Migration(migrations.Migration):

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

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

步骤8

应用第二次迁移:

python manage.py migrate

除了更新其余代码(视图,表单等)以反映新的变量名之外,这基本上是新的迁移功能如何起作用的吗?

另外,这似乎需要很多步骤。迁移操作可以某种方式压缩吗?

谢谢!

Answers:


125

因此,当我尝试此操作时,您似乎可以将步骤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,然后检查迁移文件。


感谢你的回答。此后,我已经按照概述的步骤进行了迁移,但是我很好奇您是尝试使用现有数据还是仅使用空数据库?
Fiver 2014年

2
使用现有数据对其进行了尝试,尽管在我的本地环境中的sqlite上只有几行(当我转到生产环境时,我打算清除所有内容,包括迁移文件)
wasabigeek 2014年

4
如果在迁移文件中使用模型名称,则无需更改apps.get_model它们。我花了很多时间弄清楚这一点。
艾哈迈德(Ahmed)

9
在django 2.0中,如果您更改模型名称,该./manage.py makemigrations myapp命令将询问您是否重命名了模型。例如:您是否将myapp.Foo模型重命名为Bar?[y / N]如果您回答“ y”,则迁移将包含migration.RenameModel('Foo', 'Bar')重命名字段的相同计数:-)
ceasaro

1
manage.py makemigrations myapp可能仍然失败:“如果一次更改模型的名称及其许多字段,则可能必须手动添加此名称;对于自动检测器,这看起来就像您删除了具有旧名称的模型,并添加了一个新名称,名称不同,它创建的迁移将丢失旧表中的所有数据。” Django 2.1 Docs 对我来说,创建一个空迁移,添加重命名的模型然后makemigrations像往常一样运行就足够了。
hlongmore '19

36

最初,我认为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保留为integerfield的值)。

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

第三步:

将您的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 

非常重要的是,在这一步中,您必须修改每个新的迁移,并在RenameModel 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
谢谢!那是非常有帮助的。但要注意-我还必须手动重命名和/或删除PostgreSQL字段索引,因为在将Foo重命名为Bar之后,我创建了一个名为Bar的新模型。
Anatoly Scherbakov 2015年

这次真是万分感谢!我认为关键部分是将要重命名的模型中的所有外键转换为IntegerField。这对我来说效果很好,并具有使用正确名称重新创建它们的附加优点。自然,我建议您在实际运行之前检查所有迁移!
zelanix

谢谢!为了重命名其他模型都具有外键的模型,我尝试了许多不同的策略(步骤1-3),这是唯一起作用的模型。
MSH

ForeignKeyIntegerFields 更改为s可以拯救我的今天!
穆罕默德

8

我需要做同样的事情并遵循。我一次更改了模型(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 fine。

从这里开始:https : //hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/


太好了,谢谢分享。如果该答案有帮助,请确保+1 wasibigeek。
Fiver

7

对于Django 1.10,我仅通过运行Makemigrations,然后为该应用程序迁移,就设法更改了两个模型类名称(包括ForeignKey和数据)。对于Makemigrations步骤,我必须确认我想更改表名称。迁移更改表的名称没有问题。

然后,我更改了ForeignKey字段的名称以使其匹配,然后Makemigrations再次要求我确认要更改名称。迁移比进行更改。

因此,我分两步进行了此操作,而无需进行任何特殊的文件编辑。起初我确实得到了错误,因为我忘记更改admin.py文件,如@wasibigeek所述。


非常感谢!也是Django 1.11的完美选择
旧金山

5

我也遇到了v.thorey所描述的问题,发现他的方法非常有用,但可以凝结为更少的步骤,实际上是步骤5至8,如Fiver所描述的,而没有步骤1-4,只是步骤7需要更改为在步骤3之下。总体步骤如下:

步骤1:在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()

步骤2:建立一个空的迁移

python manage.py makemigrations --empty myapp

步骤3:在步骤2中创建的迁移文件中编辑Migration类

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

步骤4:套用迁移

python manage.py migrate

完成了

PS我已经在Django 1.9上尝试过这种方法


5

我正在使用Django版本1.9.4

我已按照以下步骤操作:

我刚刚将模型oldName重命名为NewName Run python manage.py makemigrations。它将要求您 Did you rename the appname.oldName model to NewName? [y/N]选择Y

运行python manage.py migrate,它将要求您

以下内容类型是陈旧的,需要删除:

appname | oldName
appname | NewName

通过外键与这些内容类型相关的任何对象也将被删除。您确定要删除这些内容类型吗?如果不确定,请回答“否”。

Type 'yes' to continue, or 'no' to cancel: Select No

它为我重命名并将所有现有数据迁移到新的命名表。


谢谢的家伙,我很困惑,因为打“不”后什么也没发生
farhawa

3

不幸的是,我发现了重命名迁移的问题(每个django 1.x),这些问题将旧表名保留在数据库中。

Django甚至没有在旧表上尝试任何操作,只是重命名了自己的模型。外键和索引通常存在相同的问题-Django无法正确跟踪那里的更改。

最简单的解决方案(解决方法):

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

真正的解决方案(一种在2次提交中切换所有索引,约束,触发器,名称等的简便方法,而对于较小的表则如此):

提交A:

  1. 创建旧模型相同的模型
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. 切换代码以Bar仅与新模型一起使用。(包括架构上的所有关系)

在迁移准备中RunPython,它将数据从Foo复制到Bar(包括idFoo)

  1. 可选的优化(如果需要更大的表)

提交B :(不要着急,整个团队迁移后都要做)

  1. 旧型号的安全降落 Foo

进一步清理:

  • 壁球迁徙

Django中的错误:


3

只是想确认并添加ceasaro评论。Django 2.0现在似乎可以自动执行此操作。

我在Django 2.2.1上,我所要做的只是重命名模型并运行makemigrations

在这里,它询问我是否已将特定的类从重命名AB,我选择是,然后进行了迁移,并且一切似乎都可以正常工作。

注意我没有在project / migrations文件夹内的任何文件中重命名旧模型名称。


1

我需要重命名几个表。但是Django仅注意到一种模型重命名。发生这种情况是因为Django反复遍历添加的模型,然后删除了模型。对于每一对,它会检查它们是否属于同一应用,并且具有相同的字段。只有一个表没有要重命名的表的外键(您记得,外键包含模型类名称)。换句话说,只有一个表没有字段更改。这就是为什么它被注意到。

因此,解决方案是一次重命名一个表,在中更改模型类名称models.py,可能是views.py,然后进行迁移。之后,检查您的代码是否有其他引用(模型类名称,相关(查询)名称,变量名称)。如果需要,请进行迁移。然后,有选择地将所有这些迁移合并为一个(确保也复制导入)。


1

我会用@ceasaro的话,对他对这个答案的评论。

较新版本的Django可以检测到更改并询问已完成的操作。我还要补充一点,Django可能会混合一些迁移命令的执行顺序。

这将是明智的,适用小的变化和运行makemigrations以及migrate如果发生错误的迁移文件进行编辑。

某些行的执行顺序可以更改,以免出错。


高兴地注意到,如果你改变模型的名字和有定义的外键,等等,这并不工作
迪恩Kayton

扩展先前的注释:如果我要做的只是更改模型名称并运行makemigrations,我会在外键中得到'NameError:名称'<oldmodel>'未定义',等等...如果我更改它并运行makemigrations,则会出现导入错误在admin.py ...中,如果我修复此问题并再次运行makemigrations,则会收到提示“您是否将<app.oldmodel>模型重命名为<newmodel>”,但是在应用迁移时,我得到了“ ValueError:字段<app .newmodel.field1>声明为对'<app.oldmodel>'的惰性引用,但应用程序'<app>'不提供模型'<oldmodel>',依此类推……”
Dean

该错误看起来像您需要在历史迁移中重命名引用。
mhatch19年

@DeanKayton会说migrations.SeparateDatabaseAndState可以帮助吗?
diogosimao

1

如果您使用的是像PyCharm这样的优秀IDE,则可以右键单击模型名称并进行重构->重命名。这免除了您遍历引用该模型的所有代码的麻烦。然后运行makemigrations并进行迁移。Django 2+只会确认名称更改。


-10

我将Django从版本10升级到版本11:

sudo pip install -U Django

-U用于“升级”),它解决了问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.