Опасность использования 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
Есть одна команда в 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