Добавить в корзинуПозвонить
Найти в Дзене
Павлин Шарит

Опасность использования loaddata в миграциях

Есть одна команда в Django, которая работает идеально - ровно до того момента, пока ты не попробуешь накатить миграции с нуля Речь о call_command("loaddata", ...) внутри миграции Выглядит невинно: добавляешь начальные данные в базу прямо при создании схемы - удобно, лаконично. Но потом ты добавляешь новое поле в модель, и при прогоне миграций с нуля всё ломается Почему? Когда ты пишешь RunPython и вызываешь apps.get_model() - ты получаешь историческую модель. Это не та модель, что у тебя сейчас в models.py, а её слепок на момент конкретной миграции. Django специально реконструирует её из цепочки миграций, чтобы INSERT и UPDATE содержали только те колонки, которые реально существуют в базе прямо сейчас - в процессе накатки А вот loaddata на эту логику плевать хотел - он всегда импортирует модель напрямую из приложения, то есть берёт текущее состояние models.py со всеми полями, которые ты добавил позже. В итоге команда пытается сделать INSERT с колонкой, которой в базе ещё нет Реше

Опасность использования loaddata в миграциях

Есть одна команда в Django, которая работает идеально - ровно до того момента, пока ты не попробуешь накатить миграции с нуля

Речь о call_command("loaddata", ...) внутри миграции

Выглядит невинно: добавляешь начальные данные в базу прямо при создании схемы - удобно, лаконично. Но потом ты добавляешь новое поле в модель, и при прогоне миграций с нуля всё ломается

Почему?

Когда ты пишешь RunPython и вызываешь apps.get_model() - ты получаешь историческую модель. Это не та модель, что у тебя сейчас в models.py, а её слепок на момент конкретной миграции. Django специально реконструирует её из цепочки миграций, чтобы INSERT и UPDATE содержали только те колонки, которые реально существуют в базе прямо сейчас - в процессе накатки

А вот loaddata на эту логику плевать хотел - он всегда импортирует модель напрямую из приложения, то есть берёт текущее состояние models.py со всеми полями, которые ты добавил позже. В итоге команда пытается сделать INSERT с колонкой, которой в базе ещё нет

Решение - отказаться от loaddata в пользу RunPython

Читаем JSON сами, создаём объекты через историческую модель из apps.get_model(). Тогда Django собирает INSERT только по полям, которые реально существуют на момент этой миграции - и никакие будущие изменения схемы её не сломают

def load_fixture(apps, schema_editor):

MyModel = apps.get_model("myapp", "MyModel")

MyModel.objects.all().delete()

fixture_path = (

Path(__file__).resolve().parent.parent

/ "fixtures"

/ "my_fixture.json"

)

with fixture_path.open("r", encoding="utf-8") as f:

data = json.load(f)

objs = [

MyModel(**item["fields"], pk=item.get("pk"))

for item in data

if item.get("model") == "myapp.mymodel"

]

MyModel.objects.bulk_create(objs)

Простое правило - миграции должны быть детерминированными и самодостаточными, без привязки к текущему состоянию моделей

Поддержать на Boosty

Посмотреть на Youtube