Операции над миграциями

Файлы миграции состоят из одного или нескольких объектов Operation, которые декларативно записывают, что миграция должна делать с вашей базой данных.

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

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

Если вам нужен пустой файл миграции для записи собственных объектов Operation, используйте python manage.py makemigrations --empty yourappname, но имейте в виду, что добавление операций изменения схемы вручную может сбить с толку автодетектор миграции и выполнение запуска makemigrations может вывести неверный код.

Все основные операции Django доступны из модуля django.db.migrations.operations.

Вводный материал смотрите в Руководство по миграциям.

Операции для схем

CreateModel

class CreateModel(name, fields, options=None, bases=None, managers=None)[исходный код]

Создает новую модель в истории проекта и соответствующую таблицу в базе данных.

name - это название модели, которое должно быть записано в файле models.py.

fields - это список из двух кортежей (field_name, field_instance). Экземпляр поля должен быть несвязанным полем (как models.CharField(...), а не поле, взятое из другой модели).

options - это необязательный словарь значений из класса Meta модели.

base - это необязательный список других классов, от которых наследуется эта модель; он может содержать как объекты класса, так и строки в формате appname.ModelName, если вы хотите зависеть от другой модели (чтобы вы унаследовали от исторической версии). Если он не указан, по умолчанию он унаследован от стандартной модели models.Model.

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

DeleteModel

class DeleteModel(name)[исходный код]

Удаляет модель из истории проекта и ее таблицу из базы данных.

RenameModel

class RenameModel(old_name, new_name)[исходный код]

Переименовывает модель со старого имени на новое.

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

AlterModelTable

class AlterModelTable(name, table)[исходный код]

Изменяет имя таблицы модели (параметр db_table в подклассе Meta).

AlterUniqueTogether

class AlterUniqueTogether(name, unique_together)[исходный код]

Изменяет набор уникальных ограничений модели (параметр unique_together в подклассе Meta).

AlterIndexTogether

class AlterIndexTogether(name, index_together)[исходный код]

Изменяет набор пользовательских индексов модели (параметр index_together в подклассе Meta)

AlterOrderWithRespectTo

class AlterOrderWithRespectTo(name, order_with_respect_to)[исходный код]

Создает или удаляет столбец _order, необходимый для параметра order_with_respect_to в подклассе Meta.

AlterModelOptions

class AlterModelOptions(name, options)[исходный код]

Сохраняет изменения различных параметров модели (настройки в Meta модели), например permissions и verbose_name. Не влияет на базу данных, но сохраняет эти изменения для использования экземпляров RunPython. options должен быть словарём, отображающим имена опций для значений.

AlterModelManagers

class AlterModelManagers(name, managers)[исходный код]

Изменяет менеджеров, доступных во время миграции.

AddField

class AddField(model_name, name, field, preserve_default=True)[исходный код]

