Миграции
Миграции — это способ Django распространять изменения, внесенные в ваши модели (добавление поля, удаление модели и т. д.), в схему вашей базы данных. Они разработаны для большей автоматизации, но вам нужно знать, когда создавать миграции, когда их запускать и какие распространённые проблемы могут возникнуть.
Команды
Существует несколько команд, которые вы будете использовать для взаимодействия с миграциями и обработкой схемы базы данных в Django:
-
migrate, которая отвечает за применение и отмену миграций. -
makemigrations, которая отвечает за создание новых миграций на основе изменений, внесённых в ваши модели. -
sqlmigrate, которая отображает SQL-запросы для миграции. -
showmigrations, которая отображает миграции проекта и их статус.
Представьте миграции как систему контроля версий для схемы вашей базы данных. makemigrations отвечает за упаковку изменений в модели в отдельные файлы миграций — аналогично коммитам — а migrate отвечает за их применение к вашей базе данных.
Файлы миграции для каждой приложения находятся в каталоге «migrations» внутри этого приложения и предназначены для коммита и распространения как части кодовой базы. Вы должны создавать их один раз на вашей машине разработки, а затем запускать те же миграции на машинах коллег, на тестовых машинах и, в конечном итоге, на машинах в производстве.
Примечание
Можно переопределить имя пакета, содержащего миграции, на основе каждого приложения, изменив настройку MIGRATION_MODULES.
Миграции будут работать одинаково на одном и том же наборе данных и дадут согласованные результаты, то есть то, что вы видите в разработке и на этапе тестирования, при одинаковых условиях, произойдёт и в производстве.
Django будет создавать миграции для любых изменений в ваших моделях или полях — даже для опций, которые не влияют на базу данных — так как единственный способ правильно восстановить поле — это иметь все изменения в истории, а вам могут понадобиться эти опции в последующих миграциях данных (например, если вы установили пользовательские валидаторы).
Поддержка бэкендов
Миграции поддерживаются во всех бэкендах, которые поставляются с Django, а также в любых сторонних бэкендах, если в них реализована поддержка изменения схемы (с помощью класса SchemaEditor).
Однако некоторые базы данных более эффективны при миграциях схемы; некоторые замечания описаны ниже.
PostgreSQL
PostgreSQL — самая эффективная база данных в отношении поддержки схемы.
Единственный недостаток заключается в том, что до PostgreSQL 11 добавление столбцов со значениями по умолчанию приводит к полному переписыванию таблицы, пропорционально её размеру. По этой причине рекомендуется всегда создавать новые столбцы со значениями по умолчанию null=True, поскольку таким образом они будут добавлены немедленно.
MySQL
MySQL не поддерживает транзакции вокруг операций изменения схемы, что означает, что если миграция не удалась, вам придётся вручную отменить изменения, чтобы попробовать ещё раз (откат к предыдущей точке невозможен).
Кроме того, MySQL полностью переписывает таблицы практически для каждой операции со схемой и обычно затрачивает время, пропорциональное количеству строк в таблице, для добавления или удаления столбцов. На медленном оборудовании это может быть хуже, чем минута на миллион строк — добавление нескольких столбцов в таблицу всего с несколькими миллионами строк может заблокировать ваш сайт более чем на десять минут.
Наконец, MySQL имеет относительно небольшие ограничения на длину имён столбцов, таблиц и индексов, а также ограничения на суммарный размер всех столбцов, которые покрывает индекс. Это означает, что индексы, возможные в других бэкендах, не смогут быть созданы в MySQL.
SQLite
SQLite имеет очень слабую встроенную поддержку изменения схемы, поэтому Django пытается эмулировать её:
- Создание новой таблицы с новой схемой
- Копирование данных
- Удаление старой таблицы
- Переименование новой таблицы для соответствия исходному имени
Этот процесс, как правило, работает хорошо, но он может быть медленным и иногда глючить. Не рекомендуется запускать и мигрировать SQLite в среде производства, если вы не очень хорошо понимаете риски и ограничения; поддержка, которая поставляется с Django, предназначена для того, чтобы разработчики могли использовать SQLite на своих локальных машинах для разработки менее сложных проектов Django без необходимости полной базы данных.
Порядок работы
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 = '%s %s' % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
После этого мы можем запустить python manage.py migrate как обычно, и миграция данных будет выполняться вместе с другими миграциями.
Вы можете передать второй вызываемый объект в RunPython, чтобы выполнить любые логические действия, которые вы хотите выполнить при обратной миграции. Если этот вызываемый объект опущен, обратная миграция вызовет исключение.
Доступ к моделям из других приложений
При написании функции RunPython , использующей модели из приложений, отличных от того, в котором находится миграция, атрибут миграции dependencies должен включать последнюю миграцию каждого вовлечённого приложения, иначе вы можете получить ошибку, подобную: LookupError: No installed app
with label 'myappname' при попытке извлечь модель в функции RunPython с помощью apps.get_model().
В приведенном ниже примере у нас есть миграция в app1, которая должна использовать модели в app2. Мы не будем вдаваться в подробности move_m1 , кроме того факта, что она должна получить доступ к моделям из обоих приложений. Поэтому мы добавили зависимость, которая указывает на последнюю миграцию app2:
class Migration(migrations.Migration):
dependencies = [
('app1', '0001_initial'),
# added dependency to enable using models from app2 in move_m1
('app2', '0004_foobar'),
]
operations = [
migrations.RunPython(move_m1),
]
Более продвинутые миграции
Если вас интересуют более продвинутые операции миграции или вы хотите написать свои собственные, см. справочник по операциям миграции и «как» по написанию миграций.
Сжатие миграций
Вам рекомендуется свободно создавать миграции и не беспокоиться о том, сколько их; код миграции оптимизирован для обработки сотен одновременно без существенного замедления. Однако в конечном итоге вы захотите перейти от нескольких сотен миграций к нескольким, и именно здесь пригодится сжатие.
Сжатие — это процесс сокращения набора существующих миграций до одной (или иногда нескольких) миграций, которые по-прежнему представляют те же изменения.
Django делает это, беря все ваши существующие миграции, извлекая их Operation и помещая их все в последовательность, а затем выполняет оптимизатор над ними, чтобы попытаться сократить длину списка. Например, он знает, что CreateModel и DeleteModel взаимно компенсируют друг друга, и он знает, что AddField можно включить в CreateModel.
После того, как последовательность операций была сокращена максимально возможно — количество возможных сокращений зависит от того, насколько тесно связаны ваши модели и есть ли у вас операции RunSQL или RunPython (которые нельзя оптимизировать, если они не помечены как elidable), Django запишет их обратно в новые файлы миграций.
Эти файлы помечены как заменяющие ранее сжатые миграции, поэтому они могут сосуществовать со старыми файлами миграций, и Django будет интеллектуально переключаться между ними в зависимости от того, где вы находитесь в истории. Если вы всё ещё находитесь в середине набора миграций, которые вы сжали, он будет продолжать их использовать, пока не достигнет конца, а затем переключится на сжатую историю, а новые установки будут использовать новую сжатую миграцию и пропустят все старые.
Это позволяет сжимать, сохраняя старые файлы, совершать коммит и релиз, ожидать, пока все системы будут обновлены с новым релизом (или если вы проект третьей стороны, убедитесь, что ваши пользователи обновляют релизы по порядку, не пропускают ничего), а затем удалить старые файлы, сделать коммит и выполнить второй релиз.
Команда, которая поддерживает всё это, это squashmigrations — передайте ей имя приложения и имя миграции, которую вы хотите сжать, и она приступит к работе:
$ ./manage.py squashmigrations myapp 0004 Will squash the following migrations: - 0001_initial - 0002_some_change - 0003_another_change - 0004_undo_something Do you wish to proceed? [yN] y Optimizing... Optimized from 12 operations to 7 operations. Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_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 определяет сжатую миграцию).
Примечание
После сжатия миграции не следует повторно сжимать эту сжатую миграцию, пока вы не перейдете к обычной миграции.
Сериализация значений
Миграции — это файлы Python, содержащие старые определения ваших моделей. Таким образом, для их записи Django должен получить текущее состояние ваших моделей и сериализовать их в файл.
Хотя Django может сериализовать большинство вещей, есть некоторые вещи, которые мы просто не можем сериализовать в действительное представление Python — нет стандартного Python-представления того, как значение можно преобразовать обратно в код (repr() работает только для базовых значений и не определяет пути импорта).
Django может сериализовать следующее:
-
int,float,bool,str,bytes,None,NoneType -
list,set,tuple,dict,range. -
datetime.date,datetime.time, иdatetime.datetimeэкземпляры (включая те, которые учитывают часовой пояс) -
decimal.Decimalэкземпляры -
enum.Enumэкземпляры -
uuid.UUIDэкземпляры -
functools.partial()иfunctools.partialmethodэкземпляры, у которых сериализуемыеfunc,args, иkeywordsзначения. - Чистые и конкретные объекты пути из
pathlib. Конкретные пути преобразуются в эквивалентные чистые пути, например,pathlib.PosixPathвpathlib.PurePosixPath. -
os.PathLikeэкземпляры, например,os.DirEntry, которые преобразуются вstrилиbytesс помощьюos.fspath(). -
LazyObjectэкземпляры, которые оборачивают сериализуемое значение. - Типы перечислений (например,
TextChoicesилиIntegerChoices) экземпляры. - Любое поле Django
- Любая ссылка на функцию или метод (например,
datetime.datetime.today) (должно быть в области видимости верхнего уровня модуля) - Несвязанные методы, используемые внутри тела класса
- Любая ссылка на класс (должно быть в области видимости верхнего уровня модуля)
- Всё, что имеет пользовательский метод
deconstruct()(см. ниже)
Добавлена поддержка сериализации для чистых и конкретных объектов пути из pathlib, и os.PathLike экземпляров.
Django не может сериализовать:
- Вложенные классы
- Произвольные экземпляры классов (например,
MyClass(4.3, 5.7)) - Лямбда-функции
Пользовательские сериализаторы
Вы можете сериализовать другие типы, написав пользовательский сериализатор. Например, если Django по умолчанию не сериализовывал Decimal, вы можете сделать это:
from decimal import Decimal
from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter
class DecimalSerializer(BaseSerializer):
def serialize(self):
return repr(self.value), {'from decimal import Decimal'}
MigrationWriter.register_serializer(Decimal, DecimalSerializer)
Первый аргумент MigrationWriter.register_serializer() — это тип или итерируемый набор типов, которые должны использовать сериализатор.
Метод serialize() вашего сериализатора должен возвращать строку, описывающую, как значение должно отображаться в миграциях, и набор необходимых импортов в миграции.
Добавление метода deconstruct()
Вы можете позволить Django сериализовать экземпляры ваших собственных пользовательских классов, предоставив классу метод deconstruct(). Он не принимает аргументы и должен возвращать кортеж из трёх вещей (path, args, kwargs):
-
pathдолжен быть полным путем к классу в Python, включая имя класса в качестве последней части (например,myapp.custom_things.MyClass). Если ваш класс недоступен на верхнем уровне модуля, он не сериализуется. -
argsдолжен быть списком позиционных аргументов, которые нужно передать методу__init__вашего класса. Все элементы этого списка должны быть сами по себе сериализуемыми. -
kwargsдолжен быть словарем ключевых аргументов, которые нужно передать методу__init__вашего класса. Каждое значение должно быть сериализуемым.
Примечание
Это возвращаемое значение отличается от метода deconstruct() для пользовательских полей, который возвращает кортеж из четырёх элементов.
Django будет записывать значение как экземпляр вашего класса с заданными аргументами, аналогично тому, как он записывает ссылки на поля Django.
Чтобы предотвратить создание новой миграции каждый раз, когда выполняется makemigrations, вы также должны добавить метод __eq__() к декорируемому классу. Эта функция будет вызываться фреймворком миграций Django для обнаружения изменений между состояниями.
Пока все аргументы конструктора вашего класса сами по себе сериализуемы, вы можете использовать декоратор класса @deconstructible из django.utils.deconstruct для добавления метода deconstruct():
from django.utils.deconstruct import deconstructible
@deconstructible
class MyCustomClass:
def __init__(self, foo=1):
self.foo = foo
...
def __eq__(self, other):
return self.foo == other.foo
Декоратор добавляет логику для захвата и сохранения аргументов по мере их попадания в конструктор, а затем возвращает эти аргументы точно при вызове deconstruct().
Поддержка нескольких версий Django
Если вы являетесь разработчиком стороннего приложения с моделями, вам может потребоваться отправить миграции, поддерживающие несколько версий Django. В этом случае вы всегда должны запускать makemigrations с самой низкой версией Django, которую вы хотите поддержать.
Система миграций будет сохранять обратную совместимость в соответствии с той же политикой, что и остальная часть Django, поэтому файлы миграций, сгенерированные на Django X.Y, должны работать без изменений на Django X.Y+1. Однако система миграций не гарантирует совместимость в будущем. Могут быть добавлены новые функции, и файлы миграций, сгенерированные с более новыми версиями Django, могут не работать на более старых версиях.
См. также
- Справочник по операциям миграций
- Охватывает API операций схемы, специальные операции и написание собственных операций.
- Руководство по написанию миграций
- Объясняет, как структурировать и писать миграции базы данных для различных ситуаций, с которыми вы можете столкнуться.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.0/topics/migrations/