Как выполнить произвольный Python-код в Django-миграции?

Ответ

Для выполнения произвольного Python-кода в Django-миграциях используется операция migrations.RunPython. Это необходимо для сложных операций с данными, которые нельзя выразить через стандартные изменения схемы, например, для миграции данных между полями, создания начальных объектов или выполнения кастомной логики.

RunPython принимает два аргумента:

  1. forward_func: Функция, которая будет выполнена при применении миграции.
  2. reverse_func: Функция, которая будет выполнена при отмене миграции (опционально, но рекомендуется).

Обе функции получают аргументы apps (исторический реестр приложений) и schema_editor.

Пример:

from django.db import migrations

def create_initial_data(apps, schema_editor):
    # Получаем модель из исторической версии приложения 'myapp'
    MyModel = apps.get_model('myapp', 'MyModel')
    # Создаем объекты
    MyModel.objects.create(name='Test Item 1', value=10)
    MyModel.objects.create(name='Test Item 2', value=20)

def reverse_create_initial_data(apps, schema_editor):
    # Удаляем созданные объекты при откате миграции
    MyModel = apps.get_model('myapp', 'MyModel')
    MyModel.objects.filter(name__startswith='Test Item').delete()

class Migration(migrations.Migration):
    dependencies = [
        # Укажите зависимости от предыдущих миграций
        ('myapp', '0001_initial'),
    ]
    operations = [
        migrations.RunPython(
            create_initial_data,
            reverse_create_initial_data
        ),
    ]

Важно: Всегда используйте apps.get_model('app_label', 'ModelName') вместо прямого импорта моделей (from myapp.models import MyModel). Это гарантирует, что вы работаете с исторической версией модели, которая существовала на момент создания данной миграции, а не с текущей версией, что предотвращает ошибки при изменении схемы в будущем.

Ответ 18+ 🔞

Вот же ж, ну и тема подкинули, про миграции в Джанге. Слушай, а ты знаешь, что там бывает, когда стандартными операциями не обойтись? Ну, типа, надо данные перетасовать, начальные записи накидать или какую-то свою, конченую логику выполнить? Так вот, для этого у них есть такая штука — migrations.RunPython. Без неё, блядь, просто никуда.

Работает это дело так: ты пишешь обычную питонячью функцию, а миграция её запускает. Но есть нюанс, ёпта! Главный аргумент — это forward_func, то есть что делать, когда миграцию применяешь. А второй, reverse_func — это на случай, если ты решишь откатиться, как мудак. Его можно и не писать, но тогда при откате будет пиздец и нестыковка, так что лучше написать.

Обе эти функции получают на вход два объекта: apps (это типа архив всех твоих моделей на момент миграции, очень важная хуйня) и schema_editor (ну, это чтобы с базой напрямую работать, если совсем припёрло).

Смотри, вот тебе живой пример, чтобы не быть просто пиздаболом:

from django.db import migrations

def create_initial_data(apps, schema_editor):
    # Внимание, хуй с горы! Не импортируй модель напрямую!
    # Берём её из исторического архива 'apps'
    MyModel = apps.get_model('myapp', 'MyModel')
    # И создаём что душе угодно
    MyModel.objects.create(name='Test Item 1', value=10)
    MyModel.objects.create(name='Test Item 2', value=20)

def reverse_create_initial_data(apps, schema_editor):
    # А это на случай отката — чистим за собой, как хорошие мальчики
    MyModel = apps.get_model('myapp', 'MyModel')
    MyModel.objects.filter(name__startswith='Test Item').delete()

class Migration(migrations.Migration):
    dependencies = [
        # Тут указываем, от каких предыдущих миграций зависим
        ('myapp', '0001_initial'),
    ]
    operations = [
        migrations.RunPython(
            create_initial_data,          # Вперёд!
            reverse_create_initial_data   # Назад! (если что)
        ),
    ]

И запомни, как Отче наш, блядь: никогда не импортируй модель напрямую через from myapp.models import MyModel. Потому что модель со временем может измениться, а миграция-то останется старая. Получится, что ты пытаешься работать с полями, которых уже нет — и всё, пиздец, вротберунчик. Всегда используй apps.get_model('app_label', 'ModelName'), это даст тебе именно ту версию модели, которая была актуальна на момент создания миграции. Вот так вот, без этого — просто мартышлюшка.