Добавляет поле в модель. model_name - это имя модели, name - имя поля, а field - несвязанный экземпляр Field (то, что вы бы поместили в объявление поля в models.py - например, models.IntegerField(null=True).

Аргумент preserve_default указывает, является ли значение поля по умолчанию постоянным и должно ли оно быть записано в состояние проекта (True), или оно является временным и только для этой миграции (False) - обычно потому что миграция добавляет в таблицу поле, не допускающее значения NULL, и требует значения по умолчанию для помещения в существующие строки. Это не влияет на поведение установки значений по умолчанию в базе данных напрямую - Django никогда не устанавливает значения по умолчанию для базы данных и всегда применяет их в коде Django ORM.

Предупреждение

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

  • Добавьте поле, допускающее значение NULL, без значения по умолчанию и выполните команду makemigrations. Это должно вызвать миграцию с операцией AddField.
  • Добавьте в свое поле значение по умолчанию и выполните команду makemigrations. Это должно вызвать миграцию с операцией AlterField.

RemoveField

class RemoveField(model_name, name)[исходный код]

Удаляет поле из модели.

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

AlterField

class AlterField(model_name, name, field, preserve_default=True)[исходный код]

Изменяет определение поля, включая изменения его типа, null, unique, db_column и другие атрибуты поля.

Аргумент preserve_default указывает, является ли значение поля по умолчанию постоянным и должно ли оно быть записано в состояние проекта (True), или оно временное и предназначено только для этой миграции (False) - обычно потому что миграция изменяет поле, допускающее значение NULL, на поле, не допускающее значения NULL, и требует значения по умолчанию для помещения в существующие строки. Это не влияет на поведение установки значений по умолчанию в базе данных напрямую - Django никогда не устанавливает значения по умолчанию для базы данных и всегда применяет их в коде Django ORM.

Обратите внимание, что не все изменения возможны во всех базах данных - например, вы не можете изменить поле текстового типа, такое как models.TextField(), в поле числового типа, например models.IntegerField() в большинстве базы данных.

RenameField

class RenameField(model_name, old_name, new_name)[исходный код]

Изменяет имя поля (и, если не задано db_column, имя его столбца).

AddIndex

class AddIndex(model_name, index)[исходный код]

Создает индекс в таблице базы данных для модели с именем model_name. index - это экземпляр класса Index.

RemoveIndex

class RemoveIndex(model_name, name)[исходный код]

Удаляет индекс с именем name из модели с model_name.

AddConstraint

class AddConstraint(model_name, constraint)[исходный код]

Создает constraint в таблице базы данных для модели с именем model_name.

RemoveConstraint

class RemoveConstraint(model_name, name)[исходный код]

Удаляет ограничение с именем name из модели с model_name.

Специальные операции

RunSQL

class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)[исходный код]

Позволяет запускать произвольный SQL в базе данных - полезно для более сложных функций серверной части базы данных, которые Django не поддерживает напрямую.

sql и reverse_sql, если они указаны, должны быть строками SQL для запуска в базе данных. В большинстве бэкэндов баз данных (всех, кроме PostgreSQL) Django разбивает SQL на отдельные операторы перед их выполнением.

Предупреждение

В PostgreSQL и SQLite используйте только BEGIN или COMMIT в вашем SQL в неатомарных миграциях, чтобы не нарушать состояние транзакции Django.

Вы также можете передать список строк или двух кортежей. Последний используется для передачи запросов и параметров так же, как cursor.execute(). Эти три операции эквивалентны:

migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])])

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

Запросы reverse_sql выполняются, когда миграция не применяется. Они должны отменить то, что делают запросы sql. Например, чтобы отменить указанную выше вставку с удалением:

migrations.RunSQL(
    sql=[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
    reverse_sql=[("DELETE FROM musician where name=%s;", ['Reinhardt'])],
)

Если reverse_sql равно None (по умолчанию), операция RunSQL необратима.

Аргумент state_operations позволяет вам предоставлять операции, эквивалентные SQL с точки зрения состояния проекта. Например, если вы вручную создаете столбец, вы должны передать сюда список, содержащий операцию AddField, чтобы автодетектор по-прежнему имел актуальное состояние модели. Если вы этого не сделаете, при следующем запуске makemigrations он не увидит никаких операций, добавляющих это поле, и попытается запустить его снова. Например:

migrations.RunSQL(
    "ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
    state_operations=[
        migrations.AddField(
            'musician',
            'name',
            models.CharField(max_length=255),
        ),
    ],
)

Необязательный аргумент hints будет передан как **hints в метод allow_migrate() маршрутизаторов баз данных, чтобы помочь им в принятии решений о маршрутизации. Смотрите Подсказки для получения более подробной информации о подсказках базы данных.

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

RunSQL.noop

Передайте атрибут RunSQL.noop параметру sql или reverse_sql, если вы хотите, чтобы операция ничего не делала в заданном направлении. Это особенно полезно для того, чтобы сделать операцию обратимой.

RunPython

class RunPython(code, reverse_code=None, atomic=None, hints=None, elidable=False)[исходный код]

Запускает собственный код Python в историческом контексте. codereverse_code, если он указан) должны быть вызываемыми объектами, которые принимают два аргумента; первый - это экземпляр django.apps.registry.Apps, содержащий исторические модели, которые соответствуют месту операции в истории проекта, а второй - экземпляр SchemaEditor.

