Миграции
Миграции — это способ Django переносить изменения, внесённые в модели (добавление поля, удаление модели и т. д.), в схему базы данных. Они предназначены для выполнения преимущественно автоматически, но вам нужно знать, когда создавать миграции, когда запускать их и с какими распространёнными проблемами вы можете столкнуться.
Команды
Для работы с миграциями и управления схемой базы данных в Django используются несколько команд:
-
migrate, которая отвечает за применение и отмену миграций. -
makemigrations, которая отвечает за создание новых миграций на основе изменений, внесённых в модели. -
sqlmigrate, которая отображает SQL-операторы для миграции. -
showmigrations, которая выводит список миграций проекта и их состояние.
Воспринимайте миграции как систему контроля версий для схемы базы данных. makemigrations отвечает за упаковку изменений моделей в отдельные файлы миграций — аналогично коммитам, — а migrate отвечает за применение этих изменений к базе данных.
Файлы миграций для каждого приложения хранятся в каталоге «migrations» внутри приложения и предназначены для включения в систему контроля версий и распространения вместе с кодовой базой. Создавать их следует один раз на компьютере для разработки, а затем запускать те же миграции на компьютерах коллег, тестовых серверах и, в конечном итоге, на рабочих серверах.
Примечание
Имя пакета, содержащего миграции, можно переопределить отдельно для каждого приложения, изменив настройку MIGRATION_MODULES.
Миграции одинаково выполняются для одного и того же набора данных и дают согласованные результаты. Это означает, что при одинаковых условиях результат в рабочей среде будет точно таким же, как в средах разработки и тестирования.
Django создаёт миграции для любых изменений моделей или полей — даже для параметров, не влияющих на базу данных, — поскольку восстановить поле правильно можно только при наличии в истории всех изменений. Кроме того, эти параметры могут понадобиться позднее для миграций данных (например, если вы задали пользовательские валидаторы).
Поддержка серверных СУБД
Миграции поддерживаются всеми серверными СУБД, входящими в поставку Django, а также сторонними СУБД, если в них реализована поддержка изменения схемы (через класс SchemaEditor).
Однако возможности разных баз данных при выполнении миграций схемы различаются; некоторые ограничения описаны ниже.
PostgreSQL
PostgreSQL обладает наиболее широкими возможностями поддержки схемы среди рассматриваемых баз данных.
MySQL
MySQL не поддерживает транзакции для операций изменения схемы. Это означает, что если миграция не выполнится, вам придётся вручную отменить изменения, чтобы повторить попытку (вернуться к предыдущему состоянию невозможно).
В MySQL 8.0 появились существенные улучшения производительности для операций DDL, благодаря которым они выполняются эффективнее и реже требуют полного перестроения таблиц. Однако гарантировать полное отсутствие блокировок или прерываний невозможно. В случаях, когда блокировки всё же необходимы, длительность этих операций будет пропорциональна количеству затронутых строк.
Наконец, в MySQL сравнительно невелико ограничение на общий размер всех столбцов, охватываемых индексом. Поэтому создание индексов, возможных в других серверных СУБД, в MySQL завершится ошибкой.
SQLite
Встроенные возможности SQLite для изменения схемы весьма ограничены, поэтому Django пытается эмулировать эту функциональность следующим образом:
- Создаёт новую таблицу с новой схемой
- Копирует в неё данные
- Удаляет старую таблицу
- Переименовывает новую таблицу, присваивая ей исходное имя
Обычно этот процесс работает хорошо, но может выполняться медленно и иногда давать сбои. Не рекомендуется запускать SQLite и выполнять миграции в рабочей среде, если вы не знакомы с рисками и ограничениями этого решения. Поддержка SQLite, поставляемая с Django, предназначена для того, чтобы разработчики могли использовать SQLite на локальных компьютерах для разработки не слишком сложных проектов Django без необходимости в полноценной базе данных.
Рабочий процесс
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 предложит несколько вариантов. Если это покажется безопасным, Django предложит автоматически выстроить две миграции в линейную последовательность. В противном случае вам придётся самостоятельно отредактировать миграции. Не беспокойтесь, это несложно и подробнее описано ниже в разделе Файлы миграций.
Транзакции
В базах данных, поддерживающих транзакции DDL (SQLite и PostgreSQL), по умолчанию все операции миграции выполняются в одной транзакции. Если же база данных не поддерживает транзакции DDL (например, MySQL или Oracle), все операции будут выполняться без транзакции.
Чтобы запретить выполнение миграции в транзакции, задайте атрибут atomic равным False. Например:
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
Также можно выполнить часть миграции внутри транзакции с помощью atomic() или передав atomic=True в RunPython. Подробнее см. в разделе Миграции без транзакций.
Зависимости
Хотя миграции создаются для каждого приложения отдельно, таблицы и связи, подразумеваемые моделями, слишком сложны, чтобы создавать их по одному приложению за раз. Если для выполнения миграции требуется что-либо ещё — например, вы добавляете ForeignKey из приложения books в приложение authors, — в созданной миграции будет указана зависимость от миграции приложения authors.
Это означает, что при запуске миграций сначала будет выполнена миграция authors и создана таблица, на которую ссылается ForeignKey, а затем выполнится миграция, создающая столбец ForeignKey и ограничение. Если бы этого не происходило, миграция попыталась бы создать столбец ForeignKey, не имея таблицы, на которую он ссылается, и база данных выдала бы ошибку.
Такое поведение зависимостей влияет на большинство операций миграции, ограниченных одним приложением. Ограничение одним приложением (в makemigrations или migrate) — это обещание, выполняемое по мере возможности, а не гарантия: будут задействованы и другие приложения, если это необходимо для правильного определения зависимостей.
Приложения без миграций не должны иметь связей (ForeignKey, ManyToManyField и т. д.) с приложениями, в которых есть миграции. Иногда это может работать, но такая конфигурация не поддерживается.
Заменяемые зависимости
-
django.db.migrations.swappable_dependency(value)
Функция swappable_dependency() используется в миграциях для объявления «заменяемых» зависимостей от миграций приложения, в котором находится подставленная модель. В настоящее время зависимость указывается от первой миграции этого приложения. Поэтому подставленная модель должна создаваться в начальной миграции. Аргумент value — это строка "<app label>.<model>", содержащая метку приложения и имя модели, например "myapp.MyModel".
Используя swappable_dependency(), вы сообщаете инфраструктуре миграций, что миграция зависит от другой миграции, создающей заменяемую модель. Это позволяет в будущем заменить модель другой реализацией. Обычно такой подход используется для ссылок на модели, допускающие настройку или замену, например на пользовательскую модель пользователя (settings.AUTH_USER_MODEL, значение по умолчанию — "auth.User") в системе аутентификации Django.
Файлы миграций
Миграции хранятся в виде файлов на диске, которые здесь называются «файлами миграций». На самом деле это обычные файлы 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. Затем Django проверяет у этого объекта четыре атрибута, из которых обычно используются только два:
-
dependencies— список миграций, от которых зависит эта миграция. -
operations— список классовOperation, описывающих действия этой миграции.
Ключевую роль играют операции: это набор декларативных инструкций, указывающих Django, какие изменения схемы нужно выполнить. Django анализирует их и создаёт внутреннее представление всех изменений схемы во всех приложениях, а затем использует его для генерации SQL, вносящего изменения в схему.
Эта внутренняя структура также используется для определения различий между вашими моделями и текущим состоянием миграций. Django последовательно применяет все изменения к набору моделей в памяти, чтобы восстановить состояние моделей на момент последнего запуска makemigrations. Затем Django сравнивает эти модели с моделями в файлах 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, указав номер предыдущей миграции. Например, чтобы отменить миграцию books.0003:
$ 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
...\> py 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
...\> py 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
Миграция необратима, если содержит хотя бы одну необратимую операцию. Попытка отменить такую миграцию вызовет исключение IrreversibleError:
$ 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...Traceback (most recent call last): django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
...\> py 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...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
Исторические модели
При выполнении миграций 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 = f"{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 миграции должен включать последнюю миграцию каждого затронутого приложения. В противном случае при попытке получить модель в функции RunPython с помощью apps.get_model() может возникнуть ошибка, например: LookupError: No installed app with
label 'myappname'.
В следующем примере миграция приложения 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 и выстраивая их в последовательность, а затем запуская оптимизатор, который пытается сократить этот список. Например, Django знает, что CreateModel и DeleteModel взаимно отменяют друг друга, а также знает, что AddField можно включить в CreateModel.
Когда последовательность операций будет сокращена до предела — степень возможного сокращения зависит от того, насколько тесно связаны ваши модели и используете ли вы операции RunSQL или RunPython (которые нельзя оптимизировать, если они не помечены как elidable), — Django запишет результат в новый набор файлов миграций.
В этих файлах указывается, что они заменяют ранее объединённые миграции, поэтому старые и новые файлы миграций могут сосуществовать. Django будет автоматически переключаться между ними в зависимости от того, на каком этапе истории вы находитесь. Если вы ещё не завершили выполнение набора миграций, который объединили, Django продолжит использовать его до конца, а затем перейдёт к объединённой истории. При новых установках будет использоваться новая объединённая миграция, а все старые будут пропущены.
Это позволяет объединять миграции, не нарушая работу систем, которые уже используются в production и ещё не полностью обновлены. Рекомендуемый процесс: объединить миграции, сохранив старые файлы, зафиксировать изменения и выпустить релиз, дождаться обновления всех систем до нового релиза (или, если вы работаете над сторонним проектом, убедиться, что пользователи обновляются по порядку и не пропускают релизы), а затем удалить старые файлы, зафиксировать изменения и выпустить второй релиз.
Для всего этого используется команда 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? [y/N] y Optimizing... Optimized from 12 operations to 7 operations. Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.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 определяет, что миграция объединённая).
Примечание
Можно объединять и сами объединённые миграции, не преобразуя их в обычные. Это может быть полезно, если не во всех средах ещё выполнен исходный набор объединённых миграций. Однако в целом лучше преобразовать объединённые миграции в обычные, чтобы можно было удалить старые файлы миграций.
Удаление ссылок на удалённые миграции
Если вы, вероятно, захотите повторно использовать имя удалённой миграции в будущем, удалите ссылки на неё из таблицы миграций Django с помощью параметра migrate --prune.
Добавлена поддержка объединения объединённых миграций.
Сериализация значений
Миграции — это файлы 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(в том числе с учётом часового пояса) -
экземпляры
zoneinfo.ZoneInfo -
экземпляры
decimal.Decimal -
экземпляры
enum.Enumиenum.Flag -
экземпляры
uuid.UUID -
экземпляры
functools.partial()иfunctools.partialmethod, у которых значенияfunc,argsиkeywordsможно сериализовать - Объекты путей pure и concrete из
pathlib. Пути concrete преобразуются в соответствующие пути pure, напримерpathlib.PosixPathвpathlib.PurePosixPath -
экземпляры
os.PathLike, напримерos.DirEntry, которые преобразуются вstrилиbytesс помощьюos.fspath() -
экземпляры
LazyObject, оборачивающие сериализуемое значение - Экземпляры типов перечислений (например,
TextChoicesилиIntegerChoices) - Любые поля Django
-
Любые ссылки на функции или методы (например,
datetime.datetime.today) (должны находиться в глобальной области видимости модуля)- Функции можно декорировать, если они правильно обёрнуты, то есть с помощью
functools.wraps() - Декораторы
functools.cache()иfunctools.lru_cache()поддерживаются явно
- Функции можно декорировать, если они правильно обёрнуты, то есть с помощью
- Несвязанные методы, используемые внутри тела класса
- Любые ссылки на классы (должны находиться в глобальной области видимости модуля)
- Объекты с собственным методом
deconstruct()(см. ниже)
Добавлена поддержка сериализации экземпляров zoneinfo.ZoneInfo.
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/6.0/topics/migrations/