📝 Django

Squash миграций в Django

P
Автор
Pyland
📅
Опубликовано
30.06.2026
⏱️
Время чтения
2 мин
👁️
Просмотров
89
🌳
Уровень
Продвинутый
🐦 💼 ✈️

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

  1. Проверь, что новая миграция работает:
    bash python manage.py migrate --run-syncdb

  2. Закоммить оба набора (оригиналы + squashed)

  3. После того как все окружения применили 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)

🔐 Войдите в систему, чтобы оставить комментарий
🚪 Войти
💭

Комментариев пока нет

Станьте первым, кто поделится мнением об этой статье!

🔗 Похожие

Похожие статьи

Продолжите изучение с этими материалами

📝

pytest-django: тестирование Django

Охватываемые темы: Установка, @pytest.mark.djangodb, Фикстуры, Тестирование views.

📅 30.06.2026 👁️ 137
📝

Django: Теги шаблонов

Теги шаблонов — это логика внутри HTML. В отличие от {{ переменная }}, которая только...

📅 30.06.2026 👁️ 85
📝

Django: Статические файлы

Статические файлы — CSS, JavaScript, изображения, шрифты. Django обрабатывает их особым образом: в разработке раздаёт...

📅 30.06.2026 👁️ 74

Понравилась статья?

Подпишитесь на наши обновления и получайте новые статьи первыми. Развивайтесь вместе с PyLand!