Миграции

Миграции - это способ Django распространять изменения, которые вы вносите в свои модели (добавление поля, удаление модели и т. Д.), В схему вашей базы данных. Они разработаны, чтобы быть в основном автоматическими, но вам нужно знать, когда выполнять миграции, когда их запускать и с какими общими проблемами вы можете столкнуться.

Команды

Есть несколько команд, которые вы будете использовать для взаимодействия с миграциями и обработкой схемы базы данных Django:

  • migrate, который отвечает за применение и отмену миграций.
  • makemigrations, который отвечает за создание новых миграций на основе изменений, которые вы внесли в свои модели.
  • sqlmigrate, который отображает операторы SQL для миграции.
  • showmigrations, в котором перечислены миграции проекта и их статус.

Вы должны думать о миграции как о системе контроля версий для вашей схемы базы данных. makemigrationsотвечает за упаковку изменений вашей модели в отдельные файлы миграции - аналогично фиксации - и migrateотвечает за их применение в вашей базе данных.

Файлы миграции для каждого приложения находятся в каталоге «миграции» внутри этого приложения и предназначены для передачи и распространения как часть его кодовой базы. Вы должны сделать их один раз на своей машине разработки, а затем запустить те же миграции на машинах ваших коллег, ваших промежуточных машинах и, в конечном итоге, ваших производственных машинах.

Примечание

Можно переопределить имя пакета, который содержит миграции для каждого приложения, изменив 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)),
    ]

При загрузке файла миграции (в виде модуля Python) Django ищет подкласс класса django.db.migrations.Migrationcalled 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 = Trueinitial

Когда эта опция используется, эти начальные миграции обрабатываются особым образом. Для первоначальной миграции, при которой создается одна или несколько таблиц ( операция), Django проверяет, что все эти таблицы уже существуют в базе данных, и выполняет имитацию миграции, если это так. Точно так же для начальной миграции, которая добавляет одно или несколько полей ( операцию), Django проверяет, что все соответствующие столбцы уже существуют в базе данных, и применяет фиктивную миграцию, если это так. Без него начальные миграции обрабатываются так же, как и любые другие миграции.migrate --fake-initialCreateModelAddField--fake-initial

Согласованность истории

Как обсуждалось ранее, вам может потребоваться вручную линеаризовать миграции при объединении двух ветвей разработки. При редактировании зависимостей миграции вы можете непреднамеренно создать несогласованное состояние истории, в котором миграция была применена, но некоторые из ее зависимостей нет. Это явный признак того, что зависимости неверны, поэтому Django откажется выполнять миграции или делать новые миграции, пока они не будут исправлены. При использовании нескольких баз данных вы можете использовать allow_migrate()метод маршрутизаторов баз данных, чтобы контролировать, какие базы данных makemigrationsпроверяют согласованную историю.

Добавление миграций в приложения

Новые приложения предварительно настроены для принятия миграций, поэтому вы можете добавить миграции, запустив их makemigrationsпосле внесения некоторых изменений.

Если в вашем приложении уже есть модели и таблицы базы данных и еще нет миграций (например, вы создали его для предыдущей версии Django), вам необходимо преобразовать его для использования миграции, запустив:

$ python manage.py makemigrations your_app_label

Это сделает новую первоначальную миграцию для вашего приложения. Теперь запустите , и Django обнаружит, что у вас есть начальная миграция и что таблицы, которые он хочет создать, уже существуют, и отметит миграцию как уже примененную. (Без флага команда выдала бы ошибку, потому что таблицы, которые она хочет создать, уже существуют.)python manage.py migrate --fake-initialmigrate --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 = TrueMeta

Предупреждение

Это означает, что у вас НЕ будет настраиваемых 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'RunPythonapps.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операции or (которые не могут быть оптимизированы, если они не отмечены как 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_somthing.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, и в этом случае вы можете вручную решить эту проблему.

Чтобы вручную разрешить a CircularDependencyError, выделите один из ForeignKeys в цикле циклических зависимостей в отдельную миграцию и переместите с ним зависимость от другого приложения. Если вы не уверены, посмотрите, как 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()методом ( см. Ниже )
Изменено в Django 3.2:

Добавлена ​​поддержка сериализации для чистых и конкретных объектов пути из экземпляров 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. В этом случае вам всегда следует использовать самую низкую версию Django, которую вы хотите поддерживать .makemigrations

Система миграции будет поддерживать обратную совместимость в соответствии с той же политикой, что и остальная часть Django, поэтому файлы миграции, созданные в Django XY, должны запускаться без изменений в Django X.Y + 1. Однако система миграции не обещает прямой совместимости. Могут быть добавлены новые функции, а файлы миграции, созданные в более новых версиях Django, могут не работать в более старых версиях.

Смотрите также

Справочник по операциям миграции
Охватывает API операций схемы, специальные операции и написание собственных операций.
Написание миграционных инструкций
Объясняет, как структурировать и записывать миграции базы данных для различных сценариев, с которыми вы можете столкнуться.

Copyright ©2021 All rights reserved