Миграции

Миграции - это способ Django распространять изменения, которые вы вносите в свои модели (добавление поля, удаление модели и т.д.), в схему вашей базы данных. Они разработаны, чтобы быть в основном автоматическими, но вам нужно знать, когда выполнять миграции, когда их запускать и с какими общими проблемами вы можете столкнуться.

Команды

Есть несколько команд, которые вы будете использовать для взаимодействия с миграциями и обработкой Django схемы базы данных:

  • migrate, которая отвечает за применение и отмену миграции.
  • makemigrations, которая отвечает за создание новых миграций на основе изменений, которые вы внесли в свои модели.
  • sqlmigrate, которая отображает операторы SQL для миграции.
  • showmigrations, в которой перечислены миграции проекта и их статус.

Вы должны думать о миграции как о системе контроля версий для вашей схемы базы данных. makemigrations отвечает за упаковку изменений вашей модели в отдельные файлы миграции - аналогично коммитам - а migrate отвечает за их применение в вашей базе данных.

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

Примечание

Можно переопределить имя пакета, который содержит миграции для каждого приложения, изменив параметр MIGRATION_MODULES.

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

Django выполнит миграции для любых изменений ваших моделей или полей - даже для параметров, которые не влияют на базу данных, - поскольку единственный способ правильно восстановить поле - это сохранить все изменения в истории, и вам могут понадобиться эти параметры в некоторых миграция данных позже (например, если вы установили собственные валидаторы).

Поддержка бэкэндов

Миграции поддерживаются на всех бэкэндах, с которыми поставляется Django, а также в любых сторонних бэкэндах, если они запрограммированы на поддержку изменения схемы (выполняется с помощью класса SchemaEditor).

Однако некоторые базы данных более эффективны, чем другие, когда дело доходит до миграции схемы; некоторые из предостережений описаны ниже.

PostgreSQL

PostgreSQL - самая способная из всех представленных здесь баз данных с точки зрения поддержки схем.

Единственное предостережение в том, что до PostgreSQL 11 добавление столбцов со значениями по умолчанию приводило к полной перезаписи таблицы на время, пропорциональное ее размеру. По этой причине рекомендуется всегда создавать новые столбцы с null=True, так как в этом случае они будут добавлены немедленно.

MySQL

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

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

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

SQLite

В SQLite небольшие возможности встроенной поддержки изменения схемы, поэтому Django пытается имитировать ее:

  • Создание новой таблицы с новой схемой
  • Копирование данных
  • Удаление старой таблицы
  • Переименование новой таблицы в соответствии с исходным именем

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

Рабочий процесс

Работать с миграциями просто. Внесите изменения в свои модели - скажем, добавьте поле и удалите модель - а затем запустите makemigrations:

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0003_auto.py:
    - Alter field author on book

Ваши модели будут просканированы и сравнены с версиями, которые в настоящее время содержатся в ваших файлах миграции, а затем будет записан новый набор миграций. Обязательно прочтите вывод, чтобы узнать, что, по мнению makemigrations, вы изменили - это не идеально, а для сложных изменений может сделать не то, что вы ожидаете.

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

$ python manage.py migrate
Operations to perform:
  Apply all migrations: books
Running migrations:
  Rendering model states... DONE
  Applying books.0003_auto... OK

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

Если вы хотите дать миграции(-ям) осмысленное имя вместо сгенерированного, вы можете использовать параметр makemigrations --name:

$ python manage.py makemigrations --name changed_my_model your_app_label

Контроль версий

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

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

Когда это произойдет, Django предложит вам несколько вариантов. Если он сочтет это достаточно безопасным, он предложит автоматически линеаризовать две миграции за вас. Если нет, вам придется войти и изменить миграции самостоятельно - не волнуйтесь, это несложно и более подробно описано в Файлы миграции ниже.

Зависимости

