Написание миграций базы данных
В этом документе объясняется, как структурировать и писать миграции базы данных для различных сценариев, с которыми вы можете столкнуться. Для вводных материалов о миграциях см. руководство по теме.
Миграции данных и несколько баз данных
При использовании нескольких баз данных вам может потребоваться определить, нужно ли применять миграцию к определенной базе данных. Например, вы можете захотеть применить миграцию только к конкретной базе данных.
Для этого вы можете проверить псевдоним подключения к базе данных внутри операции RunPython , обратившись к атрибуту schema_editor.connection.alias.
from django.db import migrations
def forwards(apps, schema_editor):
if not 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(object):
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 в качестве подсказки, чтобы сделать ее максимально прозрачной для маршрутизатора. Это особенно важно для многократно используемых и сторонних приложений.
Миграции, добавляющие уникальные поля
Применение «простой» миграции, добавляющей уникальное непустое поле в таблицу с существующими строками, вызовет ошибку, потому что значение, используемое для заполнения существующих строк, генерируется только один раз, нарушая уникальное ограничение.
Поэтому необходимо выполнить следующие шаги. В этом примере мы добавим непустое поле UUIDField со значением по умолчанию. Измените соответствующее поле в соответствии со своими потребностями.
- Добавьте поле в вашу модель с аргументами
default=uuid.uuid4иunique=True(выберите подходящее значение по умолчанию для типа добавляемого поля). - Запустите команду
makemigrations. Это должно сгенерировать миграцию с операциейAddField. - Сгенерируйте два пустых файла миграции для того же приложения, дважды запустив
makemigrations myapp --empty. Мы переименовали файлы миграций, чтобы придать им осмысленные имена в примерах ниже. -
Скопируйте операцию
AddFieldиз автоматически сгенерированной миграции (первого из трех новых файлов) в последнюю миграцию и изменитеAddFieldнаAlterField. Например:# -*- coding: utf-8 -*- # Generated by Django A.B on YYYY-MM-DD HH:MM from __future__ import unicode_literals 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 в примере) для каждой существующей строки. Например:# -*- coding: utf-8 -*- # Generated by Django A.B on YYYY-MM-DD HH:MM from __future__ import unicode_literals from django.db import migrations, models 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() 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перезаписанные.
Управление порядком миграций
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'),
]
Вместо run_before предпочтительнее использовать dependencies когда это возможно. Вы должны использовать 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.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/howto/writing-migrations/