Миграции
Миграции — это способ 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. Это означает, что при выполнении миграций миграция 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)),
]
То, что Django ищет при загрузке файла миграции (как модуля Python), — это подкласс 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
Если вы хотите откатить все применённые миграции для приложения, используйте имя zero:
$ 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, сериализуются в миграциях, поэтому функции и классы должны храниться до тех пор, пока миграция их ссылается. Любые пользовательские поля модели также должны быть сохранены, так как они импортируются непосредственно миграциями.
Кроме того, конкретные базовые классы модели хранятся как указатели, поэтому вы должны всегда сохранять базовые классы до тех пор, пока миграция содержит ссылку на них. С положительной стороны, методы и менеджеры из этих базовых классов наследуются нормально, поэтому если вам абсолютно необходим доступ к ним, вы можете переместить их в суперкласс.
Чтобы удалить старые ссылки, вы можете объединить миграции или, если ссылок не много, скопировать их в файлы миграций.
Учитывание при удалении полей модели
Аналогично соображениям по «ссылкам на исторические функции», описанным в предыдущем разделе, удаление пользовательских полей модели из вашего проекта или приложения сторонних разработчиков вызовет проблему, если они ссылаются на старые миграции.
Чтобы помочь в этой ситуации, Django предоставляет некоторые атрибуты полей модели, которые помогают с устареванием полей модели с использованием системы проверок.
Добавьте атрибут 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 ожидает в качестве аргумента вызываемый объект, который принимает два аргумента — первый — это реестр приложений, содержащий исторические версии всех ваших моделей, соответствующие тому месту в истории миграции, где она находится, а второй — 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: No installed app
with label '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),
]
Более продвинутые миграции
Если вы заинтересованы в более продвинутых операциях миграции или хотите написать свои собственные, см. справочник по операциям миграции и «как» в написании миграций.
Слияние миграций
Рекомендуется свободно создавать миграции и не беспокоиться о том, сколько их; код миграции оптимизирован для обработки сотен миграций одновременно без значительного замедления. Однако со временем вы захотите перейти от нескольких сотен миграций к нескольким, и именно здесь и появляется слияние.
Слияние — это процесс уменьшения существующего набора многих миграций до одной (или нескольких) миграций, которые по-прежнему представляют те же изменения.
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, выделите один из ForeignKey в циклическом цикле зависимости в отдельную миграцию и перенесите зависимость от другого приложения вместе с ней. Если вы не уверены, посмотрите, как 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()(см. ниже)
Добавлена поддержка сериализации для functools.partialmethod.
Добавлена поддержка сериализации для NoneType.
Django не может сериализовать:
- Вложенные классы
- Произвольные экземпляры классов (например,
MyClass(4.3, 5.7)) - Лямбда-функции
Пользовательские сериализаторы
Вы можете сериализовать другие типы, написав пользовательский сериализатор. Например, если 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 операций схемы, специальные операции и написание собственных операций.
- Руководство по написанию миграций
- Объясняет, как структурировать и писать миграции базы данных для разных сценариев, которые могут встретиться.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.2/topics/migrations/