Хотя миграции выполняются для каждого приложения, таблицы и отношения, подразумеваемые вашими моделями, слишком сложны, чтобы их можно было создавать только для одного приложения за раз. Когда вы выполняете миграцию, для выполнения которой требуется что-то еще - например, вы добавляете ForeignKey в свое приложение books в приложение authors - полученная миграция будет содержать зависимость от миграции в разделе authors.

Это означает, что когда вы запускаете миграции, сначала выполняется миграция авторов и создается таблица, на которую ссылается ForeignKey, а затем выполняется миграция, которая создает столбец ForeignKey и создает ограничение. Если этого не произошло, миграция попыталась бы создать столбец ForeignKey без таблицы, на которую он ссылается, существующей, и ваша база данных выдала бы ошибку.

Такое поведение зависимости влияет на большинство операций миграции, когда вы ограничиваетесь одним приложением. Ограничение одним приложением (либо в makemigrations, либо в migrate) - это обещание, требующее максимальных усилий, а не гарантия; любые другие приложения, которые необходимо использовать для получения правильных зависимостей будут использованы.

Приложения без миграции не должны иметь отношений (ForeignKey, ManyToManyField и т.д.) с приложениями с миграциями. Иногда может работать, но не поддерживается.

Файлы миграции

Миграции сохраняются в формате на диске, называемом здесь «файлами миграции». Эти файлы на самом деле представляют собой обычные файлы Python с согласованным макетом объектов, написанные в декларативном стиле.

Базовый файл миграции выглядит так:

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [('migrations', '0001_initial')]

    operations = [
        migrations.DeleteModel('Tribble'),
        migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
    ]

При загрузке файла миграции (в виде модуля Python) Django ищет подкласс django.db.migrations.Migration, называемый Migration. Затем он проверяет этот объект на наличие четырех атрибутов, только два из которых используются большую часть времени:

  • dependencies, список миграций, от которых зависит эта миграция.
  • operations, список классов Operation, которые определяют, что делает эта миграция.

Операции - это ключ; они представляют собой набор декларативных инструкций, которые сообщают Django, какие изменения схемы необходимо внести. Django сканирует их и создает в памяти представление всех изменений схемы для всех приложений и использует это для генерации SQL, который вносит изменения в схему.

Эта структура в памяти также используется для определения различий между вашими моделями и текущим состоянием ваших миграций; Django выполняет все изменения в наборе моделей в памяти, чтобы определить состояние ваших моделей в последний раз, когда вы запускали makemigrations. Затем он использует эти модели для сравнения с моделями в ваших файлах models.py, чтобы определить, что вы изменили.

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

Пользовательские поля

Вы не можете изменить количество позиционных аргументов в уже перенесенном настраиваемом поле без возникновения ошибки TypeError. Старая миграция вызовет измененный метод __init__ со старой сигнатурой. Поэтому, если вам нужен новый аргумент, создайте ключевой аргумент и добавьте в конструктор что-то вроде assert 'argument_name' in kwargs.

Менеджеры модели

При желании вы можете сериализовать менеджеры в миграции и сделать их доступными в операциях RunPython. Это делается путем определения атрибута use_in_migrations в классе менеджера:

class MyManager(models.Manager):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Если вы используете функцию from_queryset() для динамического создания класса менеджера, вам необходимо наследовать от сгенерированного класса, чтобы сделать его импортируемым:

class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

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

Начальные миграции

Migration.initial

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

Первоначальные миграции помечаются атрибутом класса initial=True в классе миграции. Если initial атрибут класса не найден, миграция будет считаться «начальной», если это первая миграция в приложении (т.е. если она не зависит от какой-либо другой миграции в том же приложении).

Когда используется опция migrate --fake-initial, эти начальные миграции обрабатываются особым образом. Для первоначальной миграции, которая создает одну или несколько таблиц (операция CreateModel), Django проверяет, что все эти таблицы уже существуют в базе данных, и выполняет фиктивную миграцию, если это так. Точно так же для начальной миграции, которая добавляет одно или несколько полей (операция AddField), Django проверяет, что все соответствующие столбцы уже существуют в базе данных, и применяет фиктивную миграцию, если это так. Без –fake-initial начальные миграции обрабатываются так же, как и любые другие миграции.

