Spec-Zone.ru › Django 1.10

Миграции

Миграции — это способ 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

Django просканирует и сравнит ваши модели с версиями, которые есть в ваших файлах миграций, и затем сгенерирует новый набор миграций. Убедитесь, что вы прочитали вывод, чтобы увидеть, что 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
Новая функция в Django 1.9.

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

Первоначальные миграции отмечаются атрибутом класса 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, 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 операции (которые нельзя оптимизировать, если они не помечены как 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 экземпляры
  • functools.partial экземпляры, у которых есть сериализуемые func, args, и keywords значения.
  • LazyObject экземпляры, которые оборачивают сериализуемое значение.
  • Любой Django-поток
  • Любая функция или ссылка на метод (например, datetime.datetime.today) (должны быть в глобальной области видимости модуля)
  • Любая ссылка на класс (должна быть в глобальной области видимости модуля)
  • Всё, что имеет пользовательский метод deconstruct() (см. ниже)
Изменено в Django 1.9:

Добавлена поддержка сериализации для functools.partial и LazyObject экземпляров.

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

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

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 сгенерирует излишние новые миграции для преобразования всех этих строковых атрибутов в текстовые.

Самый простой способ добиться этого — следовать рекомендациям в руководстве по портированию Django на Python 3 и убедиться, что все ваши модули начинаются с 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.10/topics/migrations/

Spec-Zone.ru

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