Как создать миграции базы данных
В этом документе объясняется, как структурировать и написать миграции базы данных для различных сценариев, с которыми вы можете столкнуться. Для вводного материала по миграциям см. руководство по теме.
Миграции данных и несколько баз данных
При использовании нескольких баз данных вам может потребоваться определить, применять ли миграцию к определённой базе данных. Например, вы можете захотеть применить миграцию только к определённой базе данных.
Для этого вы можете проверить псевдоним подключения к базе данных внутри операции 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:
myapp/dbrouters.pyclass 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. Например:0006_remove_uuid_null.py# 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), ), ] -
Отредактируйте первый файл миграции. Сгенерированный класс миграции должен быть похож на этот:
0004_add_uuid_field.pyclass 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. Например:0005_populate_uuid_values.py# 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). (Поддержка атомарных операторов DDL в MySQL относится к отдельным операторам, а не к нескольким операторам, заключённым в транзакцию, которая может быть отменена).
Управление порядком миграций
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() , который извлекает модели из старого приложения. Этот подход позволяет развернуть ваш проект в любом месте без предварительной установки и последующего удаления старого приложения.
Вот пример миграции:
myapp/migrations/0124_move_old_app_to_new_app.pyfrom 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.
Изменение ManyToManyField для использования модели through
Если вы изменяете ManyToManyField для использования модели through , стандартная миграция удалит существующую таблицу и создаст новую, потеряв существующие отношения. Чтобы этого избежать, вы можете использовать SeparateDatabaseAndState для переименования существующей таблицы в новое имя таблицы, сообщая детекторам миграций о создании новой модели. Вы можете проверить имя существующей таблицы с помощью sqlmigrate или dbshell. Вы можете проверить новое имя таблицы с помощью свойства _meta.db_table модели через. Ваша новая модель through должна использовать те же имена для ForeignKey , что и Django. Также, если она требует дополнительных полей, они должны быть добавлены в операциях после SeparateDatabaseAndState.
Например, если у нас была модель Book с ManyToManyField , связанной с Author, мы могли бы добавить модель через AuthorBook с новым полем is_primary, как показано ниже:
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
# Old table name from checking with sqlmigrate, new table
# name from AuthorBook._meta.db_table.
migrations.RunSQL(
sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
),
],
state_operations=[
migrations.CreateModel(
name='AuthorBook',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'author',
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Author',
),
),
(
'book',
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to='core.Book',
),
),
],
),
migrations.AlterField(
model_name='book',
name='authors',
field=models.ManyToManyField(
to='core.Author',
through='core.AuthorBook',
),
),
],
),
migrations.AddField(
model_name='authorbook',
name='is_primary',
field=models.BooleanField(default=False),
),
]
Изменение неуправляемой модели на управляемую
Если вы хотите изменить неуправляемую модель (managed=False) на управляемую, необходимо удалить managed=False и сгенерировать миграцию перед внесением других изменений, связанных со схемой модели, поскольку изменения схемы, присутствующие в миграции, содержащей операцию изменения Meta.managed, могут не быть применены.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/howto/writing-migrations/