Аргумент reverse_code вызывается, когда миграции не применяются. Этот вызываемый объект должен отменить то, что сделано в вызываемом code, чтобы миграция была обратимой. Если reverse_code равен None (по умолчанию), операция RunPython необратима.

Необязательный аргумент hints будет передан как **hints в метод allow_migrate() маршрутизаторов баз данных, чтобы помочь им принять решение о маршрутизации. Смотрите Подсказки для получения более подробной информации о подсказках базы данных.

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

Рекомендуется написать код как отдельную функцию над классом Migration в файле миграции и передать его в RunPython. Вот пример использования RunPython для создания некоторых начальных объектов в модели Country:

from django.db import migrations

def forwards_func(apps, schema_editor):
    # We get the model from the versioned app registry;
    # if we directly import it, it'll be the wrong version
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).bulk_create([
        Country(name="USA", code="us"),
        Country(name="France", code="fr"),
    ])

def reverse_func(apps, schema_editor):
    # forwards_func() creates two Country instances,
    # so reverse_func() should delete them.
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).filter(name="USA", code="us").delete()
    Country.objects.using(db_alias).filter(name="France", code="fr").delete()

class Migration(migrations.Migration):

    dependencies = []

    operations = [
        migrations.RunPython(forwards_func, reverse_func),
    ]

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

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

По умолчанию RunPython запускает свое содержимое внутри транзакции в базах данных, которые не поддерживают транзакции DDL (например, MySQL и Oracle). Это должно быть безопасно, но может вызвать сбой, если вы попытаетесь использовать schema_editor, предоставленный этими бэкэндами; в этом случае передайте atomic=False операции RunPython.

В базах данных, которые действительно поддерживают транзакции DDL (SQLite и PostgreSQL), операции RunPython не содержат автоматически добавляемых транзакций, кроме транзакций, созданных для каждой миграции. Таким образом, в PostgreSQL, например, вам следует избегать объединения изменений схемы и операций RunPython в одной миграции, иначе вы можете столкнуться с такими ошибками, как OperationalError: невозможно ALTER TABLE "mytable", потому что у него есть отложенные триггерные события.

Если у вас другая база данных и вы не уверены, поддерживает ли она транзакции DDL, проверьте атрибут django.db.connection.features.can_rollback_ddl.

Если операция RunPython является частью неатомарные миграции, операция будет выполняться в транзакции, только если atomic=True будет передано в RunPython.

Предупреждение

RunPython не меняет за вас соединение моделей волшебным образом; любые методы модели, которые вы вызываете, перейдут в базу данных по умолчанию, если вы не дадите им текущий псевдоним базы данных (доступен из schema_editor.connection.alias, где schema_editor - второй аргумент вашей функции).

static RunPython.noop()[исходный код]

Передайте метод RunPython.noop в code или reverse_code, если вы хотите, чтобы операция ничего не делала в заданном направлении. Это особенно полезно для того, чтобы сделать операцию обратимой.

SeparateDatabaseAndState

class SeparateDatabaseAndState(database_operations=None, state_operations=None)[исходный код]

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

Он принимает два списка операций. Когда его попросят применить состояние, он будет использовать список state_operations (это обобщенная версия аргумента state_operations в RunSQL). Когда вас попросят применить изменения к базе данных, он будет использовать список database_operations.

Если фактическое состояние базы данных и представление состояния Django не синхронизируются, это может нарушить структуру миграции и даже привести к потере данных. Стоит проявлять осторожность и тщательно проверять свою базу данных и операции состояния. Вы можете использовать sqlmigrate и dbshell для проверки операций вашей базы данных. Вы можете использовать makemigrations, особенно с --dry-run, чтобы проверить свои операции с состоянием.

