Миграционные операции

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

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

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

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

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

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

Схема Операции

CreateModel

классCreateModel ( имя , поля , параметры = Нет , базы = Нет , менеджеры = Нет )

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

name- название модели, как будет написано в models.pyфайле.

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

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

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

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

DeleteModel

классDeleteModel ( имя )

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

RenameModel

классRenameModel ( old_name , new_name )

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

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

AlterModelTable

классAlterModelTable ( название , таблица )

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

AlterUniqueTogether

классAlterUniqueTogether ( имя , уникальное_всего )

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

AlterIndexTogether

классAlterIndexTogether ( имя , индекс_всего )

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

AlterOrderWithRespectTo

classAlterOrderWithRespectTo ( name , order_with_respect_to )

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

AlterModelOptions

классAlterModelOptions ( имя , параметры )

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

AlterModelManagers

классAlterModelManagers ( имя , менеджеры )

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

AddField

классAddField ( имя_модели , имя , поле , preserve_default = True )

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

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

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

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

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

RemoveField

classRemoveField ( имя_модели , имя )

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

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

AlterField

классAlterField ( имя_модели , имя , поле , preserve_default = True )

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

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

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

RenameField

классRenameField ( model_name , old_name , new_name )

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

AddIndex

classAddIndex ( имя_модели , индекс )

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

RemoveIndex

classRemoveIndex ( имя_модели , имя )

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

AddConstraint

classAddConstraint ( имя_модели , ограничение )

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

RemoveConstraint

classRemoveConstraint ( имя_модели , имя )

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

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

RunSQL

классRunSQL ( SQL , reverse_sql = None , state_operations = 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

КлассRunPython ( код , reverse_code = None , атомные = None , намеки = нет , elidable = False )

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

reverse_codeАргумент называется , когда unapplying миграции. Этот вызываемый объект должен отменить то, что сделано в 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- второй аргумент вашей функции).

staticRunPython.noop ()

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

SeparateDatabaseAndState

classSeparateDatabaseAndState ( database_operations = None , state_operations = None )

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

Он принимает два списка операций. Когда его попросили применить состояние, он будет использовать state_operationsсписок (это обобщенная версия RunSQL«S state_operationsаргумента). Когда вас попросят применить изменения к базе данных, он будет использовать 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 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 - более старое состояние; то есть тот, который будет текущим состоянием после завершения реверсирования миграции.

  • Вы можете увидеть реализации 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

Copyright ©2021 All rights reserved