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

Django migration strategy for renaming a model and relationship fields

我计划在现有的Django项目中重命名几个模型,其中有许多其他模型与我要重命名的模型具有外键关系。我很确定这需要多次迁移,但我不确定具体的过程。

假设我从一个名为myapp的django应用程序中的以下模型开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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()

注意,FooAnotherModel字段名不变,但关系更新为Bar模型。我的理由是我不应该一次改变太多,如果我把这个字段名改成Bar的话,我将冒丢失该列中数据的风险。

步骤2

创建空迁移:

1
python manage.py makemigrations --empty myapp

步骤3

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

1
2
3
4
5
6
7
8
9
class Migration(migrations.Migration):

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

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

步骤4

应用迁移:

1
python manage.py migrate

步骤5

编辑models.py中的相关字段名:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

创建另一个空迁移:

1
python manage.py makemigrations --empty myapp

步骤7

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

1
2
3
4
5
6
7
8
9
10
class Migration(migrations.Migration):

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

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

步骤8

应用第二次迁移:

1
python manage.py migrate


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

而且,这似乎是很多步骤。迁移操作能否以某种方式进行浓缩?

谢谢!


所以当我尝试这个的时候,你似乎可以浓缩步骤3-7:

1
2
3
4
5
6
7
8
9
10
11
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的方法对我很有用,因为直到第4步,移植工作都很好。但是,在任何迁移中,隐式将"foreignkeyfield(foo)"更改为"foreignkeyfield(bar)"都不相关。这就是当我想要重命名关系字段时迁移失败的原因(步骤5-8)。这可能是因为我的"anoThermodel"和"yetanothermodel"在我的案例中被发送到其他应用程序中。

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

我改编了这个方法,特别是奥特兰泽的技巧。

就像Fiver一样,假设我们在myapp中有:

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

在myotherapp中:

1
2
3
4
5
6
7
8
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:

将每个OneToonField(foo)或ForeignKeyField(foo)转换为integerField()。(这将保留相关foo对象的id作为integerfield的值)。

1
2
3
4
5
6
7
class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

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

然后

1
2
3
python manage.py makemigrations

python manage.py migrate

第2步:(如第2-4步)

更改模型名称

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

创建空迁移:

1
python manage.py makemigrations --empty myapp

然后像这样编辑:

1
2
3
4
5
6
7
8
9
class Migration(migrations.Migration):

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

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

最终

1
python manage.py migrate

步骤3:

将integerfield()转换回以前的foreignkeyfield或onetoonefield,但使用新的条形模型。(上一个integerfield正在存储ID,因此django了解这一点并重新建立连接,这很酷。)

1
2
3
4
5
6
7
class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

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

然后这样做:

1
python manage.py makemigrations

非常重要的是,在这个步骤中,您必须修改每个新的迁移,并添加对renamemodel foo->bar迁移的依赖。因此,如果anoThermodel和yetanothermodel都在myotherapp中,则在myotherapp中创建的迁移必须如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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')
        ),
    ]

然后

1
python manage.py migrate

步骤4:

最终,您可以重命名字段

1
2
3
4
5
6
7
8
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()

然后自动重命名

1
python manage.py makemigrations

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

1
python manage.py migrate

就这样!

这在Django1.8上工作


对于django1.10,我通过简单地运行makemigrations来更改两个模型类名(包括foreignkey和with data),然后为应用程序迁移。对于makemigrations步骤,我必须确认我想要更改表名。migrate更改了表的名称,没有问题。

然后我更改了要匹配的foreignkey字段的名称,makemigrations再次要求我确认要更改名称。迁移比进行更改。

所以我分两步做了这个,没有任何特殊的文件编辑。正如@wasibigeek所提到的,我一开始确实会出错,因为我忘记更改admin.py文件。


我需要做同样的事情。我立刻改变了模型(即步骤1和步骤5)。然后创建了一个模式迁移,但将其编辑为:

1
2
3
4
5
6
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 1.9.4版

我遵循以下步骤:

我刚把旧型号改名为新型号运行python manage.py makemigrations。它会要求你Did you rename the appname.oldName model to NewName? [y/N]选择y

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

以下内容类型已过时,需要删除:

1
2
appname | oldName
appname | NewName

通过外键与这些内容类型相关的任何对象也将被删除。是否确实要删除这些内容类型?如果你不确定,回答"不"。

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

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


我也遇到了V.Thorey所描述的问题,发现他的方法非常有用,但是可以简化为更少的步骤,如Fiver所描述的步骤5到8,没有步骤1到4,只是步骤7需要更改为下面的步骤3。总体步骤如下:

步骤1:在models.py中编辑相关字段名

1
2
3
4
5
6
7
8
9
10
11
12
13
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:创建空迁移

1
python manage.py makemigrations --empty myapp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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:应用迁移

1
python manage.py migrate

多恩

P.S.我在Django 1.9上尝试过这种方法


不幸的是,我发现重命名迁移存在问题(每个django 1.x),将旧表名留在数据库中(甚至不要在旧表上尝试任何操作,只需重命名模型)。

该案例的解决方案:

1
2
3
4
class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo

我会用@ceasaro的话来评价他的回答。

新版本的django可以检测到变化并询问所做的工作。我还想补充一下,django可能会混合执行一些迁移命令的顺序。

应用小的更改并运行makemigrationsmigrate,如果出现错误,可以编辑迁移文件。

可以更改某些行的执行顺序以避免错误。


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

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


如果您使用的是像pycharm这样的好的IDE,那么您可以右键单击模型名称,然后执行重构->重命名。这就省去了检查引用模型的所有代码的麻烦。然后运行makemigrations和migrate。Django 2+只需确认名称更改。


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

1
sudo pip install -U Django

(-U表示"升级")解决了这个问题。