Написание миграций базы данных
Этот документ объясняет, как структурировать и писать миграции базы данных для различных сценариев, с которыми вы можете столкнуться. Для вводной информации о миграциях см. руководство по темам.
Миграции данных и несколько баз данных
При использовании нескольких баз данных вам может потребоваться выяснить, следует ли выполнять миграцию с той или иной базой данных. Например, вы можете захотеть выполнить миграцию только для конкретной базы данных.
Для этого можно проверить псевдоним подключения к базе данных внутри операции RunPython путем просмотра атрибута schema_editor.connection.alias.
from django.db import migrations
def forwards(apps, schema_editor):
if schema_editor.connection.alias != 'default':
return
# Your migration code goes here
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards),
]
Вы также можете предоставить подсказки, которые будут переданы методу allow_migrate() маршрутизаторов баз данных как **hints.
class MyRouter:
def allow_migrate(self, db, app_label, model_name=None, **hints):
if 'target_db' in hints:
return db == hints['target_db']
return True
Затем, чтобы использовать это в своих миграциях, сделайте следующее:
from django.db import migrations
def forwards(apps, schema_editor):
# Your migration code goes here
...
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards, hints={'target_db': 'default'}),
]
Если операция RunPython или RunSQL затрагивает только одну модель, рекомендуется передать model_name в качестве подсказки, чтобы сделать ее максимально прозрачной для маршрутизатора. Это особенно важно для многократно используемых и сторонних приложений.
Миграции, добавляющие уникальные поля
Применение «простой» миграции, добавляющей уникальное поле не допускающее NULL в таблицу с существующими строками, вызовет ошибку, потому что значение, используемое для заполнения существующих строк, генерируется только один раз, тем самым нарушая уникальное ограничение.
Поэтому необходимо выполнить следующие действия. В этом примере мы добавим поле UUIDField не допускающее NULL с значением по умолчанию. Измените соответствующее поле в соответствии с вашими потребностями.
- Добавьте поле в вашу модель с аргументами
default=uuid.uuid4иunique=True(выберите подходящее значение по умолчанию для типа поля, которое вы добавляете). - Запустите команду
makemigrations. Это должно сгенерировать миграцию с операциейAddField. - Создайте два пустых файла миграции для того же приложения, дважды запустив
makemigrations myapp --empty. В примерах ниже мы переименовали файлы миграций, чтобы придать им осмысленные имена. -
Скопируйте операцию
AddFieldиз сгенерированной миграции (первый из трех новых файлов) в последнюю миграцию, изменитеAddFieldнаAlterField, и добавьте импортыuuidиmodels. Например:# Generated by Django A.B on YYYY-MM-DD HH:MM from django.db import migrations, models import uuid class Migration(migrations.Migration): dependencies = [ ('myapp', '0005_populate_uuid_values'), ] operations = [ migrations.AlterField( model_name='mymodel', name='uuid', field=models.UUIDField(default=uuid.uuid4, unique=True), ), ] -
Измените первый файл миграции. Класс сгенерированной миграции должен выглядеть примерно так:
class Migration(migrations.Migration): dependencies = [ ('myapp', '0003_auto_20150129_1705'), ] operations = [ migrations.AddField( model_name='mymodel', name='uuid', field=models.UUIDField(default=uuid.uuid4, unique=True), ), ]Измените
unique=Trueнаnull=True- это создаст промежуточное поле NULL и отложит создание уникального ограничения до тех пор, пока мы не заполним уникальные значения во всех строках. -
В первом пустом файле миграции добавьте операцию
RunPythonилиRunSQLдля генерации уникального значения (UUID в примере) для каждой существующей строки. Также добавьте импортuuid. Например:# Generated by Django A.B on YYYY-MM-DD HH:MM from django.db import migrations import uuid def gen_uuid(apps, schema_editor): MyModel = apps.get_model('myapp', 'MyModel') for row in MyModel.objects.all(): row.uuid = uuid.uuid4() row.save(update_fields=['uuid']) class Migration(migrations.Migration): dependencies = [ ('myapp', '0004_add_uuid_field'), ] operations = [ # omit reverse_code=... if you don't want the migration to be reversible. migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), ] -
Теперь вы можете применить миграции как обычно, используя команду
migrate.Обратите внимание, что существует проблема гонки, если вы разрешаете создание объектов во время выполнения этой миграции. Объекты, созданные после
AddFieldи доRunPython, будут иметь свои исходныеuuidперезаписаны.
Неатомарные миграции
В базах данных, которые поддерживают транзакции DDL (SQLite и PostgreSQL), миграции по умолчанию выполняются внутри транзакции. Для таких случаев, как выполнение миграций данных для больших таблиц, вы можете предотвратить выполнение миграции в транзакции, установив атрибут atomic в False.
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
В таких миграциях все операции выполняются без транзакции. Можно выполнить части миграции внутри транзакции, используя atomic() или передав atomic=True в RunPython.
Вот пример неатомарной миграции данных, которая обновляет большую таблицу меньшими партиями:
import uuid
from django.db import migrations, transaction
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
while MyModel.objects.filter(uuid__isnull=True).exists():
with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4()
row.save()
class Migration(migrations.Migration):
atomic = False
operations = [
migrations.RunPython(gen_uuid),
]
Атрибут atomic не оказывает никакого влияния на базы данных, которые не поддерживают транзакции DDL (например, MySQL, Oracle).
Управление порядком миграций
Django определяет порядок применения миграций не по имени файла каждой миграции, а путем построения графа, используя два свойства класса Migration: dependencies и run_before.
Если вы использовали команду makemigrations, вы, вероятно, уже видели dependencies в действии, потому что автоматически созданные миграции имеют это значение, определенное в процессе создания.
Свойство dependencies объявляется так:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('myapp', '0123_the_previous_migration'),
]
Обычно этого будет достаточно, но время от времени вам может потребоваться убедиться, что ваша миграция выполняется *до* других миграций. Это полезно, например, для того, чтобы миграции сторонних приложений выполнялись *после* замены вашей AUTH_USER_MODEL.
Для этого поместите все миграции, которые должны зависеть от вашей, в атрибут run_before вашего класса Migration:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_do_awesome'),
]
По возможности используйте dependencies вместо run_before. Вы должны использовать run_before только в том случае, если указать dependencies в миграции, которую вы хотите выполнить после той, которую вы пишете, нежелательно или непрактично.
Миграция данных между приложениями сторонних разработчиков
Вы можете использовать миграцию данных для перемещения данных из одного приложения стороннего разработчика в другое.
Если вы планируете удалить старое приложение позже, вам нужно установить свойство dependencies в зависимости от того, установлено ли старое приложение. В противном случае у вас будут отсутствующие зависимости после удаления старого приложения. Аналогично, вам нужно будет перехватить LookupError в вызове apps.get_model(), который извлекает модели из старого приложения. Этот подход позволяет вам развернуть свой проект в любом месте без предварительной установки и последующего удаления старого приложения.
Вот пример миграции:
from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
OldModel = apps.get_model('old_app', 'OldModel')
except LookupError:
# The old app isn't installed.
return
NewModel = apps.get_model('new_app', 'NewModel')
NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all()
)
class Migration(migrations.Migration):
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]
dependencies = [
('myapp', '0123_the_previous_migration'),
('new_app', '0001_initial'),
]
if global_apps.is_installed('old_app'):
dependencies.append(('old_app', '0001_initial'))
Также подумайте, что должно произойти при отмене миграции. Вы можете либо ничего не делать (как в примере выше), либо удалить часть или все данные из нового приложения. Соответственно, измените второй аргумент операции RunPython.
Изменение необработанной модели на управляемую
Если вы хотите изменить необработанную модель (managed=False) на управляемую, вы должны удалить managed=False и сгенерировать миграцию перед внесением других изменений в схему модели, поскольку изменения схемы, которые появляются в миграции, содержащей операцию для изменения Meta.managed, могут не быть применены.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/howto/writing-migrations/