Spec-Zone.ru › Django 3.2

Миграции

Миграции — это способ 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_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, в этом случае вы можете исправить её вручную.

Чтобы вручную исправить 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() (см. ниже)
Изменено в Django 3.2:

Была добавлена поддержка сериализации чистых и конкретных объектов пути из pathlib, и os.PathLike экземпляров.

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/3.2/topics/migrations/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API