Миграции
Миграции — это способ 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 ищет при загрузке файла миграции (как модуля 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 миграции должен включать последнюю миграцию каждого приложения, вовлечённого в процесс. В противном случае при попытке извлечь модель в функции 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 и упорядочивая их последовательно, а затем применяя оптимизатор, чтобы попытаться сократить длину списка. Например, он знает, что 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? [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, в этом случае вы можете вручную её разрешить.
Чтобы вручную разрешить циклическую зависимость, выделите одну из 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()(см. ниже)
Добавлена поддержка сериализации функций, декорированных 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.1/topics/migrations/