Когда в Django требуются кастомные миграции?

Ответ

Кастомные миграции в Django необходимы, когда стандартные возможности makemigrations и migrate недостаточны для выполнения сложных операций с базой данных или данными. Это позволяет выполнять произвольный Python-код или SQL-запросы в процессе миграции.

Основные сценарии:

  1. Сложные преобразования данных: При изменении структуры модели, которое требует миграции существующих данных. Например, разделение одного поля на несколько, объединение полей, изменение типа данных с преобразованием значений.
  2. Выполнение произвольного SQL: Для оптимизации миграций на больших таблицах, использования специфичных для СУБД функций, создания индексов с особыми параметрами или выполнения других операций, не поддерживаемых Django ORM напрямую.
  3. Интеграция с внешними системами: Если миграция затрагивает данные или схемы в других базах данных или внешних сервисах, которые не управляются Django.
  4. Создание или изменение не-модельных объектов БД: Например, создание хранимых процедур, триггеров, представлений (views) или функций базы данных.
  5. Откат сложных изменений: Для определения обратной логики (reverse_func) при откате миграции, особенно если forwards_func выполнял необратимые изменения данных.

Пример кастомной миграции (Python):

# app_name/migrations/000X_custom_data_migration.py
from django.db import migrations, models

def forwards_func(apps, schema_editor):
    # Получаем модель User из текущего состояния проекта
    User = apps.get_model('auth', 'User')
    # Получаем доступ к базе данных
    db_alias = schema_editor.connection.alias

    # Пример: Обновление поля 'first_name' для всех пользователей
    # Допустим, мы хотим добавить префикс "User_" к каждому имени
    for user in User.objects.using(db_alias).all():
        user.first_name = f"User_{user.first_name}"
        user.save()

def backwards_func(apps, schema_editor):
    # Логика отката: удаляем префикс
    User = apps.get_model('auth', 'User')
    db_alias = schema_editor.connection.alias
    for user in User.objects.using(db_alias).all():
        if user.first_name and user.first_name.startswith("User_"):
            user.first_name = user.first_name[len("User_"):]
            user.save()

class Migration(migrations.Migration):
    dependencies = [
        ('auth', '0012_alter_user_first_name_max_length'), # Зависимость от предыдущей миграции
    ]

    operations = [
        migrations.RunPython(forwards_func, backwards_func),
        # Можно также использовать migrations.RunSQL для выполнения сырых SQL-запросов
        # migrations.RunSQL("ALTER TABLE myapp_mymodel ADD COLUMN new_field INT DEFAULT 0;",
        #                  "ALTER TABLE myapp_mymodel DROP COLUMN new_field;")
    ]

Использование migrations.RunPython или migrations.RunSQL позволяет гибко управлять эволюцией схемы и данных базы данных, выходя за рамки автоматической генерации миграций Django.