Согласованность истории

Как обсуждалось ранее, вам может потребоваться вручную линеаризовать миграции при объединении двух ветвей разработки. При редактировании зависимостей миграции вы можете непреднамеренно создать несогласованное состояние истории, когда миграция была применена, но некоторые из ее зависимостей нет. Это явный признак того, что зависимости неверны, поэтому Django откажется выполнять миграции или делать новые миграции, пока они не будут исправлены. При использовании нескольких баз данных вы можете использовать метод allow_migrate() из маршрутизация баз данных для контроля того, для каких баз данных makemigrations проверяет согласованную историю.

Добавление миграций в приложения

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

Если в вашем приложении уже есть модели и таблицы базы данных и еще нет миграций (например, вы создали его в предыдущей версии Django), вам необходимо преобразовать его для использования миграции; это простой процесс:

$ python manage.py makemigrations your_app_label

Это сделает новую первоначальную миграцию для вашего приложения. Теперь запустите python manage.py migrate --fake-initial, и Django обнаружит, что у вас есть начальная миграция и, что таблицы, которые он хочет создать, уже существуют, и отметит миграцию как уже примененную. (Без флага migrate --fake-initial команда выдаст ошибку, потому что таблицы, которые она хочет создать, уже существуют.)

Обратите внимание, что это работает только с учетом двух вещей:

  • Вы не меняли своих моделей с тех пор, как сделали для них таблицы. Чтобы миграция работала, вы должны сначала сделать первоначальную миграцию, а затем внести изменения, поскольку Django сравнивает изменения с файлами миграции, а не с базой данных.
  • Вы не должны редактировать свою базу данных вручную - Django не сможет обнаружить, что ваша база данных не соответствует вашим моделям, вы просто получите ошибки, когда миграции попытаются изменить эти таблицы.

Отмена миграций

Любую миграцию можно отменить с помощью migrate, используя номер предыдущей миграций:

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK

Если вы хотите отменить все миграции, примененные к приложению, используйте имя zer:

$ python manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK

Исторические модели

Когда вы запускаете миграции, Django работает с историческими версиями ваших моделей, хранящимися в файлах миграции. Если вы пишете код Python с помощью операции RunPython или если у вас есть методы allow_migrate на маршрутизаторах базы данных, вам необходимо использовать эту историческую модель версии, а не импортировать их напрямую.

Предупреждение

Если вы импортируете модели напрямую, а не используете исторические модели, ваши миграции могут работать изначально, но не будут работать в будущем, когда вы попытаетесь повторно запустить старые миграции (обычно, когда вы настраиваете новую установку и выполняете все миграции для настройки базы данных).

Это означает, что проблемы исторической модели могут быть не сразу очевидны. Если вы столкнетесь с такой ошибкой, можно отредактировать миграцию, чтобы использовать исторические модели, а не прямой импорт, и зафиксировать эти изменения.

Поскольку сериализовать произвольный код Python невозможно, в этих исторических моделях не будет никаких пользовательских методов, которые вы определили. Однако у них будут одинаковые поля, отношения, менеджеры (только те, у которых есть use_in_migrations = True) и опции Meta (также версионные, поэтому они могут отличаться от ваших текущих).

Предупреждение

Это означает, что у вас НЕ будет настраиваемых методов save(), вызываемых для объектов, когда вы обращаетесь к ним в миграциях, и у вас НЕ будет никаких настраиваемых конструкторов или методов экземпляра. Планируйте правильно!

Ссылки на функции в параметрах полей, таких как upload_to и limit_choices_to, и объявления менеджера модели с менеджерами, имеющими use_in_migrations = True, сериализуются в миграциях, поэтому функции и классы должны быть сохранены пока есть ссылающаяся на них миграция. Любые пользовательские поля модели также необходимо сохранить, так как они импортируются напрямую путем миграции.

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

Чтобы удалить старые ссылки, вы можете использовать squash migrations или, если ссылок немного, скопировать их в файлы миграции.

