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

How to force migrations to a DB if some tables already exist in Django?

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

现在的场景是这样的:

我需要的:

能够 运行 迁移和 "ignore" 现有表的类型并应用新表。或实现此目的的任何替代方法。

这可能吗?

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

因此,在执行任何其他操作之前,您需要通过删除因某种原因丢失且无法找回的任何迁移文件的 django_migrations table 行,使事情恢复同步. table 应该只包含您确实拥有并且实际正确应用于数据库的那些迁移的行

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

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

那些只包含尚未应用到数据库的更改的迁移文件,运行 没有 --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

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

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

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

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

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

  4. 可以运行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 将 TAKE STUFF OUT您的数据库。例如,您可能在 0003 中添加的列将与其数据一起被删除。既然你说不能丢数据,那回去一定要小心。

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

这个博客 post 非常棒。 https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html

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

以下是我遵循的步骤。一如既往,在开始之前备份所有内容。我确实在 VM 中工作,所以我只是在继续之前拍了一张快照。

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

对我来说效果很好。