Spec-Zone.ru › Django 1.9

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

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

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

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

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

Spec-Zone.ru

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