Перейти к содержанию

Миграции

Когда модели меняются, таблицы в базе должны меняться вместе с ними. Миграции записывают каждое изменение в файл, который можно применить, посмотреть и откатить — схема развивается контролируемыми шагами, а не правками руками.

Каждая миграция — это JSON-файл с SQL для применения (forward), SQL для отмены (reverse) и снимком результирующей схемы.

Рабочий процесс

endo makemigrations initial     # записать migrations/0001_initial.json из моделей
endo migrate                    # применить ожидающие миграции
endo showmigrations             # [x] применена  /  [ ] ожидает
endo sqlmigrate 0001            # напечатать forward-SQL миграции
endo migrate 0002               # применить до 0002 включительно
endo rollback                   # отменить самую свежую миграцию
endo rollback --steps 2         # отменить две последние

Миграции записывают себя в таблицу endocore_migrations, поэтому migrate идемпотентен.

Что определяется автоматически

  • Создание / удаление таблиц (включая through-таблицы M2M).
  • Добавление / удаление колонок (ALTER TABLE ... ADD/DROP COLUMN).
  • Создание / удаление индексов (из db_index, FK и Meta.indexes).
  • Изменение колонок (тип/null) → переносимая пересборка таблицы: создаётся новая таблица с новой схемой, копируются данные, старая удаляется, новая переименовывается. Данные сохраняются, изменение обратимо.

Переименование колонок

Автоматически отличить переименование от «удалить+добавить» невозможно, поэтому переименования явные:

endo makemigrations rename_fullname --rename user.fullname=name

Это порождает ALTER TABLE "user" RENAME COLUMN "fullname" TO "name" (и обратную операцию), работает на SQLite (≥ 3.25) и PostgreSQL и сохраняет данные.

Миграции данных

Миграции схемы вычисляются автоматически диффом; преобразования данных (забэкфиллить новую колонку, изменить форму JSON-блоба, слить строки) — это не то, что можно «сравнить», поэтому вместо одноразового скрипта пишете Python-файл:

endo makemigrations backfill_slugs --python   # создаёт migrations/0003_backfill_slugs.py
# migrations/0003_backfill_slugs.py
def forward(conn) -> None:
    from Models.post import Post
    for post in Post.objects.all():
        post.slug = post.title.lower().replace(" ", "-")
        post.save()


def reverse(conn) -> None:
    raise NotImplementedError("this data migration cannot be reversed")

Она нумеруется в той же истории, что и миграции схемы — migrate, rollback и showmigrations видят её и применяют по порядку, вместо скрипта, который нужно не забыть запустить в нужный момент относительно схемной миграции, от которой он зависит. forward/reverse выполняются в собственном блоке atomic(), поэтому брошенное исключение откатывает уже сделанные записи, а миграция не отмечается как применённая. Импортируйте и используйте свои модели напрямую — к моменту выполнения миграции приложение уже сконфигурировано. Не определяйте reverse (или бросайте исключение, как в сгенерированной заглушке), если миграцию нельзя отменить — тогда rollback явно упадёт с ошибкой вместо молчаливого бездействия.

Программный API

from endocore.orm import Migrator, get_models

m = Migrator(get_models())              # или Migrator([Post, Author])
m.makemigrations("initial")
m.makedatamigration("backfill_slugs")   # создаёт пустую заглушку forward()/reverse()
m.migrate()
m.showmigrations()                      # [("0001_initial", True), ...]
m.rollback(steps=1)

Границы (бета)

  • Преобразования колонок сложнее пересборки (например, разбить одну колонку на две) всё ещё требуют миграции данных в паре со схемной.
  • Миграции генерируются для настроенного диалекта вашего проекта; при смене бэкенда перегенерируйте их.

Прототипирование

Для быстрых прототипов миграции можно пропустить и вызвать create_all(*models) — но для всего, что эволюционирует, используйте миграции.