Миграции
Миграции — это способ 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 в производственной среде, если вы не хорошо понимаете риски и ограничения; встроенная в 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, и т. д.) с приложениями с миграциями. Иногда это может работать, но это не поддерживается.
Заменяемые зависимости
-
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)),
]
Django ищет подкласс django.db.migrations.Migration, называемый Migration, при загрузке файла миграции (как Python-модуля). Затем он проверяет этот объект на наличие четырёх атрибутов, из которых в большинстве случаев используются только два:
-
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) (должна быть в глобальной области видимости модуля)- Функции могут быть декорированы, если правильно обернуты, например, с использованием
functools.wraps() - Декораторы
functools.cache()иfunctools.lru_cache()явно поддерживаются
- Функции могут быть декорированы, если правильно обернуты, например, с использованием
- Несвязанные методы, используемые внутри тела класса
- Любая ссылка на класс (должна быть в глобальной области видимости модуля)
- Всё, что имеет пользовательский метод
deconstruct()(см. ниже)
Добавлена поддержка сериализации для enum.Flag.
Добавлена поддержка сериализации функций, декорированных functools.cache() или functools.lru_cache().
Django не может сериализовать:
- Вложенные классы
- Произвольные экземпляры классов (например,
MyClass(4.3, 5.7)) - Lambda-функции
Пользовательские сериализаторы
Вы можете сериализовать другие типы, написав пользовательский сериализатор. Например, если 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/5.0/topics/migrations/