ChatGPT解决这个技术问题 Extra ChatGPT

用于重命名模型和关系字段的 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 开发文档中阅读的内容,我假设以下迁移策略:

步骤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 步

应用第二次迁移:

python manage.py migrate

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

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

谢谢!


w
wasabigeek

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


尝试使用现有数据,尽管在我的本地环境中的 sqlite 上只有几行(当我转移到生产时,我打算清除所有内容,包括迁移文件)
如果您在迁移文件中使用 apps.get_model,则不必更改模型名称。我花了很多时间才弄清楚这一点。
在 django 2.0 中,如果您更改模型名称,./manage.py makemigrations myapp 命令将询问您是否重命名了模型。例如:您是否将 myapp.Foo 模型重命名为 Bar? [y/N] 如果您回答 'y',您的迁移将包含 migration.RenameModel('Foo', 'Bar') 重命名字段的相同计数 :-)
manage.py makemigrations myapp 可能仍会失败:“如果您同时更改模型的名称及其相当多的字段,您可能必须手动添加它;对于自动检测器,这看起来就像您删除了具有旧名称的模型并添加了一个具有不同名称的新表,它创建的迁移将丢失旧表中的所有数据。” Django 2.1 Docs 对我来说,创建一个空迁移,向其中添加模型重命名,然后像往常一样运行 makemigrations 就足够了。
跟进@ceasaro:如果您同时对模型进行了更改,Django 可能不会自动检测到您重命名了模型。要解决此问题,请在两个单独的迁移中进行更改。首先重命名模型 makemigrations,然后更改模型,然后再次 makemigrations
A
Ali Shamakhi

起初,我认为 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()

步骤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 模型。 (之前的 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 

非常重要的是,在这一步你必须修改每个新的迁移并添加对 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


谢谢!这非常有帮助。但请注意 - 我还必须手动重命名和/或删除 PostgreSQL 字段索引,因为在将 Foo 重命名为 Bar 之后,我创建了一个名为 Bar 的新模型。
这次真是万分感谢!我认为关键部分是将要重命名的模型内外的所有外键转换为IntegerField。这对我来说非常有效,并且具有使用正确名称重新创建它们的额外优势。当然,我会建议在实际运行之前检查所有迁移!
谢谢!我尝试了许多不同的策略来重命名其他模型具有外键的模型(步骤 1-3),这是唯一有效的策略。
今天将 ForeignKey 更改为 IntegerField 拯救了我的一天!
J
Jayakrishnan

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


这确实需要成为当前的最佳答案,即使较旧的答案仍然有用且有趣。这些天,Django 只是为你做这件事。
J
John Q

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


太好了,谢谢分享。如果该答案有帮助,请务必 +1 wasibigeek。
e
excyberlabber

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

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

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


非常感谢!也非常适合 Django 1.11
C
Curtis Lo

我也遇到了 v.thorey 描述的问题,发现他的方法非常有用,但可以浓缩为更少的步骤,实际上是第 5 到 8 步,正如 Fiver 描述的那样,没有第 1 到第 4 步,除了第 7 步需要更改为我的在步骤 3 之后。总体步骤如下:

第一步:编辑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 上尝试过这种方法


因此,一旦我意识到我的一个 ForeignKey 字段具有 blank=True, null=True,这种方法对我来说非常有效。一旦我做了我的models.IntegerField(blank=True, null=True),这项技术就运作良好。谢谢!
P
Piyush S. Wanare

我正在使用 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

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


谢谢老兄,我很困惑,因为点击“不”后什么也没发生
v
valex

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

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

在这里它询问我是否已将特定类从 A 重命名为 B,我选择了是并运行了 migrate 并且一切似乎都有效。

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


S
Sławomir Lenart

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

Django 甚至没有在旧表上尝试任何东西,只是重命名了他自己的模型。外键和一般索引也存在同样的问题 - Django 没有正确跟踪更改。

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

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

真正的解决方案(一种在 2 次提交中切换所有索引、约束、触发器、名称等的简单方法,但适用于较小的表):

提交一个:

创建与旧模型相同的模型

# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...

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

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

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

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

旧模型 Foo 的安全丢弃

进一步清理:

壁球移民

Django中的错误:

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


x
x-yuri

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

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


d
diogosimao

我会根据他对此answer的评论发表@ceasaro 的话。

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

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

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


需要注意的是,如果您更改模型名称并且定义了外键等,这将不起作用......
扩展先前的评论:如果我所做的只是更改模型名称并运行 makemigrations,我会在外键等中得到 'NameError: name '' is not defined' 等...如果我更改它并运行 makemigrations,我会收到导入错误在 admin.py... 如果我修复该问题并再次运行 makemigrations,我会收到提示“您是否将 模型重命名为 ” 但是在应用迁移时,我会收到“ValueError: The field 是用对“”的惰性引用声明的,但应用程序“”不提供模型“”等...
此错误看起来您需要重命名历史迁移中的引用。
@DeanKayton 会说 migrations.SeparateDatabaseAndState 可以提供帮助吗?
J
Josh

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


J
JulienD

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

sudo pip install -U Django

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