Рекомендации при удалении полей модели

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

Чтобы помочь в этой ситуации, Django предоставляет некоторые атрибуты полей модели, чтобы помочь с устареванием полей модели с помощью system check framework.

Добавьте атрибут system_check_deprecated_details в поле вашей модели, как показано ниже:

class IPAddressField(Field):
    system_check_deprecated_details = {
        'msg': (
            'IPAddressField has been deprecated. Support for it (except '
            'in historical migrations) will be removed in Django 1.9.'
        ),
        'hint': 'Use GenericIPAddressField instead.',  # optional
        'id': 'fields.W900',  # pick a unique ID for your field.
    }

После периода устаревания по вашему выбору (два или три выпуска функций для полей в самом Django) измените атрибут system_check_deprecated_details на system_check_removed_details и обновите словарь, аналогичный:

class IPAddressField(Field):
    system_check_removed_details = {
        'msg': (
            'IPAddressField has been removed except for support in '
            'historical migrations.'
        ),
        'hint': 'Use GenericIPAddressField instead.',
        'id': 'fields.E900',  # pick a unique ID for your field.
    }

Вы должны сохранить методы поля, которые необходимы для его работы при миграции базы данных, такие как __init __(), deconstruct() и get_internal_type(). Сохраняйте это поле-заглушку до тех пор, пока существуют любые миграции, которые ссылаются на это поле. Например, после сжатия миграций и удаления старых вы сможете полностью удалить поле.

Миграция данных

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

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

Django не может автоматически генерировать миграции данных для вас, как это происходит с миграциями схемы, но написать их не так уж сложно. Файлы миграции в Django состоят из Операций, а основная операция, которую вы используете для миграции данных, это RunPython.

Для начала создайте пустой файл миграции, с которым вы можете работать (Django поместит файл в нужное место, предложит имя и добавит зависимости за вас):

python manage.py makemigrations --empty yourappname

Затем откройте файл; это должно выглядеть примерно так:

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
    ]

Теперь все, что вам нужно сделать, это создать новую функцию и использовать ее RunPython. RunPython ожидает вызываемый объект в качестве аргумента, который принимает два аргумента - первый app registry, который имеет исторические версии всех ваших моделей, загруженные в него, соответствуют тому месту в вашей истории, где происходит миграция, а второй - это SchemaEditor, который вы можете использовать для ручного изменения схемы базы данных (но будьте осторожны, это может сбить с толку автодетектор миграции!)

Давайте напишем простую миграцию, которая заполняет наше новое поле name комбинированными значениями first_name и last_name (мы пришли в себя и поняли, что не у всех есть имя и фамилия). Все, что нам нужно сделать, это использовать историческую модель и перебирать строки:

from django.db import migrations

def combine_names(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Person = apps.get_model('yourappname', 'Person')
    for person in Person.objects.all():
        person.name = '%s %s' % (person.first_name, person.last_name)
        person.save()

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(combine_names),
    ]

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

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

Доступ к моделям из других приложений

При написании функции RunPython, которая использует модели из приложений, отличных от того, в котором расположена миграция, атрибут dependencies миграции должен включать последнюю миграцию каждого участвующего приложения, иначе вы можете получить ошибку, похожую на LookupError: не установлено приложение с меткой 'myappname' при попытке получить модель в функции RunPython с помощью apps.get_model().

В следующем примере у нас есть миграция в app1, которая должна использовать модели в app2. Нас не интересуют детали move_m1, за исключением того факта, что ему потребуется доступ к моделям из обоих приложений. Поэтому мы добавили зависимость, которая указывает последнюю миграцию app2:

class Migration(migrations.Migration):

    dependencies = [
        ('app1', '0001_initial'),
        # added dependency to enable using models from app2 in move_m1
        ('app2', '0004_foobar'),
    ]

    operations = [
        migrations.RunPython(move_m1),
    ]

Более продвинутые миграции

