Spec-Zone.ru › Django 3.0

Миграции

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

Команды

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

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

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

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

Примечание

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

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

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

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

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

Однако некоторые базы данных более функциональны, чем другие, когда речь идёт о миграциях схемы; некоторые особенности рассмотрены ниже.

PostgreSQL

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

Единственное замечание состоит в том, что до PostgreSQL 11 добавление столбцов со значениями по умолчанию приводит к полной перегенерации таблицы, пропорционально её размеру. По этой причине рекомендуется всегда создавать новые столбцы со значениями по умолчанию, так как таким образом они будут добавлены сразу.

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 запросит вас и предоставит несколько вариантов. Если посчитает это достаточно безопасным, предложит автоматическую линеаризацию этих двух миграций. Если нет, вам придется самостоятельно изменить миграции — не беспокойтесь, это несложно, и это подробно описано в разделе Файлы миграций ниже.

Зависимости

Хотя миграции предназначены для каждого приложения, таблицы и отношения, подразумеваемые вашими моделями, слишком сложны, чтобы создавать их по одному приложению за раз. Когда вы создаёте миграцию, которая требует выполнения чего-то ещё (например, вы добавляете 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 значения.
  • LazyObject экземпляры, которые оборачивают сериализуемое значение.
  • Типы перечислений (например, TextChoices или IntegerChoices ) экземпляры.
  • Любое поле Django
  • Любую ссылку на функцию или метод (например, datetime.datetime.today ) (должна находиться в глобальной области видимости модуля)
  • Несвязанные методы, используемые в теле класса
  • Любую ссылку на класс (должна находиться в глобальной области видимости модуля)
  • Всё, что имеет пользовательский метод deconstruct() (см. ниже)
Изменено в Django 2.2:

Была добавлена поддержка сериализации для NoneType.

Django не может сериализовать:

  • Вложенные классы
  • Произвольные экземпляры классов (например, MyClass(4.3, 5.7) )
  • Lambda-функции

Пользовательские сериализаторы

Новое в Django 2.2.

Вы можете сериализовать другие типы, написав пользовательский сериализатор. Например, если 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.0/topics/migrations/

Spec-Zone.ru

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