Операции миграции
Файлы миграции состоят из одного или нескольких Operations, объектов, которые декларативно записывают, что миграция должна сделать с вашей базой данных.
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 модели.
bases — необязательный список других классов, от которых эта модель должна наследоваться; он может содержать как объекты классов, так и строки в формате "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).
AlterModelTableComment
-
class AlterModelTableComment(name, table_comment)
Изменяет комментарий к таблице модели (опция db_table_comment в подклассе Meta).
AlterUniqueTogether
-
class AlterUniqueTogether(name, unique_together)
Изменяет набор уникальных ограничений модели (опция unique_together в подклассе Meta).
AlterIndexTogether
-
class AlterIndexTogether(name, index_together)
Изменяет набор пользовательских индексов модели (опция index_together в подклассе Meta).
Предупреждение
AlterIndexTogether официально поддерживается только для файлов миграции до Django 4.2. По причинам обратной совместимости он всё ещё входит в публичный API и не планируется его устаревать или удалять, но его не следует использовать для новых миграций. Используйте операции AddIndex и RemoveIndex вместо него.
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) — обычно, потому что миграция добавляет поле, не допускающее 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.
RenameIndex
-
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.2/ref/migration-operations/