迁移 django-model 字段名称更改而不丢失数据

Posted

技术标签:

【中文标题】迁移 django-model 字段名称更改而不丢失数据【英文标题】:migrating django-model field-name change without losing data 【发布时间】:2011-03-30 17:08:42 【问题描述】:

我有一个 django 项目,其中的数据库表已经包含数据。我想更改字段名称而不会丢失该列中的任何数据。我最初的计划是简单地更改模型字段名称,而不会实际更改 db 表的名称(使用 db_column 列参数):

原模型:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

新模型:

class Foo(models.Model):
  name = models.CharField(max_length=50, db_column='orig_name')

但是,运行 South 的 schemamigration --auto 会生成一个迁移脚本,该脚本会删除原始列 orig_name,并添加一个新列 name,这会产生删除该列中数据的不良副作用。 (我也很困惑 South 为什么要更改数据库中列的名称,因为我对 db_column 的理解是它可以在不更改数据库表列名称的情况下更改模型字段名称)。

如果我无法在不更改 db 字段的情况下更改模型字段,我想我可以像这样进行更直接的名称更改:

原模型:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

新模型:

class Foo(models.Model):
  name = models.CharField(max_length=50)

无论我最终使用哪种策略(我更喜欢第一种,但会发现第二种可以接受),我主要关心的是确保我不会丢失该列中已经存在的数据。

这是否需要一个多步骤的过程? (如1.添加一列,2.将数据从旧列迁移到新列,3.删除原列) 或者我可以用db.alter_column之类的东西来改变迁移脚本吗?

在更改列名的同时保留该列中的数据的最佳方法是什么?

【问题讨论】:

【参考方案1】:

1.编辑django模型上的字段名

2.创建一个空迁移,如下所示:

$ python manage.py makemigrations --empty testApp(testApp是你的应用名)

    编辑最近创建的空迁移文件

    操作 = [ migrations.RenameField('你的模型', '旧字段', '新字段'), ]

    应用迁移 $ python manage.py 迁移

数据库列名将被更改为新名称。

【讨论】:

【参考方案2】:

正如其他回复中所指出的,现在使用db_column 重命名数据库上没有更改的字段非常容易。但是生成的迁移实际上会创建一些 SQL 语句。您可以通过在迁移时调用 ./manage.py sqlmigrate ... 来验证这一点。

为避免对您的数据库造成任何影响,您需要使用SeparateDatabaseAndState 向 Django 表明它不需要在 DB 中执行任何操作。

如果你想了解更多,我写了一个小article。

【讨论】:

【参考方案3】:

Django 2.0.9(及更高版本)可以自动检测字段是否被重命名,并提供重命名而不是删除和创建新字段的选项

