Короткий ответ
Data migration создаётся через manage.py makemigrations --empty appname и содержит операцию RunPython с функцией, которая читает/изменяет данные через исторические версии моделей (apps.get_model), а не через прямой импорт текущих моделей приложения.
Как это работает подробнее
Django передаёт в функцию RunPython специальный объект apps, содержащий историческую версию моделей на момент именно этой миграции — это защищает от падения старых миграций, если метод модели в будущем изменится или исчезнет.
Пример кода
example.py
from django.db import migrations
def set_default_country(apps, schema_editor):
User = apps.get_model("myapp", "User")
User.objects.filter(country__isnull=True).update(country="US")
def reverse_default_country(apps, schema_editor):
pass # осознанно noop: восстановить исходные NULL невозможно без дополнительных данных
class Migration(migrations.Migration):
dependencies = [("myapp", "0005_add_country_field")]
operations = [migrations.RunPython(set_default_country, reverse_default_country)]Что использовать на практике
- Практика. Data migration используют для заполнения новой колонки вычисленными значениями, нормализации данных; для больших таблиц такую миграцию стоит писать с батчингом (iterator() с chunk_size), чтобы не держать долгую транзакцию.
Подводные камни
- Ошибка. Импортируют модель напрямую (from myapp.models import User) вместо apps.get_model("myapp", "User") — миграция может сломаться в будущем; не добавляют reverse_code, оставляя RunPython.noop, хотя откат данных был бы ожидаем.