Squash — сжатие множества миграций в одну для упрощения истории.
Когда нужен squash
- Накопилось 50+ миграций — история тяжёлая
- Разработка завершена, нужно «начать чисто»
- Ускорение запуска тестов (fewer migration files to load)
Как сделать squash
# Сжать миграции с 0001 по 0050 в одну
python manage.py squashmigrations tasks 0001 0050
# С указанием имени результата
python manage.py squashmigrations tasks 0001 0050 --squashed-name initial_squashed
Создаётся файл 0001_squashed_0050_initial_squashed.py.
После squash
-
Проверь, что новая миграция работает:
bash python manage.py migrate --run-syncdb -
Закоммить оба набора (оригиналы + squashed)
-
После того как все окружения применили squashed миграцию, удали оригиналы:
bash # Убери replaces из squashed файла # Удали оригинальные файлы миграций
Когда НЕ делать squash
Squash — необратимая операция с рисками. Не делай его в следующих случаях:
Активный деплой или rolling update. Если на серверах сейчас применяются старые миграции, а squashed ещё нет — возникнет конфликт. Django не сможет определить, какие миграции уже применены.
В команде есть ветки со старыми миграциями. Если коллега создал новую миграцию поверх 0045, а ты сделал squash 0001–0050, у него возникнет конфликт при мерже. Сначала все ветки должны быть смерджены в main.
Производственная БД с долгой историей миграций. Если не все окружения (staging, prod, локальные машины) применили одинаковый набор миграций — squash создаст несогласованность. Убедись, что python manage.py showmigrations показывает одинаковое состояние везде.
RunPython в squashed миграциях
Если в сжимаемых миграциях есть RunPython или RunSQL — squash их включит, но могут быть проблемы:
# Такая операция в оригинальной миграции...
migrations.RunPython(populate_slugs, reverse_code=migrations.RunPython.noop)
- Django включит
RunPythonв squashed-миграцию как есть - Если функция
populate_slugsссылается на модели черезapps.get_model— это нормально - Если функция импортирует модели напрямую (
from .models import Task) — сломается: модель может измениться к моменту запуска squash на новой БД - Всегда проверяй
RunPython-функции вручную после squash
Безопасный способ написать RunPython для squash:
def populate_slugs(apps, schema_editor):
# Используй apps.get_model, а не прямой импорт
Task = apps.get_model('tasks', 'Task')
for task in Task.objects.filter(slug=''):
task.slug = slugify(task.title)
task.save()
Тестирование после squash
После создания squashed-миграции обязательно проверь:
# 1. Запусти все тесты — ничего не должно сломаться
python manage.py test
# 2. Проверь чистую установку (симулирует новое окружение)
python manage.py migrate --run-syncdb
# 3. Убедись что squashed миграция применяется с нуля
# Создай тестовую БД, примени только squashed миграцию
python manage.py migrate tasks 0001_squashed_0050_initial_squashed
Если тесты проходят и чистая миграция работает — squash корректен.
Безопасное удаление старых миграций
Удалять оригинальные миграции можно только когда все окружения уже применили squashed-миграцию:
# 1. Убедись что все окружения применили squashed
python manage.py showmigrations tasks # должен быть [X] у squashed
# 2. Открой squashed файл и удали атрибут replaces
# replaces = [('tasks', '0001_initial'), ('tasks', '0002_...'), ...]
# После удаления Django больше не считает его заменой старых миграций
# 3. Удали оригинальные файлы
find . -path "*/tasks/migrations/0[0-4]*.py" -delete
# 4. Закоммить изменения
git add .
git commit -m "chore: remove squashed original migrations for tasks app"
Если удалить оригиналы раньше времени, окружения где они ещё не применены, не смогут восстановить историю миграций.
Ограничения
- Squash не работает с
RunPythonиRunSQLбез обратных операций - Нельзя делать squash до первой миграции если использовались
initial=True - Осторожно с кастомными операциями
Альтернатива: сбросить миграции (только для разработки)
# Только если база создаётся с нуля!
find . -path "*/migrations/0*.py" -delete
python manage.py makemigrations
python manage.py migrate --fake-initial
Ускорение тестов без squash
# pytest.ini
[pytest]
# Использовать --reuse-db чтобы не пересоздавать БД каждый раз
pytest --reuse-db
💬 Комментарии (0)
Комментариев пока нет
Станьте первым, кто поделится мнением об этой статье!