Spec-Zone.ru › Django 4.1

Миграции

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

Команды

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

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

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

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

Примечание

Возможно переопределение имени пакета, содержащего миграции, на основе каждого приложения, изменив настройку MIGRATION_MODULES.

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

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

Поддержка бэкендов

Миграции поддерживаются всеми бэкендами, поставляемыми с Django, а также любыми сторонними бэкендами, если они реализовали поддержку изменения схемы (через класс SchemaEditor).

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

PostgreSQL

PostgreSQL обладает наибольшей функциональностью в отношении поддержки схемы среди всех баз данных.

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 миграции должен включать последнюю миграцию каждого вовлечённого приложения, иначе при попытке извлечь модель в функции RunPython с помощью apps.get_model() может возникнуть ошибка, подобная: LookupError: No installed app with label 'myappname'.

В следующем примере у нас есть миграция в app1, которая должна использовать модели в app2. Мы не будем вникать в детали move_m1 за исключением того, что ей придётся получить доступ к моделям из обоих приложений. Поэтому мы добавили зависимость, которая определяет последнюю миграцию app2.

class Migration(migrations.Migration):

    dependencies = [
        ('app1', '0001_initial'),
        # added dependency to enable using models from app2 in move_m1
        ('app2', '0004_foobar'),
    ]

    operations = [
        migrations.RunPython(move_m1),
    ]

Более сложные миграции

Если вы заинтересованы в более сложных операциях миграции или хотите написать свои собственные, см. справочник по операциям миграции и «как-то» на написании миграций.

Сжатие миграций

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

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

Django делает это, беря все ваши существующие миграции, извлекая их Operation и помещая их все в последовательность, а затем выполняет оптимизатор над ними, чтобы попытаться сократить длину списка — например, он знает, что CreateModel и DeleteModel взаимно компенсируют друг друга, и он знает, что AddField можно объединить с CreateModel.

После того, как последовательность операций будет уменьшена до максимума (размер зависит от взаимосвязанности моделей и наличия операций RunSQL или RunPython, которые не могут быть оптимизированы, если не помечены как elidable), Django запишет её обратно в новый набор файлов миграций.

Эти файлы помечены, чтобы указать, что они заменяют ранее сжатые миграции, поэтому они могут сосуществовать со старыми файлами миграций, и Django будет интеллектуально переключаться между ними в зависимости от того, где вы находитесь в истории. Если вы всё ещё находитесь в процессе выполнения набора миграций, которые были сжаты, он будет продолжать использовать их до тех пор, пока не достигнет конца, а затем переключится на сжатую историю, в то время как новые установки будут использовать новую сжатую миграцию и пропустит все старые.

Это позволяет сжать миграции и не сломать системы, которые сейчас работают в режиме производства и не полностью обновлены. Рекомендуемый процесс: сжать, сохранить старые файлы, сделать коммит и релиз, подождать, пока все системы будут обновлены с помощью нового релиза (или, если вы проект стороннего разработчика, убедитесь, что ваши пользователи обновляют релизы в порядке, без пропуска каких-либо), а затем удалить старые файлы, сделать коммит и сделать второй релиз.

Команда, которая поддерживает всё это, — это squashmigrations. Передайте ей имя приложения и имя миграции, которую вы хотите сжать, и она приступит к работе:

$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [yN] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.

Используйте параметр squashmigrations --squashed-name, если хотите установить имя сжатой миграции вместо использования автоматически сгенерированного.

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

Чтобы вручную устранить CircularDependencyError, выделите одну из ForeignKey в циклической зависимости, создайте для неё отдельную миграцию и перенесите зависимость от другого приложения вместе с ней. Если вы не уверены, посмотрите, как makemigrations справляется с проблемой, когда его просят создать совершенно новые миграции из ваших моделей. В будущих версиях Django squashmigrations будет обновлена, чтобы попытаться самостоятельно разрешать эти ошибки.

После того, как вы сжали свою миграцию, вы должны её сохранить вместе с миграциями, которые она заменяет, и распространить это изменение на все работающие экземпляры вашего приложения, убедившись, что они запускают migrate для сохранения изменения в своей базе данных.

Затем необходимо переключиться со сжатой миграции на нормальную миграцию:

  • Удалите все файлы миграций, которые она заменяет.
  • Обновите все миграции, которые зависят от удалённых миграций, чтобы они зависели от сжатой миграции вместо них.
  • Удалите атрибут replaces в классе Migration сжатой миграции (так Django определяет, что это сжатая миграция).

Примечание

После сжатия миграции не следует повторно сжимать сжатую миграцию до тех пор, пока вы не переключите её полностью на нормальную миграцию.

Удаление ссылок на удалённые миграции

Новое в Django 4.1.

Если в будущем вы, вероятно, будете повторно использовать имя удалённой миграции, вы должны удалить ссылки на неё из таблицы миграций Django с помощью параметра migrate --prune.

Сериализация значений

Миграции — это файлы Python, содержащие старые определения ваших моделей, поэтому для их записи Django должен взять текущее состояние ваших моделей и сериализовать их в файл.

Хотя Django может сериализовать большинство вещей, есть некоторые вещи, которые мы просто не можем сериализовать в допустимое представление Python — нет стандартного Python-представления, как значение может быть преобразовано обратно в код (repr() работает только для основных значений и не определяет пути импорта).

Django может сериализовать следующее:

  • int, float, bool, str, bytes, None, NoneType
  • list, set, tuple, dict, range.
  • datetime.date, datetime.time, и datetime.datetime экземпляры (включая те, которые учитывают часовой пояс)
  • decimal.Decimal экземпляры
  • enum.Enum экземпляры
  • 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 не может сериализовать:

  • Вложенные классы
  • Произвольные экземпляры классов (например, 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.1/topics/migrations/

Spec-Zone.ru

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