Spec-Zone.ru › Django 4.1

Операции миграции

Файлы миграции состоят из одного или нескольких 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 — список пар 2-х элементов (field_name, field_instance). Экземпляр поля должен быть свободным полем (т.е. только models.CharField(...), а не поле, взятое из другой модели).

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

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

managers принимает список пар 2-х элементов (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 — экземпляр поля (то, что вы бы поставили в объявлении поля в models.py — например, models.IntegerField(null=True)).

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

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

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

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

RemoveField

class RemoveField(model_name, name)

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

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

AlterField

class AlterField(model_name, name, field, preserve_default=True)

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

Аргумент preserve_default указывает, является ли значение по умолчанию поля постоянным и должно быть включено в состояние проекта (True), или временным и только для этой миграции (False) — обычно потому, что миграция изменяет необязательное поле на обязательное и нуждается в значении по умолчанию для существующих строк. Это не влияет на поведение установки значений по умолчанию непосредственно в базе данных — 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.

RenameIndex

Новая в Django 4.1.
class RenameIndex(model_name, new_name, old_name=None, old_fields=None)

Переименовывает индекс в таблице базы данных для модели с model_name. Должен быть указан ровно один из old_name и old_fields. old_fields — это итерируемый объект строк, часто соответствующих полям index_together.

На базах данных, которые не поддерживают операцию переименования индексов (SQLite и MariaDB < 10.5.2), операция удалит и создаст индекс заново, что может быть затратно по времени.

AddConstraint

class AddConstraint(model_name, 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.

Также можно передать список строк или 2-кортежей. Последний используется для передачи запросов и параметров так же, как 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-код в историческом контексте. code (и reverse_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: cannot ALTER TABLE "mytable" because it has pending trigger events.

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

Написание собственных

Операции имеют относительно простой 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)

Вы можете использовать эту шаблонную структуру, однако мы рекомендуем изучить встроенные операции 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 – это более старое состояние, то есть то, которое станет текущим состоянием после завершения отката миграции.
  • Вы можете увидеть реализации references_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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/ref/migration-operations/

Spec-Zone.ru

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