Написание миграции базы данных ¶
В этом документе показано, как структурировать и записывать миграции базы данных для различных сценариев, с которыми вы можете столкнуться. Вводные материалы по миграции см. В руководстве по теме .
Множественные миграции данных и баз данных ¶
При использовании нескольких баз данных может потребоваться определить, следует ли применять миграцию к определенной базе данных. Например, возможно, что миграцию нужно применить только к одной базе данных.
Для этого можно полагаться на псевдоним базы данных соединения в операции 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
качестве флага , чтобы сделать его как можно более прозрачным к маршрутизатору. Это особенно важно для сторонних приложений и приложений многократного использования.
Миграции с добавлением уникальных полей ¶
Применение «простой» миграции, которая добавляет уникальное ненулевое поле в таблицу, которая уже содержит строки, приведет к ошибке, поскольку значение, используемое для заполнения существующих полей, создается только один раз, что противоречит ограничению уникальности.
Поэтому необходимо выполнить следующие шаги. В этом примере мы добавим ненулевое поле UUIDField
со значением по умолчанию. Измените соответствующее поле в соответствии со своими потребностями.
Добавьте поле в свой шаблон с параметрами
default=uuid.uuid4
иunique=True
(выберите значение по умолчанию, соответствующее типу добавляемого поля).Запустите команду
makemigrations
. Это должно вызвать миграцию, содержащую операциюAddField
.Создайте два пустых файла миграции для одного и того же приложения, запустив дважды . Мы переименовали файлы миграции в приведенных ниже примерах, чтобы дать им более понятные имена.
makemigrations monapp --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
перезаписано исходное значение .
Неатомные миграции ¶
В базах данных, которые обрабатывают транзакции для операторов определения схемы (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). (MySQL поддерживает атомарные инструкции DDL < https://dev.mysql.com/doc/refman/en/atomic-ddl.html > `_ для отдельных инструкций, а не для серии инструкций. внутри транзакции, которую можно было откатить.)
Управление порядком миграций ¶
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
over 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
по своему усмотрению.
Изменение поля ManyToManyField
для использования промежуточного шаблона ¶
Если вы измените поле ManyToManyField
для использования through
промежуточного шаблона , миграция по умолчанию удалит существующую таблицу и создаст новую, которая сотрет существующие связи. Чтобы избежать этого, вы можете использовать SeparateDatabaseAndState
для переименования существующей таблицы с новым именем, сообщая Autodetector, что новая модель была создана. Текущее имя таблицы отображается с помощью команд sqlmigrate
или dbshell
. Новое имя таблицы - это свойство _meta.db_table
промежуточной модели. Ваша новая промежуточная модель должна использовать те же имена внешних ключей, что и 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
не может быть применена.