(同样适用于 Django 2.2

初步回答

发布,如果它仍然对某人有帮助。

对于 Django 2.0 + 只需重命名模型中的字段

class Foo(models.Model):
    orig_name = models.CharField(max_length=50)

class Foo(models.Model):
    name = models.CharField(max_length=50)

现在运行python manage.py makemigrations 它将通过删除旧字段和添加新字段的操作生成迁移。

继续并将其更改为关注。

operations = [
    migrations.RenameField(
        model_name='foo',
        old_name='orig_name',
        new_name='name')
]

现在运行python manage.py migrate,它将重命名数据库中的列而不会丢失数据。

【讨论】:

嗯,它确实有效,不确定你的用例,你能提供一些细节吗?事实上,在 **Django 2.0.9 ** 中,如果我重命名该字段,它提供了选项。我已经更新了答案。 LoL 不,因为我很久以前就想出了一种不同的方法。对不起。 感谢您的回答@Aarif,这对我很有帮助。如果您要重命名一个或两个字段,则使用此方法应该没问题。但是,如果您一次重命名多个字段(无论如何都不推荐),您可能会遇到问题。 请参阅我的评论。在 Paul Sebastian 的回答中引用旧列名的表单 - 这些会导致迁移错误,(Django 2.2.5)。 只是一个附录,如果您想这样做,请确保您没有同时更改该字段中的 任何内容,否则 Django 将不会t 将其检测为名称更改,而是将其检测为字段删除和新字段添加。我尝试删除 blank=True, null=True 并同时设置默认值,但它不起作用。一旦我恢复它并只更改名称,它就起作用了!【参考方案4】:

更新Django 3.1 中,一次只更改一个字段非常简单。

就我而言:

旧的字段名称是:is_admin 新字段名称为:is_superuser

当我通过python manage.py makemigrations 进行迁移时,它问我是否要重命名该字段。我只是点击y 重命名。然后我通过python manage.py migrate 迁移。在我的案例中,终端历史如下所示:

注意:我没有一次测试多个字段。

【讨论】:

【参考方案5】:

可以在不进行任何手动迁移文件编辑的情况下重命名字段:

▶︎ 从这样的开始:

class Foo(models.Model):
  old_name = models.CharField(max_length=50)

▶︎ 将 db_column=OLD_FIELD_NAME 添加到原始字段。

class Foo(models.Model):
  old_name = models.CharField(max_length=50, db_column='old_name')

▶︎ 跑:python3 manage.py makemigrations

▶︎ 将字段从 OLD_FIELD_NAME 重命名为 NEW_FIELD_NAME

class Foo(models.Model):
  new_name = models.CharField(max_length=50, db_column='old_name')

▶︎ 跑:python3 manage.py makemigrations

系统会提示您:

您是否将 MODEL.OLD_FIELD_NAME 重命名为 MODEL.NEW_FIELD_NAME(ForeignKey)? [是/否] 是

这将生成两个迁移文件,而不仅仅是一个,尽管这两个迁移都是自动生成的。

此过程适用于 Django 1.7+。

【讨论】:

"将 db_column=OLD_FIELD_NAME 添加到原始字段。" -> 嗯?在哪里?我不明白这个答案 @NaturalBornCamper 我添加了一些示例代码,应该使这些步骤更容易理解。 这仅重命名模型字段,数据库列名称保持不变。更进一步,您将如何在不停机的情况下重命名数据库列? RE“如何在不停机的情况下重命名数据库列?”,这是一个更棘手的问题,最好作为一个新问题提出。【参考方案6】:

实际上在 Django 1.10 中,只需重命名模型中的字段,然后运行 ​​makemigrations,立即识别操作(即一个字段消失,另一个字段代替它出现):

$ ./manage.py makemigrations
Did you rename articlerequest.update_at to articlerequest.updated_at (a DateTimeField)? [y/N] y
Migrations for 'article_requests':
  article_requests/migrations/0003_auto_20160906_1623.py:
    - Rename field update_at on articlerequest to updated_at

【讨论】:

只是想强调一下 Django 实际上询问你是否想要重命名(我想你可以回答 NO 并取消你正在运行的 makemigrations) 已确认。也在 Django 2.2.5 上工作过。但是......迁移开始失败,不是因为与模型或数据库直接相关,而是因为使用旧列名的 ModelForm。因此,您需要在运行迁移之前清理表单以及模型本身。我宁愿表单在使用中失败而不是 DDL 迁移错误,但事实就是这样。在 postgres 中进行更改很容易,但 django 迁移完全卡住了,所以我恢复了 DDL 重命名,从 git 恢复并正确完成。 @JLPeyret 我认为您应该阅读问题标题,我已经使用 Django 2.2.9 对其进行了测试,如果您对字段名称进行了硬编码,它就可以正常工作,即作为开发人员,您有责任对 Django 进行适当的更改,因为它正在按预期工作 @Aarif 我完全不明白你的意思。这个问题与我的重命名问题有关,人们有不同的,一些过于复杂的答案,而 Django 确实可以解决这个问题。至于“我的责任”,我希望 DDL 不会在 Form 问题上失败,尽管我希望它会在 Model 问题上失败。我的评论是说它确实有效,并解释了为什么它可能需要小心,并且是出于我的错误某人对可能相似的事情的评论关于你的答案。我建议你少自尊。 我的评论绝不是批评您的正确答案。【参考方案7】:

我在 Django 1.7.7 上遇到了这种情况。我最终做了以下对我有用的事情。

./manage.py makemigrations <app_name> --empty

添加了migrations.RenameField 的一个简单子类,它不涉及数据库:

class RenameFieldKeepDatabaseColumn(migrations.RenameField):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
    pass

def database_forwards(self, app_label, schema_editor, from_state, to_state):
    pass

【讨论】:

【参考方案8】:

在保留 DB 字段的同时更改字段名称

为 Django 1.8+ 添加答案(使用 Django-native 迁移,而不是 South)。

进行迁移,首先添加一个db_column 属性,然后重命名该字段。 Django 明白第一个是无操作(因为它更改了db_column 以保持不变),而第二个是无操作(因为它不更改架构)。我实际上检查了日志以查看没有架构更改...

operations = [
    migrations.AlterField(
        model_name='mymodel',
        name='oldname',
        field=models.BooleanField(default=False, db_column=b'oldname'),
    ),
    migrations.RenameField(
        model_name='mymodel',
        old_name='oldname',
        new_name='newname',
    ),
]

【讨论】:

我不确定AlterField 部分是否必要,至少在 Django 1.9 上是这样。我只使用 RenameField 部分创建了一个迁移,它运行良好。 @luizs81这取决于您是否希望在数据库中重命名列。如果没有 AlterField,您将进行架构更改 在 Django 2+ 中不起作用。但同样的逻辑适用于related_name => 如果保持不变,Django 将检测到该字段已重命名。 小心!这些操作在某些情况下会触发更改,例如外键 (DROP/SET CONSTRAINT)。我很难学会(在生产中)。我向 Django 创建了一张票,并发出了拉取请求来修复这些情况。 code.djangoproject.com/ticket/31825 code.djangoproject.com/ticket/31826 也就是说,您可以使用 manage.py sqlmigrate &lt;app_name&gt; &lt;migration&gt; 检查它将运行哪些 SQL 命令。它依赖于数据库,因此使用与生产中相同的数据库类型运行它。 @iurisilvio 应该做些什么来避免添加/删除约束。有什么办法可以避免吗?【参考方案9】:

我遇到过这种情况。我想更改模型中的字段名称,但保持列名称相同。

我的做法是schemamigration --empty [app] [some good name for the migration]。问题在于,就 South 而言,更改模型中的字段名称​​是一种需要处理的更改。因此必须创建迁移。但是,我们知道在数据库端不需要做任何事情。 因此,空迁移避免了对数据库进行不必要的操作,同时满足了 South 处理它认为是更改的需要。

请注意,如果您使用 loaddata 或使用 Django 的测试夹具工具(在幕后使用 loaddata)。您必须更新固定装置以使用新的字段名称,因为固定装置基于模型字段名称,而不是数据库字段名称。

对于数据库中列名确实发生变化的情况,我从不建议使用db.rename_column 进行列迁移。我使用this answer中sjh描述的方法:

我已将新列添加为一个架构迁移,然后创建了一个数据迁移以将值移动到新字段中,然后创建第二个架构迁移以删除旧列

正如我在comment 中提到的那样,db.rename_column 的问题在于它没有将约束与列一起重命名。我不知道这个问题只是表面上的问题,或者这是否意味着未来的迁移可能会因为找不到约束而失败。

【讨论】:

【参考方案10】:

这很容易修复。但是您必须自己修改迁移。

使用db.rename_column,而不是删除和添加列。您可以简单地修改schemamigration --auto创建的迁移

【讨论】:

不错!我认为会有一个简单的解决方案。因此,如果我将以下内容添加到转发中: db.rename_column('myapp.foo', 'orig_name', 'name') 并将以下内容向后添加: db.rename_column('myapp.foo', 'name', 'orig_name' ) 是否应该将这些更改添加到包含模型定义更改的迁移脚本中?还是我不应该在创建迁移脚本之前手动更改模型定义? (如果我的问题不清楚,请告诉我,我会另作解释) 没关系,这是个愚蠢的问题。我将首先更改模型定义,然后创建一个 --empty 迁移脚本,并更改​​ forwards 和 backwards 方法以包含我上面包含的 rename_column 数据。谢谢! 示例:***.com/questions/3235995/…

以上是关于迁移 django-model 字段名称更改而不丢失数据的主要内容,如果未能解决你的问题,请参考以下文章

迁移到现有数据库而不匹配迁移

使用不同的参考名称迁移工作项。有没有办法更改工作项类型的字段引用名称或设置自定义引用名称?

Django 模型 - 在选择字段中更改选项并迁移

django-Model _meta API

如何使用 FFMPEG 保存 rtsp 流而不丢包

Oracl百分百迁移数据库,不丢表和数据,恢复无错误提醒