Если вас интересуют более сложные операции миграции или вы хотите написать свои собственные, смотрите Справочник по операциям миграции и «how-to» в написание миграций.

Сжатие миграций

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

Сжатие - это процесс сокращения существующего набора из многих миграций до одной (или иногда нескольких) миграций, которые по-прежнему представляют те же изменения.

Django делает это, беря все ваши существующие миграции, извлекая их Operation и помещая их все по порядку, а затем запускает над ними оптимизатор, чтобы попытаться уменьшить длину списка - например, он знает, что CreateModel и DeleteModel отменяют друг друга, и он знает, что AddField можно свернуть в CreateModel.

После того, как последовательность операций была максимально сокращена - возможная сумма зависит от того, насколько тесно связаны ваши модели и есть ли у вас RunSQL или RunPython операции (которые не могут быть оптимизированы, если они не отмечены как elidable) - Django затем запишет их обратно в новый набор файлов миграции.

Эти файлы помечены, чтобы сказать, что они заменяют ранее сжатые миграции, поэтому они могут сосуществовать со старыми файлами миграции, и Django будет разумно переключаться между ними в зависимости от того, где вы находитесь в истории. Если вы все еще на полпути через набор миграций, которые вы сжали, он будет продолжать использовать их, пока не достигнет конца, а затем переключится на сжатую историю, в то время как новые установки будут просто использовать новую сжатую миграцию и пропускать все старые.

Это позволяет вам сжать, а не испортить системы, которые в настоящее время находятся в производстве, которые еще не полностью обновлены. Рекомендуемый процесс - сжать, сохранить старые файлы, зафиксировать и выпустить, подождать, пока все системы не будут обновлены с новым выпуском (или, если вы сторонний проект, просто убедитесь, что ваши пользователи обновляют выпуски по порядку, не пропуская ни одного), а затем удалите старые файлы, зафиксируйте и сделайте второй выпуск.

Команда, которая поддерживает все это - squashmigrations - просто передайте ей метку приложения и имя миграции, которое вы хотите сжать, и она начнет работать:

$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [yN] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_somthing.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.

Используйте параметр squashmigrations --squashed-name, если вы хотите установить имя сжатой миграции, а не использовать автоматически сгенерированное.

Обратите внимание, что взаимозависимости моделей в Django могут быть очень сложными, и сжатие может привести к невыполнению миграции; либо неправильно оптимизировать (в этом случае вы можете повторить попытку с помощью --no-optimize, хотя вы также должны сообщить о проблеме), либо с помощью CircularDependencyError, и в этом случае вы можете вручную решить эту проблему.

Чтобы вручную разрешить ошибку CircularDependencyError, выделите один из ForeignKeys в цикле циклических зависимостей в отдельную миграцию и переместите с ним зависимость в другое приложение. Если вы не уверены, посмотрите, как makemigrations решает проблему, когда вас просят создать новые миграции из ваших моделей. В следующем выпуске Django squashmigrations будет обновлен, чтобы попытаться устранить эти ошибки самостоятельно.

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

Затем вы должны перевести сжатую миграцию на нормальную миграцию:

  • Удаление всех заменяемых файлов миграции.
  • Обновление всех миграций, которые зависят от удаленных миграций, чтобы вместо этого зависеть от сжатой миграции.
  • Удаление атрибута replaces в классе Migration сжатой миграции (так Django сообщает, что это сжатая миграция).

Примечание

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

Сериализация значений

Миграции - это просто файлы Python, содержащие старые определения ваших моделей, поэтому для их записи Django должен взять текущее состояние ваших моделей и сериализовать их в файл.

Хотя Django может сериализовать большинство вещей, есть некоторые вещи, которые мы просто не можем сериализовать в действительное представление Python - нет стандарта Python для того, как значение может быть преобразовано обратно в код (repr() работает только для базовых значений и не указывает пути импорта).