Пример использования SeparateDatabaseAndState смотрите в разделе change-a-manytomanyfield-to-use-a-through-model.

Собственный код

Операции имеют относительно простой API, и они разработаны таким образом, чтобы вы могли легко написать свой собственный код, чтобы дополнить встроенный в Django. Базовая структура Operation выглядит так:

from django.db.migrations.operations.base import Operation

class MyCustomOperation(Operation):

    # If this is False, it means that this operation will be ignored by
    # sqlmigrate; if true, it will be run and the SQL collected for its output.
    reduces_to_sql = False

    # If this is False, Django will refuse to reverse past this operation.
    reversible = False

    def __init__(self, arg1, arg2):
        # Operations are usually instantiated with arguments in migration
        # files. Store the values of them on self for later use.
        pass

    def state_forwards(self, app_label, state):
        # The Operation should take the 'state' parameter (an instance of
        # django.db.migrations.state.ProjectState) and mutate it to match
        # any schema changes that have occurred.
        pass

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        # The Operation should use schema_editor to apply any changes it
        # wants to make to the database.
        pass

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        # If reversible is True, this is called when the operation is reversed.
        pass

    def describe(self):
        # This is used to describe what the operation does in console output.
        return "Custom Operation"

    @property
    def migration_name_fragment(self):
        # Optional. A filename part suitable for automatically naming a
        # migration containing this operation, or None if not applicable.
        return "custom_operation_%s_%s" % (self.arg1, self.arg2)
New in Django 3.2:

Добавлено свойство migration_name_fragment.

Вы можете взять этот шаблон и работать с ним, хотя мы предлагаем взглянуть на встроенные операции Django в django.db.migrations.operations - они охватывают множество примеров использования полувнутренних аспектов миграции фреймворка, такие как ProjectState, и шаблоны, используемые для получения исторических моделей, а также ModelState и шаблоны, используемые для изменения исторических моделей в state_forwards().

Несколько замечаний:

  • Вам не нужно слишком много знать о ProjectState, чтобы писать миграции; просто знайте, что у него есть свойство apps, которое дает доступ к реестру приложений (который вы затем можете вызвать с помощью get_model).

  • И database_forwards, и database_backwards получают два состояния, переданных им; они представляют разницу, которую применил бы метод state_forwards, но даны вам для удобства и скорости.

  • Если вы хотите работать с классами модели или экземплярами модели из аргумента from_state в database_forwards() или database_backwards(), вы должны отображать состояния модели с помощью clear_delayed_apps_cache() способ сделать связанные модели доступными:

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        # This operation should have access to all models. Ensure that all models are
        # reloaded in case any are delayed.
        from_state.clear_delayed_apps_cache()
        ...
    
  • to_state в методе database_backwards - это более старое состояние; то есть то, которое будет текущим состоянием после завершения реверсирования миграции.

  • Вы можете встретить реализации reference_model во встроенных операциях; это часть кода автоопределения и не имеет значения для пользовательских операций.

Предупреждение

По соображениям производительности экземпляры Field в ModelState.fields повторно используются при миграции. Вы никогда не должны изменять атрибуты этих экземпляров. Если вам нужно изменить поле в state_forwards(), вы должны удалить старый экземпляр из ModelState.fields и добавить на его место новый. То же самое верно для экземпляров Manager в ModelState.managers.

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

from django.db.migrations.operations.base import Operation

class LoadExtension(Operation):

    reversible = True

    def __init__(self, name):
        self.name = name

    def state_forwards(self, app_label, state):
        pass

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        schema_editor.execute("CREATE EXTENSION IF NOT EXISTS %s" % self.name)

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        schema_editor.execute("DROP EXTENSION %s" % self.name)

    def describe(self):
        return "Creates extension %s" % self.name

    @property
    def migration_name_fragment(self):
        return "create_extension_%s" % self.name
Вернуться на верх