如果 Django 中已经存在某些表,如何强制迁移到数据库?

Posted

技术标签:

【中文标题】如果 Django 中已经存在某些表,如何强制迁移到数据库?【英文标题】:How to force migrations to a DB if some tables already exist in Django? 【发布时间】:2017-10-08 09:23:00 【问题描述】:

我有一个 Python/Django 项目。由于一些回滚和其他混合因素,我们最终陷入了一种奇怪的情况。

目前的场景是这样的:

DB 有正确的表 数据库无法回滚或删除

代码是最新的

Migrations 文件夹落后一两次迁移后的数据库。 (这些迁移是从其他地方应用的,“其他地方”不再存在)

我添加和更改了一些模型

我运行 makemigrations 创建了新的迁移,但它是新表和数据库中已存在的一些表的混合。 如果我运行 migrate,它会抱怨我尝试创建的某些表已经存在。

我需要什么:

为了能够运行迁移并“忽略”现有表并应用新表。或任何替代方法来实现这一点。

这可能吗?

【问题讨论】:

【参考方案1】:

当您应用迁移时,Django 会在名为 django_migrations 的表中插入一行。这是 Django 知道哪些迁移已经应用,哪些没有应用的唯一方法。因此,该表中的行必须与您的 migrations 目录中的文件匹配。如果您在应用迁移文件后丢失了迁移文件,或者做了其他任何事情以使事情不同步,那么您将遇到问题..因为数据库中的迁移编号与项目中的迁移文件引用的迁移文件不同。

因此,在您执行任何其他操作之前,您需要通过删除 django_migrations 表中的行来使您以某种方式丢失且无法恢复的任何迁移文件恢复同步。 该表应该只包含那些您确实拥有并且实际正确应用到数据库的迁移的行

现在您需要处理 Django Migrations 不知道的数据库中的任何更改......为此有几个选项:

如果事情已经解决,已经应用到数据库的数据库更改与未应用的迁移文件位于不同的迁移文件中,那么您可以使用 --fake 一次运行一个迁移来修复它选择实际上已经在数据库中的任何更改。 fake 选项只是将行写入django_migrations 表,将迁移标记为已完成。仅当数据库实际上已经包含该迁移文件中包含的所有更改时才执行此操作。

那些只包含未应用到数据库的更改的迁移文件,在没有--fake 选项的情况下运行,Django 将应用它们。例如:

# database already has it
manage.py migrate myapp 0003 --fake 
# need it
manage.py migrate myapp 0004
# database already has it
manage.py migrate myapp 0005 --fake

如果您的迁移文件中应用了部分但不是全部更改,那么您的问题就更大了。在这种情况下,有几种方法可以解决(仅选择一种):

    编辑迁移文件以将已经应用的更改(无论是 Django 执行还是您手动执行都无关紧要)放入编号较小的迁移中,并将您需要完成的所有内容放入编号较大的文件中。现在您可以--fake 较低编号的,并正常运行较高编号的。假设您对模型进行了 10 次更改,其中 5 次实际上已经在数据库中,但是 Django 不知道它们。所以当您运行 makemigrations 时,将创建一个包含所有 10 项的新迁移变化。这通常会失败,因为数据库服务器无法添加例如已经存在的列。将这些已应用的更改从您的新迁移文件中移出,移到之前的(已应用的)迁移文件中。然后 Django 将假定这些已与先前的迁移一起应用,并且不会尝试再次应用它们。然后您可以像往常一样migrate,新的更改将被应用。

    如果您不想修改旧的迁移文件,更简洁的方法是首先运行 makemigrations --empty appname 创建一个空的迁移文件。然后运行makemigrations,它将创建另一个迁移,其中包含 Django 认为需要完成的所有更改。将该文件中已完成的迁移移动到您创建的空迁移中。然后--fake 那个。这将使 Django 对数据库外观的理解与现实保持同步,然后您可以像往常一样migrate,应用上次迁移文件中的更改。

    删除您刚刚使用 makemigrations 创建的任何新迁移。现在,注释掉或放回模型中尚未应用于数据库的任何内容,让您的代码与数据库中的实际内容相匹配。现在您可以执行makemigrationsmigrate appname --fake,您将恢复同步。然后取消注释您的新代码并正常运行'makemigrations'然后migrate,更改将被应用。如果更改很小(例如,添加几个字段),有时这是最简单的。如果变化很大,那就不是......

    您可以继续(小心地)自己更改数据库,使数据库保持最新。现在只需运行migrate --fake,如果您没有搞砸,那么一切都会好起来的。同样,这对于较小的更改很容易,对于复杂的更改则不那么容易。

    您可以运行manage.py sqlmigrate > mychanges.sql。这会生成mychanges.sql,其中包含 Django 将对数据库执行的所有 SQL。现在编辑该文件以删除已应用的任何更改,留下需要做的事情。使用pgadminpsql 执行该SQL(我希望你使用的是postgresql)。现在所有更改都已完成.. 所以你可以运行manage.py migrate --fake,这将使 Django 与现实同步,你应该一切就绪。如果您的 SQL 技能足够,这可能是最直接的解决方案。

