Spec-Zone.ru › Django 1.11

Миграции

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

Команды

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

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

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

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

Примечание

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

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

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

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

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

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

PostgreSQL

PostgreSQL — наиболее эффективна из всех баз данных здесь с точки зрения поддержки схемы; единственное замечание заключается в том, что добавление столбцов со значениями по умолчанию приведет к полной переписыванию таблицы, пропорционально её размеру.

По этой причине рекомендуется всегда создавать новые столбцы со null=True, так как таким образом они будут добавлены немедленно.

MySQL

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

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

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

SQLite

SQLite имеет очень ограниченную встроенную поддержку изменения схемы, поэтому Django пытается эмулировать её следующим образом:

  • Создание новой таблицы с новой схемой
  • Копирование данных
  • Удаление старой таблицы
  • Переименование новой таблицы для соответствия оригинальному имени

Этот процесс в целом работает хорошо, но он может быть медленным и иногда глючным. Не рекомендуется запускать и мигрировать SQLite в производственной среде, если вы не осознаете риски и ограничения; поддержка, поставляемая с Django, предназначена для того, чтобы разработчики могли использовать SQLite на своих локальных машинах для разработки менее сложных проектов 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 ) — это обещание «лучше, чем ничего», а не гарантия; любые другие приложения, необходимые для корректного определения зависимостей, будут использоваться.

Файлы миграций

Миграции хранятся в формате на диске, здесь называемом «файлами миграций». Фактически, это обычные файлы 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 ищет подкласс django.db.migrations.Migration по имени Migration, когда загружает файл миграции (как модуль Python). Затем он проверяет этот объект на наличие четырёх атрибутов, из которых два используются чаще всего:

  • 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 проверяет на согласованность истории.

Изменено в Django 1.10:

Были добавлены проверки согласованности миграций. Проверки, основанные на роутерах баз данных, были добавлены в 1.10.1.

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

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

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

$ python manage.py makemigrations your_app_label

Это создаст новую первоначальную миграцию для вашего приложения. Теперь запустите python manage.py migrate --fake-initial, и Django обнаружит, что у вас есть первоначальная миграция и что таблицы, которые он хочет создать, уже существуют, и пометит миграцию как уже применённую. (Без флага migrate --fake-initial команда выдаст ошибку, так как таблицы, которые он хочет создать, уже существуют.)

Обратите внимание, что это работает только при двух условиях:

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

Модели истории

При выполнении миграций 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

Затем откройте файл; он должен выглядеть примерно так:

# -*- coding: utf-8 -*-
# Generated by Django A.B on YYYY-MM-DD HH:MM
from __future__ import unicode_literals

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
    ]

Теперь всё, что вам нужно сделать, это создать новую функцию и использовать RunPython для её вызова. RunPython ожидает вызываемый объект в качестве аргумента, который принимает два аргумента — первый — это реестр приложений, содержащий исторические версии всех ваших моделей, соответствующие тому, где в истории находится миграция, а второй — SchemaEditor, с помощью которого вы можете вручную изменять схему базы данных (но будьте осторожны, это может сбить с толку автообнаруживатель миграций!)

Давайте напишем простую миграцию, которая заполняет наше новое поле name комбинированными значениями first_name и last_name (мы пришли к выводу, что не у всех есть имя и фамилия). Всё, что нам нужно сделать, это использовать историческую модель и перебрать строки:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

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_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.

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

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

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

Затем вы должны перевести сжатую миграцию в обычную миграцию:

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

Примечание

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

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

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

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

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

  • int, long, float, bool, str, unicode, bytes, None
  • list, set, tuple, dict
  • экземпляры datetime.date, datetime.time, и datetime.datetime (включая экземпляры, учитывающие часовой пояс)
  • экземпляры decimal.Decimal
  • экземпляры enum.Enum
  • экземпляры uuid.UUID
  • экземпляры functools.partial, у которых есть сериализуемые значения func, args, и keywords
  • экземпляры LazyObject, которые содержат сериализуемое значение
  • Любое поле Django
  • Любая ссылка на функцию или метод (например, datetime.datetime.today)(должна быть в глобальном пространстве имён модуля)
  • Любая ссылка на класс (должна быть в глобальном пространстве имён модуля)
  • Любое значение с пользовательским методом deconstruct() (см. ниже)
Изменено в Django 1.10:

Добавлена поддержка сериализации enum.Enum.

Изменено в Django 1.11:

Добавлена поддержка сериализации uuid.UUID.

Django может сериализовать следующие элементы только на Python 3:

  • Свободные методы, используемые внутри тела класса (см. ниже)

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

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

Из-за того, что __qualname__ был введён только в Python 3, Django может сериализовать только следующую структуру (свободный метод, используемый внутри тела класса) на Python 3 и не сможет сериализовать ссылку на него на Python 2:

class MyModel(models.Model):

    def upload_to(self):
        return "something dynamic"

    my_file = models.FileField(upload_to=upload_to)

Если вы используете Python 2, рекомендуется перенести методы для upload_to и подобных аргументов, принимающих вызываемые функции (например, default), в тело основного модуля, а не в тело класса.

Добавление метода 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(object):

    def __init__(self, foo=1):
        self.foo = foo
        ...

    def __eq__(self, other):
        return self.foo == other.foo

Декоратор добавляет логику для захвата и сохранения аргументов во время передачи им в конструктор, а затем возвращает эти аргументы точно при вызове deconstruct().

Поддержка Python 2 и 3

Для генерации миграций, поддерживающих Python 2 и 3, все строковые литералы, используемые в ваших моделях и полях (например, verbose_name, related_name, и т.д.), должны быть последовательно строками байтов или строками текста (unicode) как в Python 2, так и в Python 3 (а не строками байтов в Python 2 и строками текста в Python 3, как в случае с немаркированными строковыми литералами). В противном случае, запуск makemigrations в Python 3 сгенерирует ложные новые миграции для преобразования всех этих атрибутов строк в текст.

Самый простой способ добиться этого — следовать рекомендациям в руководстве по переносу на Python 3 Django и убедиться, что все ваши модули начинаются с from __future__ import unicode_literals, чтобы все немаркированные строковые литералы всегда были строками unicode независимо от версии Python. Когда вы добавите это в приложение с существующими миграциями, сгенерированными на Python 2, ваш следующий запуск makemigrations в Python 3, вероятно, сгенерирует много изменений, так как он преобразует все атрибуты строк байтов в строки текста; это нормально и должно произойти только один раз.

Поддержка нескольких версий 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/1.11/topics/migrations/

Spec-Zone.ru

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