Операции над миграциями¶
Файлы миграции состоят из одного или нескольких объектов 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 в историческом контексте. 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: невозможно 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)
Добавлено свойство 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