Написание миграции базы данных

В этом документе показано, как структурировать и записывать миграции базы данных для различных сценариев, с которыми вы можете столкнуться. Вводные материалы по миграции см. В руководстве по теме .

Множественные миграции данных и баз данных

При использовании нескольких баз данных может потребоваться определить, следует ли применять миграцию к определенной базе данных. Например, возможно, что миграцию нужно применить только к одной базе данных.

Для этого можно полагаться на псевдоним базы данных соединения в операции 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.py
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 . Например :

    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.py
    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 . Например :

    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 перезаписано исходное значение .

Неатомные миграции

В базах данных, которые обрабатывают транзакции для операторов определения схемы (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() который извлекает модели старого приложения. Такой подход позволяет развернуть проект в любом месте без необходимости сначала устанавливать, а затем удалять старое приложение.

Вот пример миграции:

myapp / migrations / 0124_move_old_app_to_new_app.py
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 не может быть применена.

Copyright ©2020 All rights reserved