Миграции
Миграции — это способ 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':
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 при загрузке файла миграции (как модуля 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, первоначальные миграции обрабатываются так же, как и любые другие миграции.
Добавление миграций в приложения
Добавление миграций в новые приложения простое — они предварительно настроены для принятия миграций, поэтому просто выполните makemigrations, как только внесете изменения.
Если ваше приложение уже имеет модели и таблицы базы данных, но еще нет миграций (например, вы создали его с предыдущей версией Django), вам нужно преобразовать его для использования миграций; это простой процесс:
$ python manage.py makemigrations your_app_label
Это создаст новую первоначальную миграцию для вашего приложения. Теперь выполните python
manage.py migrate --fake-initial, и Django обнаружит, что у вас есть первоначальная миграция и что таблицы, которые он должен создать, уже существуют, и отметит миграцию как уже примененную. (Без флага migrate
--fake-initial команда выдаст ошибку, потому что таблицы, которые она должна создать, уже существуют.)
Обратите внимание, что это работает только при соблюдении двух условий:
- Вы не изменяли свои модели с момента создания их таблиц. Для работы миграций необходимо сначала создать первоначальную миграцию, а затем внести изменения, так как Django сравнивает изменения с файлами миграции, а не с базой данных.
- Вы не редактировали базу данных вручную — Django не сможет обнаружить, что ваша база данных не соответствует вашим моделям, вы получите только ошибки, когда миграции попытаются изменить эти таблицы.
Флаг --fake-initial для migrate был добавлен. Раньше 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, models
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, models
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 (которые не могут быть оптимизированы) — 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 -
Экземпляры
functools.partial, которые имеют сериализуемые значенияfunc,argsиkeywords. -
Экземпляры
LazyObject, которые обертывают сериализуемое значение. - Любое поле Django
- Любая ссылка на функцию или метод (например,
datetime.datetime.today) (должна находиться в области видимости верхнего уровня модуля) - Любая ссылка на класс (должна находиться в области видимости верхнего уровня модуля)
- Всё, что имеет пользовательский метод
deconstruct()(см. ниже)
Поддержка сериализации для экземпляров functools.partial и LazyObject была добавлена.
Django может сериализовать следующие значения только в Python 3:
- Ссылочные методы, используемые внутри тела класса (см. ниже)
Django не может сериализовать:
- Вложенные классы
- Произвольные экземпляры классов (например,
MyClass(4.3, 5.7)) - Lambda-функции
Из-за того, что __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, могут не работать с более старыми версиями.
Переход с South
Если у вас уже есть предварительно созданные миграции, созданные с помощью South, то процесс обновления для использования django.db.migrations довольно прост:
- Убедитесь, что все установки полностью обновлены с их миграциями.
- Удалите
'south'изINSTALLED_APPS. - Удалите все ваши файлы миграций (с номерами), но не саму директорию или
__init__.py- убедитесь, что вы удалили также файлы.pyc. - Запустите
python manage.py makemigrations. Django должен увидеть пустые директории миграций и создать новые начальные миграции в новом формате. - Запустите
python manage.py migrate --fake-initial. Django увидит, что таблицы для начальных миграций уже существуют, и отметит их как примененные, не выполняя их. (Django не будет проверять, что структура таблиц соответствует вашим моделям, а только то, что существуют правильные имена таблиц).
Флаг migrate --fake-initial был добавлен. Ранее начальные миграции всегда автоматически фейково применялись, если были обнаружены существующие таблицы.
Библиотеки/Сторонние приложения
Если вы являетесь разработчиком библиотеки или приложения, и хотите поддерживать как миграции South (для Django 1.6 и ниже), так и миграции Django (для 1.7 и выше), вы должны сохранить два параллельных набора миграций в вашем приложении, один в каждом формате.
Для этого South 1.0 будет автоматически искать миграции в формате South в директории south_migrations в первую очередь, прежде чем искать в migrations, что означает, что проекты пользователей будут прозрачно использовать правильный набор, если вы поместите ваши миграции South в директорию south_migrations, а ваши миграции Django в директорию migrations.
Дополнительную информацию можно найти в примечаниях к выпуску South 1.0.
См. также
- Справочник по операциям миграции
- Охватывает API операций со схемой, специальные операции и написание собственных операций.
- Руководство по написанию миграций
- Объясняет, как структурировать и писать миграции базы данных для различных сценариев, с которыми вы можете столкнуться.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/topics/migrations/