我应该添加两个警告:

首先,如果您应用稍后的迁移,例如 0003_foobar.py,然后事情没有解决,并且您决定尝试返回并应用 0002_bazbuz.py,那么 Django 将从您的数据库中取出内容。例如,您可能在 0003 中添加的列将连同其数据一起被删除。既然你说你不能丢失数据,那么返回时要非常小心。

其次,不要急于运行--fake 迁移。确保您要伪造的整个迁移实际上已经在数据库中。否则它会变得非常混乱。如果您确实后悔伪造迁移并且不想回滚,则可以通过从 django_migrations 表中删除该行来消除 django 对伪造迁移的了解。可以这样做..如果您了解自己在做什么。如果您知道迁移确实没有应用,那就没关系。

【讨论】:

更清晰,你可以写一个博客来帮助更多的人。 救命稻草。这应该是官方文档的一部分。我无法告诉您有多少人遇到了迁移问题。对我来说,如果我不是日常的 SQL 驱动程序,我会过得更艰难。谢谢!【参考方案2】:

这篇博文真的很到位。 https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html

让我总结一下他的场景 2 中的步骤(您有一个生产数据库并希望更改一个或多个应用程序中的架构/模型)。就我而言,我有两个应用程序,队列和路由表,它们具有我需要应用于生产系统的模型修改。关键是我已经有了数据库,所以这就是 --fake-initial 发挥作用的地方。

这是我遵循的步骤。与往常一样,在开始之前备份所有内容。我确实在虚拟机中工作,所以在继续之前我只是拍了一张快照。

1) 删除每个应用的迁移历史记录。

python manage.py migrate --fake queue zero
python manage.py migrate --fake routingslip zero

2) 清除应用程序所在的整个项目中的所有迁移文件

find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
find . -path "*/migrations/*.pyc"  -delete

3) 进行迁移

python manage.py makemigrations

4) 应用迁移,伪装初始,因为数据库已经存在,我们只想要更改:

python manage.py migrate --fake-initial

对我来说效果很好。

【讨论】:

如果您有一个历史记录很少的简单数据库,则删除所有迁移并重新开始工作正常。但是,如果您有一个随着时间的推移而建立的数据库,并且希望能够恢复旧的数据库备份,那么如果您删除了迁移历史记录,您将遇到问题

以上是关于如果 Django 中已经存在某些表,如何强制迁移到数据库?的主要内容,如果未能解决你的问题,请参考以下文章

如果项目文件夹已经存在,则强制 django-admin startproject

Django 表已经存在

如果在 django 中使用多个数据库,如何仅迁移所需的模型表

django 模型创建连接到 mssql

如何强制对我的 Django 应用程序的某些 URL 使用 SSL?

查询 django 迁移表