Ответ
Кастомные миграции в Django необходимы, когда стандартных операций makemigrations (изменение схемы БД) недостаточно для обновления состояния базы данных.
Когда это необходимо:
- Миграция данных (Data Migration): Заполнение новых полей на основе значений существующих, преобразование формата данных в колонке.
- Выполнение сырых SQL-запросов: Создание триггеров, хранимых процедур или использование специфичных для СУБД функций, которые не поддерживаются Django ORM.
- Зависимости от внешних систем: Например, если нужно синхронизировать данные с внешним API после изменения схемы.
- Сложные переименования или реструктуризация: Когда простое переименование поля в модели может привести к потере данных.
Как создать и использовать:
- Создать пустой файл миграции:
python manage.py makemigrations --empty yourappname - Написать код миграции:
В сгенерированном файле используются операции
migrations.RunPythonдля выполнения Python-кода илиmigrations.RunSQLдля SQL-запросов.
Пример: migrations.RunPython для преобразования данных
Предположим, мы хотим объединить поля first_name и last_name в новое поле full_name.
Файл миграции 0002_populate_full_name.py:
from django.db import migrations
def combine_names(apps, schema_editor):
# Получаем историческую версию модели, чтобы миграция не зависела от текущего кода
User = apps.get_model('auth', 'User')
for user in User.objects.all():
user.full_name = f"{user.first_name} {user.last_name}".strip()
user.save()
# Функция для отката миграции (опционально, но рекомендуется)
def uncombine_names(apps, schema_editor):
User = apps.get_model('auth', 'User')
User.objects.update(full_name="")
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'), # Зависит от предыдущей миграции
]
operations = [
migrations.RunPython(combine_names, reverse_code=uncombine_names),
]
Ключевые моменты:
apps.get_model(): Всегда используйте этот метод вместо прямого импорта модели, чтобы работать с её версией на момент применения миграции.reverse_code: Предоставляйте функцию для отката изменений, чтобы миграцию можно было отменить.- Производительность: Для больших объемов данных используйте
queryset.iterator()илиqueryset.bulk_update()для экономии памяти.
Ответ 18+ 🔞
А, ну вот, смотри, классика жанра! Кастомные миграции в Django — это когда стандартный makemigrations смотрит на твою модель, пожимает плечами и говорит: «Ну, тут, блядь, я пас, разбирайся сам».
Когда это, сука, нужно:
- Миграция данных (Data Migration): Ну, например, ты добавил поле
full_name, а в старых записях там пусто. Надо взятьfirst_nameиlast_name, склеить их и запихнуть на место. Или, допустим, ты раньше хранил телефон как строку, а теперь хочешь его чистить и валидировать — надо пройтись по всей базе и поправить. - Сырой SQL: Когда тебе надо нагородить какую-нибудь хранимую процедуру, триггер или использовать фичу базы данных, про которую Django ORM даже не слышал. ORM в такие моменты делает вид, что не понимает, о чём ты.
- Зависимости от внешнего мира: Скажем, после добавления поля нужно пойти нахуй к какому-нибудь API, дёрнуть оттуда данные и заполнить это поле. Стандартные миграции на такое не способны, им бы свою схему поправить.
- Сложные переезды: Просто переименовать поле — легко. А если тебе надо разбить одну таблицу на две, перенести часть данных, при этом не потерять связи? Вот тут и начинается настоящий цирк, блядь.
Как это, сука, делается:
-
Создаёшь пустой файл для творчества:
python manage.py makemigrations --empty yourappnameDjango тебе любезно накидает заготовочку, а дальше — твой выход.
-
Пишешь код, который всё и поломает (или починит): В этом файле тебе доступны две главные пушки:
migrations.RunPython(чтобы запустить твой питонячий код) иmigrations.RunSQL(чтобы впендюрить в базу чистый SQL, от которого у ORM случится инфаркт).
Пример на живца: migrations.RunPython
Допустим, у нас есть модель User от django.contrib.auth. Мы добавили поле full_name, и надо его заполнить из first_name и last_name.
Вот так выглядит файл миграции 0002_populate_full_name.py:
from django.db import migrations
def combine_names(apps, schema_editor):
# ВАЖНО! Не импортируй модель напрямую! Берём её историческую версию.
# Иначе через полгода, когда модель изменится, миграция ебнется.
User = apps.get_model('auth', 'User')
for user in User.objects.all():
user.full_name = f"{user.first_name} {user.last_name}".strip()
user.save()
# Функция для отката. Если что-то пойдёт не так, сможем откатиться.
def uncombine_names(apps, schema_editor):
User = apps.get_model('auth', 'User')
User.objects.update(full_name="")
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'), # Цепляемся к предыдущей миграции
]
operations = [
migrations.RunPython(combine_names, reverse_code=uncombine_names),
]
На что смотреть, чтобы не обосраться:
apps.get_model(): Это святое, блядь. Всегда используй только этот метод. Иначе твоя миграция станет хрупкой, как яичко фаберже, и сломается при первом же изменении модели.reverse_code: Старайся писать функцию для отката. Это как страховочная верёвка. Без неё откатить миграцию будет сложно, а иногда и невозможно. Не будь мудаком, как тот Герасим из рассказа — предусматривай обратный ход.- Производительность: Если у тебя в таблице не десять записей, а десять тысяч, то цикл
for user in User.objects.all()спасёт тебе всю оперативку. Используйiterator()илиbulk_update(), чтобы не превратить сервер в кирпич. Иначе админ базы данных придёт и лично впендюрит тебе этот самыйiterator()куда подальше.
Вот и вся магия. Главное — не бояться и помнить, что кастомная миграция это последнее средство, когда ORM уже бессилен. А то некоторые так увлекаются, что начинают всю бизнес-логику в миграции пихать, а потом удивляются, почему ничего не работает.