Spec-Zone.ru › Django 1.10

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

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

Миграции данных и несколько баз данных

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

Для этого можно проверить псевдоним подключения к базе данных внутри 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 в качестве подсказки, чтобы сделать её работу с маршрутизатором более прозрачной. Это особенно важно для повторно используемых и сторонних приложений.

Миграции, добавляющие уникальные поля

Применение «простой» миграции, добавляющей уникальное не-NULL поле в таблицу с существующими строками, вызовет ошибку, потому что значение, используемое для заполнения существующих строк, генерируется только один раз, нарушая уникальное ограничение.

Поэтому необходимо выполнить следующие шаги. В этом примере мы добавим не-NULL 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 1.10.

В базах данных, поддерживающих транзакции 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/1.10/howto/writing-migrations/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API