忽略所有当前迁移并从当前模型状态开始而不删除迁移 files/history

Ignore all current migrations and start from current model state WITHOUT deleting the migration files/history

前言:使用非常大、非常旧的 Django 应用程序从 Oracle 迁移到 Postgres。

需要能够从我的模型的当前状态开始一个新的数据库,忽略 5 年以上的迁移,但不删除任何迁移文件。

我的解决方案是使用 Django,让它为我完成所有工作。

  1. 使用 default 中的所有变量使用假连接创建数据库。将 USER 更改为 postgres,因为它是唯一可以访问数据库的用户 postgres,并且由于之前的原因,NAME 更改为 postgres。 ('main' 我叫它,它只用了一次,创建数据库)
  2. 弄清楚我的应用程序的路径是什么,遍历我的所有应用程序,如果应用程序在我的路径本地,则在应用程序路径中创建一个临时 migrations_<random> 文件夹,然后 [=18= 】 里面那个。最后,更新 MIGRATION_MODULES 告诉 django 在哪里可以找到所述应用程序的迁移文件夹。
  3. 呼叫./manage.py makemigrations,制作它们。 (记住这将使用来自 MIGRATION_MODULES 的迁移)
  4. 调用 ./manage.py migrate,并应用它们。 (记住这将使用来自 MIGRATION_MODULES 的迁移)
  5. 删除 django_migrations table
  6. 中的所有迁移
  7. (清理)将 MIGRATION_MODULES 设置为原始状态,删除所有应用程序中创建的所有 migrations_<random> 文件夹。
  8. 调用 ./manage.py migrate --fake 以“应用”您在项目中的所有现有迁移

尽情享受吧!

import inspect
import os
import tempfile
from django.apps import apps
from django.db import connection, connections
from contextlib import ExitStack
from django.conf import settings
from django.core.management import call_command
from django.db.migrations.recorder import MigrationRecorder


def create_database_and_models():

    settings.DATABASES['main'] = settings.DATABASES['default'].copy()
    settings.DATABASES['main']['USER'] = 'postgres'
    settings.DATABASES['main']['NAME'] = 'postgres'

    with connections['main'].cursor() as cursor:
        database_name = settings.DATABASES['default']['NAME']
        cursor.execute('CREATE DATABASE %s' % (database_name,))
        print('Create db %s' % (database_name))

    original_migration_modules = settings.MIGRATION_MODULES.copy()
    project_path = os.path.abspath(os.curdir)

    with ExitStack() as stack:
        # Find each app that belongs to the user and are not in the site-packages. Create a fake temporary
        # directory inside each app that will tell django we don't have any migrations at all.
        for app_config in apps.get_app_configs():
            module = app_config.module
            app_path = os.path.dirname(os.path.abspath(inspect.getsourcefile(module)))

            if app_path.startswith(project_path):
                temp_dir = stack.enter_context(tempfile.TemporaryDirectory(prefix='migrations_', dir=app_path))
                # Need to make this directory a proper python module otherwise django will refuse to recognize it
                open(os.path.join(temp_dir, '__init__.py'), 'a').close()
                settings.MIGRATION_MODULES[app_config.label] = '%s.%s' % (app_config.module.__name__,
                                                                          os.path.basename(temp_dir))

        # create brand new migrations in fake migration folders local to the app
        call_command('makemigrations')
        # call migrate as normal, this will just read those fake folders with migrations in them.
        call_command('migrate')

        # delete all the migrations applied, remember these where NOT the real migrations from the project
        MigrationRecorder.Migration.objects.all().delete()

    settings.MIGRATION_MODULES = original_migration_modules

    # Fake all the current migrations in the project
    call_command('migrate', fake=True)

我假设您想在 PostgreSQL 中创建数据库时“重新开始”,但如果需要,仍然能够在旧的 Oracle 数据库上使用(甚至更新)您的迁移。我可以想到 3 个选项来实现:

压缩迁移

在某种程度上,这可以由 Django 本身处理,但 Django 并不总是对所有事情都是正确的,它实际上会留下一些东西来手动修复它们,就像任何 RunPython 操作一样。

通过简单地调用 ./manage.py squashmigrations,您可以要求 Django 为您之前拥有的所有旧迁移创建一个替代迁移。如果之前的任何迁移应用到您的数据库,Django 将忽略试图替换它们的压缩迁移并继续应用旧迁移,但是当应用 none 被压缩迁移替换的迁移时,Django将忽略旧的迁移并只应用压缩的迁移,将其视为实现相同数据库状态的更快方法。

手动为现有迁移创建替换(压缩迁移)

当您在 Django 中压缩迁移时,此解决方案理解了幕后实际发生的事情,它与压缩过程非常相似,但实现方式不同。

在后台,当您尝试压缩迁移时,Django 将创建一个迁移,其中仅包含所有先前迁移的所有步骤(如果可能,应用一些优化)并将添加一个特殊字段 replacesMigration class 里面,包含一个被压扁的旧迁移列表。

这意味着您可以创建自己的迁移来替换其他迁移列表。 这是迁移系统的高级用法,请谨慎使用!实现方法:

  1. 确保您当前的模型状态在 Django 迁移中得到完整体现。要检查,运行 ./manage.py makemigrations YOUR_APP_NAME --dry-run 并确保 returns No changes detected in app 'YOUR_APP_NAME'.
  2. 暂时将所有要替换的迁移移到迁移目录之外,这样 Django 就不知道它们的存在
  3. 运行 ./manage.py migrate YOUR_APP_NAME 生成替换旧迁移的新迁移
  4. 在新创建的迁移中 Migration class 的顶部添加 replaces = [...] 属性,将三个点替换为此迁移将替换的所有迁移的列表(要获取该列表,您可以预先创建一个压缩的迁移,只是为了从中复制 replaces 字段)
  5. 确保新的迁移名称不与任何旧名称冲突。您现在可以毫无问题地更改它的名称,即使这是初始迁移(迁移的名称并不重要,它们只需要在必要时被其他迁移正确引用,您甚至可以摆脱迁移编号)
  6. 将旧迁移移回您的迁移目录
  7. 通过调用 ./manage.py makemigrations YOUR_APP_NAME --dry-运行` 确保迁移仍然创建与以前相同的数据库状态。

请注意,对于此解决方案(对于自动创建的压缩迁移),您需要为每个一致的迁移文件序列(或图形)进行一次替换迁移。这意味着如果您想要替换从 0002 到 0008 的迁移,保留 0009 并替换 0010 到 0012,您将需要 2 个替换迁移,每个序列范围一个。迁移的“图表”也是如此(例如,如果您有两个 0004 迁移和一个合并迁移 0005)。

对于上述两个选项,当至少有一个旧迁移已应用于您的数据库时,Django 将使用旧迁移文件,如果应用了 none 个,则将使用新迁移文件。在 squash 迁移之后创建的每个迁移都将在这两种情况下使用。

为您的数据库保留单独的迁移列表。

也可以将两个迁移列表放在不同的目录中,从而将它们分开。 Django 允许您使用 MIGRATION_MODULES 设置覆盖每个应用程序的迁移文件的默认位置。请注意,迁移将完全分开,因此您需要手动使它们保持同步。在您的应用程序模型中进行一些更改时,您需要 运行 ./manage.py makemigrations 两次,在 运行 之间更改 MIGRATION_MODULES 设置,否则您将不得不从一个位置到另一个位置。我强烈建议不要使用此解决方案,因为它很容易搞乱迁移列表之间的同步,并且除了在从头开始重新创建 Oracle 数据库时出于某种原因需要旧进程的情况外,其他方法也没有任何好处(你仍然可以使用旧方法,但在这种情况下您必须手动调用旧迁移之一。