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

问题描述 投票:124回答:12

我打算在现有的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开发文档中读到的内容,我假设以下迁移策略:

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

请注意,AnotherModelfoo字段名称不会更改,但关系会更新为Bar模型。我的理由是我不应该立刻改变太多,如果我将这个字段名称更改为bar,我将面临丢失该列中数据的风险。

Step 2

创建一个空迁移:

python manage.py makemigrations --empty myapp

Step 3

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

class Migration(migrations.Migration):

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

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

Step 4

应用迁移:

python manage.py migrate

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

Step 6

创建另一个空迁移:

python manage.py makemigrations --empty myapp

Step 7

编辑在步骤6中创建的迁移文件中的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')
    ]

Step 8

应用第二次迁移:

python manage.py migrate

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

此外,这似乎是很多步骤。迁移操作能否以某种方式压缩?

谢谢!

python django django-migrations
12个回答
105
投票

所以,当我尝试这个时,似乎你可以浓缩步骤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,然后检查迁移文件。


0
投票

如果您使用的是像PyCharm这样的好IDE,您可以右键单击模型名称并执行重构 - >重命名。这样可以省去浏览引用模型的所有代码的麻烦。然后运行makemigrations并迁移。 Django 2+只会确认名称更改。


0
投票

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

我在Jango 2.2.1上,所有我必须做的事情是重命名模型并运行makemigrations。

在这里它询问我是否已将特定类重命名为A到B,我选择了yes并运行迁移,所有这些似乎都有效。

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


-7
投票

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

sudo pip install -U Django

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


31
投票

起初,我认为Fiver的方法对我有效,因为迁移工作到第4步。然而,隐式更改'ForeignKeyField(Foo)'到'ForeignKeyField(Bar)'在任何迁移中都没有关系。这就是当我想重命名关系字段时迁移失败的原因(步骤5-8)。这可能是由于我的'AnotherModel'和'YetAnotherModel'在我的情况下在其他应用程序中调度的事实。

所以我设法重命名我的模型和关系字段,执行以下步骤:

我改编了this的方法,尤其是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()

Step 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

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

更改型号名称

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

Step 3:

将IntegerField()转换回以前的ForeignKeyField或OneToOneField,但使用新的Bar Model。 (之前的整数字段存储了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

Step 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应该问你是否真的重命名了modelname,说是)

python manage.py migrate

就是这样!

这适用于Django 1.8


7
投票

我需要做同样的事情。我一下子改变了模型(即步骤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/


6
投票

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

然后我更改了ForeignKey字段的名称以匹配,Makemigrations再次询问我确认我想要更改名称。迁移比做出改变。

所以我在没有任何特殊文件编辑的情况下分两步完成。我一开始就遇到错误,因为我忘了更改admin.py文件,正如@wasibigeek所提到的那样。


5
投票

我也面对这个问题,因为v.thorey描述并发现他的方法非常有用,但可以简化为更少的步骤,实际上是步骤5到8,正如Fiver所描述的,没有步骤1到4,除了步骤7需要改变为我的第3步以下。总体步骤如下:

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

附:我在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

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


2
投票

不幸的是,我发现了重命名迁移的问题(每个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(包括Foo的id

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

提交B :(不急,在整个团队迁移时这样做)

  1. 旧模型Foo的安全下降

进一步清理:

  • 压制迁移

Django中的错误:


1
投票

我需要重命名几张桌子。但Django只注意到一个模型重命名。发生这种情况是因为Django迭代了添加的,然后删除的模型。对于每一对,它检查它们是否是相同的应用程序并且具有identical fields。只有一个表没有要重命名的表的外键(外键包含模型类名,如您所记得的)。换句话说,只有一个表没有字段更改。这就是它被注意到的原因。

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


1
投票

我会发表@ceasaro的话,我对这个answer的评论。

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

应用小的更改并运行makemigrationsmigrate是明智的,如果发生错误,可以编辑迁移文件。

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

© www.soinside.com 2019 - 2024. All rights reserved.