Миграции
Миграции — это способ Django распространять изменения, которые вы вносите в модели (добавление поля, удаление модели и т. д.) в схему вашей базы данных. Они разработаны для большей автоматизации, но вам нужно знать, когда создавать миграции, когда их запускать и какие распространённые проблемы могут возникнуть.
Команды
Существует несколько команд, которые вы будете использовать для взаимодействия с миграциями и обработкой схемы базы данных Django:
-
migrate, которая отвечает за применение и отмену миграций. -
makemigrations, которая отвечает за создание новых миграций на основе изменений, внесённых в ваши модели. -
sqlmigrate, которая отображает SQL-запросы для миграции. -
showmigrations, которая отображает миграции проекта и их статус.
Представьте миграции как систему управления версиями для схемы вашей базы данных. makemigrations отвечает за упаковку изменений модели в отдельные файлы миграций — аналогично коммитам — а migrate отвечает за применение этих изменений в вашей базе данных.
Файлы миграций для каждого приложения хранятся в каталоге «migrations» внутри этого приложения и предназначены для коммита и распространения как часть кодовой базы. Вы должны создавать их на своей машине разработчика, а затем запускать те же миграции на машинах коллег, на машинах для тестирования и, в конечном итоге, на ваших производственных машинах.
Примечание
Возможно переопределение имени пакета, содержащего миграции, для каждого приложения, изменив настройку MIGRATION_MODULES.
Миграции будут работать одинаково на одних и тех же данных и давать согласованные результаты, что означает, что то, что вы видите на этапе разработки и тестирования, при одинаковых условиях, произойдёт и на этапе производства.
Django будет создавать миграции для любого изменения в ваших моделях или полях, — даже для опций, которые не влияют на базу данных, — так как единственный способ правильно восстановить поле — это иметь все изменения в истории, и вам могут понадобиться эти опции в некоторых миграциях данных позже (например, если вы установили пользовательские валидаторы).
Поддержка бэкендов
Миграции поддерживаются всеми бэкендами, поставляемыми с Django, а также любыми сторонними бэкендами, если они запрограммировали поддержку изменения схемы (с помощью класса SchemaEditor).
Однако некоторые базы данных более способны, чем другие, когда дело доходит до миграций схемы; некоторые замечания приведены ниже.
PostgreSQL
PostgreSQL — самая способная из всех баз данных с точки зрения поддержки схемы.
MySQL
MySQL не поддерживает транзакции в операциях изменения схемы, что означает, что если миграция не будет применена, вам придется вручную отменить изменения, чтобы попробовать снова (невозможно откатиться к более ранней точке).
Кроме того, MySQL полностью переписывает таблицы почти для каждой операции со схемой и в целом тратит время пропорционально количеству строк в таблице для добавления или удаления столбцов. На медленном оборудовании это может быть хуже, чем минута на миллион строк — добавление нескольких столбцов в таблицу всего с несколькими миллионами строк может заблокировать ваш сайт более чем на десять минут.
Наконец, MySQL имеет относительно небольшие ограничения на длину имён столбцов, таблиц и индексов, а также ограничение на общий размер всех столбцов, которые охватывает индекс. Это означает, что индексы, которые возможны в других бэкендах, не будут созданы в MySQL.
SQLite
SQLite имеет очень ограниченную встроенную поддержку изменения схемы, поэтому Django пытается смоделировать её следующим образом:
- Создание новой таблицы с новой схемой
- Копирование данных
- Удаление старой таблицы
- Переименование новой таблицы для соответствия исходному имени
Этот процесс в целом работает хорошо, но может быть медленным и иногда ошибочным. Не рекомендуется запускать и мигрировать 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 предложит вам варианты. Если он посчитает это достаточно безопасным, он предложит автоматически упорядочить две миграции за вас. Если нет, вам нужно будет вручную изменить миграции — не волнуйтесь, это не сложно, и это подробно объяснено в Файлы миграций ниже.
Транзакции
В базах данных, которые поддерживают транзакции 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, и т. д.) к приложениям с миграциями. Иногда это может работать, но это не поддерживается.
Файлы миграций
Миграции хранятся в виде дискового формата, который здесь называется «файлами миграций». На самом деле эти файлы представляют собой обычные файлы 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, передав номер предыдущей миграции. Например, для отмены миграции 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 миграции должен включать последнюю миграцию каждого вовлечённого приложения, иначе вы можете получить ошибку, подобную: 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_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экземпляры (включая те, которые учитывают часовой пояс) -
decimal.Decimalэкземпляры -
enum.Enumиenum.Flagэкземпляры -
uuid.UUIDэкземпляры -
functools.partial()иfunctools.partialmethodэкземпляры, имеющие сериализуемыеfunc,args, иkeywordsзначения. - Чистые и конкретные объекты пути из
pathlib. Конкретные пути преобразуются в эквиваленты чистых путей, например,pathlib.PosixPathвpathlib.PurePosixPath. -
os.PathLikeэкземпляры, например,os.DirEntry, которые преобразуются вstrилиbytesс помощьюos.fspath(). -
LazyObjectэкземпляры, которые оборачивают сериализуемое значение. - Типы перечислений (например,
TextChoicesилиIntegerChoices) экземпляры. - Любое поле Django
- Любая ссылка на функцию или метод (например,
datetime.datetime.today) (должна находиться в глобальном пространстве имён модуля) - Несвязанные методы, используемые изнутри тела класса
- Любая ссылка на класс (должна находиться в глобальном пространстве имён модуля)
- Любой объект с пользовательским методом
deconstruct()(см. ниже)
Была добавлена поддержка сериализации для enum.Flag.
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должен содержать полное имя пути к классу, включая имя класса (например,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/4.2/topics/migrations/