Django может сериализовать следующее:

  • int, float, bool, str, bytes, None, NoneType
  • list, set, tuple, dict, range.
  • datetime.date, datetime.time, и datetime.datetime экземпляры (включая те, которые знают часовой пояс)
  • экземпляры decimal.Decimal
  • экземпляры enum.Enum
  • экземпляры uuid.UUID
  • Экземпляры functools.partial() и functools.partialmethod, которые имеют сериализуемые значения func, args и keywords.
  • Экземпляры LazyObject, которые обертывают сериализуемое значение.
  • Любое поле Django
  • Ссылка на любую функцию или метод (например, datetime.datetime.today) (должна находиться в области верхнего уровня модуля)
  • Несвязанные методы, используемые из тела класса
  • Ссылка на любой класс (должна быть в области верхнего уровня модуля)
  • Все, что имеет собственный метод deconstruct() (смотрите ниже)
Changed in Django 2.1:

Добавлена поддержка сериализации для functools.partialmethod.

Changed in Django 2.2:

Добавлена поддержка сериализации для NoneType.

Django не может сериализовать:

  • Вложенные классы
  • Экземпляры произвольных классов (например, MyClass(4.3, 5.7))
  • Лямбды

Пользовательские сериализаторы

New in Django 2.2.

Вы можете сериализовать другие типы, написав собственный сериализатор. Например, если бы Django не сериализовал Decimal по умолчанию, вы могли бы сделать это:

from decimal import Decimal

from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter

class DecimalSerializer(BaseSerializer):
    def serialize(self):
        return repr(self.value), {'from decimal import Decimal'}

MigrationWriter.register_serializer(Decimal, DecimalSerializer)

Первый аргумент MigrationWriter.register_serializer() - это тип или итерация типов, которые должны использовать сериализатор.

Метод serialize() вашего сериализатора должен возвращать строку о том, как значение должно отображаться в миграциях, и набор любых операций импорта, которые необходимы в миграции.

Добавление метода deconstruct()

Вы можете позволить Django сериализовать ваши собственные экземпляры пользовательского класса, предоставив классу метод deconstruct(). Он не принимает аргументов и должен возвращать кортеж из трех вещей (path, args, kwargs):

  • path должен быть путем Python к классу с именем класса, включенным в последнюю часть (например, myapp.custom_things.MyClass). Если ваш класс недоступен на верхнем уровне модуля, он не может быть сериализован.
  • args должен быть списком позиционных аргументов для передачи методу __init__ вашего класса. Все в этом списке должно само быть сериализуемым.
  • kwargs должен быть ключевым аргументом, передаваемым в метод вашего класса __init__. Каждое значение должно быть сериализуемым.

Примечание

Это возвращаемое значение отличается от метода deconstruct() для пользовательских полей, который возвращает кортеж из четырех элементов.

Django запишет значение как экземпляр вашего класса с заданными аргументами, аналогично тому, как он записывает ссылки на поля Django.

Чтобы предотвратить создание новой миграции каждый makemigrations, вам также следует добавить метод __eq__() в декорированный класс. Эта функция будет вызываться платформой миграции Django для обнаружения изменений между состояниями.

Поскольку все аргументы конструктора вашего класса сами по себе сериализуемы, вы можете использовать декоратор класса @deconstructible из django.utils.deconstruct, чтобы добавить метод deconstruct():

from django.utils.deconstruct import deconstructible

@deconstructible
class MyCustomClass:

    def __init__(self, foo=1):
        self.foo = foo
        ...

    def __eq__(self, other):
        return self.foo == other.foo

Декоратор добавляет логику для захвата и сохранения аргументов на пути к вашему конструктору, а затем возвращает эти аргументы именно тогда, когда вызывается deconstruct().

Поддержка нескольких версий Django

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

Система миграции будет поддерживать обратную совместимость в соответствии с той же политикой, что и остальная часть Django, поэтому файлы миграции, созданные в Django X.Y, должны работать без изменений в Django X.Y+1. Однако система миграции не обещает прямой совместимости. Могут быть добавлены новые функции, а файлы миграции, созданные с помощью более новых версий Django, могут не работать в более старых версиях.

См.также

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