Миграции: версионирование схемы БД
Миграции — это система контроля версий для структуры базы. Вы меняете модели в Python, а Django генерирует и применяет нужные изменения схемы.
Суть: makemigrations превращает изменения моделей в файлы-инструкции, migrate применяет их к базе. Миграции версионируются и хранятся в git вместе с кодом.
Зачем нужны миграции
Структура базы со временем меняется: добавили поле, переименовали столбец, создали индекс. Делать это руками через SQL опасно: легко забыть применить изменение на сервере, рассинхронизировать базы разработчиков, потерять данные. Миграции решают проблему: каждое изменение схемы фиксируется в файле, файлы нумеруются и складываются в git. Любой разработчик и любой сервер применяет одну и ту же последовательность — и базы гарантированно совпадают.
Две команды
Весь рабочий цикл — это две команды. makemigrations смотрит на разницу между вашими моделями и последней миграцией, и генерирует новый файл-инструкцию. migrate применяет ещё не применённые миграции к базе.
python manage.py makemigrations
python manage.py migrate
Поток выглядит так:
Меняем models.py
│
▼
makemigrations ── создаёт blog/migrations/0002_xxx.py
│ (Python-описание: что добавить/изменить)
▼
migrate ──────── выполняет SQL: ALTER TABLE / CREATE INDEX
│
▼
django_migrations ── служебная таблица: какие миграции применены
Django помнит применённые миграции в служебной таблице django_migrations, поэтому migrate можно вызывать многократно — он применит только новое.
Что внутри файла миграции
Файл миграции — обычный Python. Он содержит список операций (CreateModel, AddField, AlterField) и зависимости от предыдущих миграций. Обычно его не пишут руками — Django генерирует автоматически. Посмотреть, какой SQL будет выполнен, помогает команда sqlmigrate:
python manage.py sqlmigrate blog 0001
Как это работает под капотом
Django строит граф зависимостей миграций: каждая миграция знает, после какой она идёт. Это направленный граф — как список задач, которые нужно выполнить в правильном порядке. Такую задачу топологической сортировки легко смоделировать:
# Попробуй сам ▶ — порядок применения миграций (топосорт)
deps = {
"0001_initial": [],
"0002_add_slug": ["0001_initial"],
"0003_add_index": ["0002_add_slug"],
"0004_author_fk": ["0001_initial"],
}
applied, order = set(), []
def apply(name):
if name in applied:
return
for parent in deps[name]:
apply(parent)
applied.add(name)
order.append(name)
for m in deps:
apply(m)
print("Порядок применения:")
for i, m in enumerate(order, 1):
print(f" {i}. {m}")
Django делает ровно это: сначала применяет «родителей», потом «детей», чтобы база всегда была в согласованном состоянии.
Откат и сброс
Миграции обратимы: migrate blog 0002 откатит базу до состояния второй миграции. Это полезно при экспериментах. На проде откатывать опасно, если миграция удаляла данные.
Data-миграции: изменение самих данных
Иногда нужно не поменять структуру, а преобразовать существующие данные: заполнить новое поле, разбить одно поле на два, нормализовать значения. Для этого создают пустую миграцию через makemigrations --empty и пишут операцию RunPython с двумя функциями — «вперёд» и «назад» (для отката). Важно: внутри data-миграции модель берут не импортом, а через apps.get_model("blog", "Post") — так вы получаете «историческую» версию модели, соответствующую этому моменту в истории миграций, а не текущему коду. Иначе при будущих изменениях модели старая миграция сломается. Это тонкий, но важный момент: миграции должны оставаться воспроизводимыми спустя месяцы, даже когда модель уже выглядит иначе.
Частые ошибки
- Менять модель и забыть makemigrations. База останется со старой схемой, появятся ошибки «no such column».
- Не коммитить файлы миграций в git. Тогда на сервере и у коллег схема разойдётся.
- Редактировать уже применённую миграцию. Django запутается. Создавайте новую.
- Удалять файлы миграций, чтобы «начать заново». На проде это рассинхронизирует
django_migrationsи реальную схему. - Добавлять обязательное поле без default к таблице с данными. Django спросит, чем заполнить старые строки.
Best practices
- Делайте маленькие миграции и осмысленно их называйте:
makemigrations --name add_post_slug. - Всегда коммитьте миграции вместе с изменением модели — это часть истории.
- Перед деплоем прогоняйте
migrateна копии прод-базы. - Сложные изменения данных делайте отдельной data-миграцией с
RunPython.
Итоги
Миграции — версионирование схемы базы. makemigrations фиксирует изменения моделей в файлы, migrate применяет их. Файлы хранятся в git, применяются в порядке зависимостей, и гарантируют, что схема одинакова везде. Теперь научимся доставать из базы данные.