Как написать дата-миграцию
В этой статье мы поговорим об особых миграциях. Если вы не знаете, что такое миграции — вот статья о них.
Обычные миграции меняют структуру таблиц в базе данных: добавляют поля, удаляют поля, создают новые таблицы и удаляют старые. Дата-миграции — это особые миграции, которые не трогают структуру таблиц, но меняют данные в них. Дата-миграции позволяют автоматически рассчитать какое-нибудь поле или, например, перенести данные из одной таблицы в другую. Обычные миграции (их ещё называют схема-миграции) и дата-миграции вместе берут на себя всю работу с данными в Django. Освоив эти два инструмента вы получите полную свободу в плане хранения данных.
Хочешь менять данные — пиши миграцию
Сразу начнём с примера: у вас есть интернет-магазин, на котором одни пользователи могут размещать объявления о продаже товаров, а другие могут эти товары покупать. Вы написали две модели: покупателя и продавца:
class Customer(models.Model):
name = models.CharField(max_length=200)
...
class Seller(models.Model):
name = models.CharField(max_length=200)
...
В ходе активной разработки сайта вы вдруг осознали: продавцы тоже могут быть покупателями, они тоже могут что-нибудь купить. Стало ясно, что нужна модель User
, куда вы соберёте все данные из Customer
и Seller
и затем удалите эти старые модели из БД.
Обычная миграция здесь не поможет. Максимум что вы сможете — это переименовать одну из моделей в User
. Но как перетащить данные из двух моделей в одну?.. Есть вариант с shell
-скриптом: создать модель User
, потом написать скрипт, который перенесёт всех пользователей в новую модель. Но у этого подхода есть два существенных минуса:
- Без инструкции тут не разобраться. У вас есть 2 миграции: одна создаёт модель
User
, другая удаляетCustomer
иSeller
.shell
-скрипт должен работать строго между ними. Если случайно запустить вторую миграцию доshell
-скрипта, то данные удалятся безвозвратно. - Всем разработчикам придётся повторять это шаманство у себя на компьютерах.
Вместо этого можно воспользоваться дата-миграциями и сделать всё абсолютно безопасно, контролируемо и в пару команд. А потом просто запустить migrate
на сервере.
Для начала создайте модель User
:
class User(models.Model):
name = models.CharField(max_length=200)
...
class Customer(models.Model):
name = models.CharField(max_length=200)
...
class Seller(models.Model):
name = models.CharField(max_length=200)
...
Теперь создайте пустую миграцию. Вместо appname
укажите имя приложения, т.е. название папки, где лежит models.py
, который вы собираетесь менять:
$ python manage.py makemigrations --empty appname
Создастся миграция примерно с таким текстом:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('appname', '0009_auto_20170602_1756'),
]
operations = [
]
Далее добавьте в operations
операцию RunPython
. Она делает одну простую вещь: запускает функцию, которую вы ей передадите. Теперь вам нужны две функции: одна переместит в новую модель продавцов, а другая — покупателей:
operations = [
migrations.RunPython(copy_customers_to_users),
migrations.RunPython(copy_sellers_to_users),
]
Осталось только написать эти функции. Вот пример одной из них, допишите её прямо в файл миграции:
def copy_customers_to_users(apps, schema_editor):
User = apps.get_model('appname', 'User')
Customer = apps.get_model('appname', 'Customer')
for customer in Customer.objects.all():
User.objects.get_or_create(name=customer.name)
...
Вместо обычного create
здесь вы используете get_or_create
. Он тоже создает записи в таблице, но делает это иначе. Отлаживая код вы будете запускать миграцию повторно много раз, и метод get_or_create
не даст замусорить таблицу Customer
множеством дублей. Подробнее о том как это работает читайте в статье про идемпотентные дата-миграции и в документации к методу get_or_create.
Обратите внимание, что модели вы не просто импортируете, а получаете с помощью get_model
. Это потому, что код миграций должен запускаться вне зависимости от того, что лежит в models.py
. Следующей миграцией вы удалите модели Customer
и Seller
из файла models.py
, импортировать их оттуда станет невозможно, а дата-миграция обязана работать всё так же исправно.
Никогда не импортируйте из models.py
После написания функций copy_customers_to_users
и copy_sellers_to_users
достаточно запустить migrate
, как для обычной миграции, и ваши данные скопируются в новую модель:
$ python manage.py migrate
...
0010_create_user... OK
0011_copy_customers_and_sellers_to_user... OK
0012_remove_customers_and_sellers... OK
Что почитать
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.