重命名模型和关系字段的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()

我想将__重命名为ABC0模型,因为该名称实际上没有意义,并且会在代码中引起混淆,而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()

请注意,fooAnotherModel字段名称不会更改,但关系会更新为Bar模型。我的理由是,我不应该一次更改太多,如果我将该字段名更改为bar,则可能会丢失该列中的数据。

步骤2

创建空迁移:

python manage.py makemigrations --empty myapp

第3步

编辑在步骤2中创建的迁移文件中的Migration类,将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步

编辑在步骤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')
]

第8步

应用第2次迁移:

python manage.py migrate

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

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

谢谢!

119077 次浏览

所以当我试着这样做的时候,似乎你可以浓缩第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和更旧的迁移文件(!),则可能会出现一些错误。

更新:正如凯萨罗所提到的,较新版本的Django通常能够检测并询问模型是否被重命名。因此,请先尝试manage.py makemigrations,然后再检查迁移文件。

我需要做同样的事情并跟随。我一下子改变了模型(步骤1和步骤5来自Fiver的回答)。然后创建了一个模式迁移,但将其编辑为:

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


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

这个效果很好。我现有的所有数据都显示出来了,所有其他引用的表格都很好。

从这里开始:__abc0

起初,我认为Fiver的方法对我有效,因为迁移在第4步之前一直运行良好。但是,将“ ForeignKeyField(foo)”隐式更改为“ ForeignKeyField(bar)”在任何迁移中都不相关。这就是当我想重命名关系字段(步骤5-8)时迁移失败的原因。 这可能是因为在我的情况下,我的“ AnotherModel ”和“ YetAnotherModel ”在其他应用程序中被调度。

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

我采用了的方法,特别是Otranzer的技巧。

比如五,假设我们有我的应用

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

步骤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,但使用新的条形模型。(之前的IntegerField存储了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都在MyoTherap中,则在MyoTherap中创建的迁移必须如下所示:

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

第四步:

最后,您可以重命名字段。

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

就是这样!

这适用于Django1.8

不幸的是,我发现了重命名迁移的问题(每个Django1.X),这会在数据库中留下旧的表名。

Django甚至没有在旧表上尝试任何东西,只是重新命名了他自己的模型。 外键和索引通常也有同样的问题——Django无法正确跟踪其中的更改。

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

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

的解决方案(切换所有的简单方法 2次提交中的索引、约束、触发器、名称等,但用于__(ABC0表):

提交A:

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


class Bar(model.Model):
...
  1. 切换代码以仅用于新型号Bar。 (包括模式上的所有关系)

迁移中准备__abc0,它将数据从foo复制到bar (包括FOO的id

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

提交B:(不着急,在整个团队迁移时进行)

  1. 旧型号Foo的安全下降

进一步清理:

  • 挤压迁移

Django中的错误:

  • https://code.djangoproject.com/ticket/23577.

我也遇到了V.Thorey所描述的问题,并发现他的方法非常有用,但可以浓缩为更少的步骤,实际上是Five所描述的第5步到第8步,而不是第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中创建的迁移文件中编辑迁移类

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

完成

P.S.我在Django1.9上尝试过这种方法。

我使用的是Django版本1.9.4。

我必须遵循以下步骤:-

我刚刚将模型OldName重命名为NewName 运行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

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

对于Django1.10,我设法通过简单地运行MakeMigration来更改两个模型类名(包括一个外键和数据),然后为应用程序进行迁移。对于MakeMigration步骤,我必须确认我想要更改表名。Migrate更改了表的名称,没有出现任何问题。

然后,我更改了ForeignKey字段的名称以进行匹配,Makemigrations再次要求我确认是否要更改名称。Migrate Than进行了更改。

因此,我采取了两个步骤,没有任何特殊的文件编辑。正如@wasibigeek所提到的,一开始我确实遇到了错误,因为我忘记更改admin.py文件。

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

sudo pip install -U Django

-U表示“升级”),它解决了这个问题。

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

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

我会把@Ceasaro的话写成我的话,写在他对这个__的评论上。

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

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

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

如果你正在使用一个好的IDE,比如PyCharm,你可以右键单击模型名称并进行重构->重命名。这为您省去了检查所有引用模型的代码的麻烦。然后运行MakeMigration并迁移。Django 2+将简单地确认名称更改。

只是想确认并补充一下Ceasaro的评论。Django2.0现在似乎可以自动执行此操作。

我在Django2.2.1上,我所要做的就是重命名模型并运行makemigrations

在这里,它询问我是否已将特定的类从A重命名为B,我选择“是”并运行“迁移”,一切似乎都正常工作。

注意:我没有在项目/迁移文件夹中的任何文件中重命名旧模型名称。

在Django的当前版本中,您可以重命名模型并运行python manage.py makemigrations,然后Django将询问您是否要重命名模型,如果您选择Yes,则所有重命名过程都将自动完成。