迁移操作

迁移文件由一个或多个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)[source]

在项目历史记录中创建一个新的模型,并在数据库中创建一个相应的表与之匹配。

name 是模型名称,就像在models.py文件中编写的那样。

fields(field_name, field_instance)的 2 元组列表。字段实例应该是未绑定的字段(因此只是models.CharField(...),而不是从另一个模型中获取的字段)。

options 是模型Meta类中值的可选字典。

bases 是此模型要继承的其他类的可选列表;如果要依赖另一个模型(因此您继承自历史版本),它可以包含类对象和格式为"appname.ModelName"的字符串。如果未提供,则默认为继承自标准models.Model

managers 获取(manager_name, manager_instance)的 2 元组列表。列表中的第一个管理器将是迁移期间此模型的默认管理器。

DeleteModel

class DeleteModel(name)[source]

从项目历史记录中删除模型及其表。

RenameModel

class RenameModel(old_name, new_name)[source]

将模型从旧名称重命名为新名称。

如果您同时更改了模型的名称和相当多的字段,您可能需要手动添加此操作;对于自动检测器来说,这看起来像是您删除了一个旧名称的模型并添加了一个不同名称的新模型,它创建的迁移将丢失旧表中的任何数据。

AlterModelTable

class AlterModelTable(name, table)[source]

更改模型的表名(Meta子类上的db_table选项)。

AlterModelTableComment

class AlterModelTableComment(name, table_comment)[source]

更改模型的表注释(Meta子类上的db_table_comment选项)。

AlterUniqueTogether

class AlterUniqueTogether(name, unique_together)[source]

更改模型的唯一约束集(Meta子类上的unique_together选项)。

AlterIndexTogether

class AlterIndexTogether(name, index_together)[source]

更改模型的自定义索引集(Meta子类上的index_together选项)。

警告

AlterIndexTogether仅正式支持 Django 4.2 之前的迁移文件。出于向后兼容性的原因,它仍然是公共 API 的一部分,并且没有计划弃用或删除它,但它不应用于新的迁移。请改用AddIndexRemoveIndex操作。

AlterOrderWithRespectTo

class AlterOrderWithRespectTo(name, order_with_respect_to)[source]

创建或删除Meta子类上的order_with_respect_to选项所需的_order列。

AlterModelOptions

class AlterModelOptions(name, options)[source]

存储对各种模型选项(模型的Meta中的设置)的更改,例如permissionsverbose_name。不会影响数据库,但会将这些更改保留下来,以便RunPython实例使用。options应为一个字典,将选项名称映射到值。

AlterModelManagers

class AlterModelManagers(name, managers)[source]

更改迁移期间可用的管理器。

AddField

class AddField(model_name, name, field, preserve_default=True)[source]

向模型添加字段。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)[source]

从模型中删除字段。

请记住,当反转时,这实际上是在向模型添加字段。如果字段可为空,或者它具有可用于填充重新创建的列的默认值,则该操作是可逆的(除了任何数据丢失,这是不可逆的)。如果字段不可为空且没有默认值,则该操作是不可逆的。

PostgreSQL

RemoveField还会删除与已删除字段相关的任何其他数据库对象(例如视图)。这是因为生成的DROP COLUMN语句将包含CASCADE子句,以确保表外的相关对象也会被删除

AlterField

class AlterField(model_name, name, field, preserve_default=True)[source]

更改字段的定义,包括对其类型、nulluniquedb_column和其他字段属性的更改。

preserve_default参数指示字段的默认值是否永久有效,应添加到项目状态中(True),还是只是针对此迁移的临时值(False)——通常是因为迁移正在将可为空的字段更改为不可为空的字段,并且需要一个默认值才能放入现有行中。它不会影响直接在数据库中设置默认值的行为——Django 从不设置数据库默认值,而始终在 Django ORM 代码中应用它们。

请注意,并非所有更改都可以在所有数据库上进行——例如,在大多数数据库上,您无法将文本类型字段(如models.TextField())更改为数字类型字段(如models.IntegerField())。

RenameField

class RenameField(model_name, old_name, new_name)[source]

更改字段的名称(并且,除非设置了db_column,否则还更改其列名)。

AddIndex

class AddIndex(model_name, index)[source]

在模型的数据库表中创建索引,模型名为model_nameindexIndex类的实例。

RemoveIndex

class RemoveIndex(model_name, name)[source]

从模型中删除名为name的索引,模型名为model_name

RenameIndex

class RenameIndex(model_name, new_name, old_name=None, old_fields=None)[source]

重命名 model_name 模型对应数据库表中的索引。 必须且只能提供 old_nameold_fields 中的一个。 old_fields 是一个字符串迭代器,通常对应于 index_together (Django 5.1 之前的选项) 中的字段。

在不支持索引重命名语句的数据库(SQLite 和 MariaDB < 10.5.2)上,此操作将删除并重新创建索引,这可能代价很高。

AddConstraint

class AddConstraint(model_name, constraint)[source]

model_name 模型对应的数据库表中创建一个 约束

RemoveConstraint

class RemoveConstraint(model_name, name)[source]

model_name 模型中删除名为 name 的约束。

特殊操作

RunSQL

class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)[source]

允许在数据库上运行任意 SQL - 这对于 Django 不直接支持的数据库后端的更高级功能非常有用。

sql 和(如果提供)reverse_sql 应是在数据库上运行的 SQL 字符串。 在大多数数据库后端(除 PostgreSQL 外的所有后端),Django 都会在执行之前将 SQL 分成单个语句。

警告

在 PostgreSQL 和 SQLite 上,仅在 非原子迁移 中的 SQL 中使用 BEGINCOMMIT,以避免破坏 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_sqlNone(默认值),则 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 属性传递给 sqlreverse_sql。这在使操作可逆方面特别有用。

RunPython

class RunPython(code, reverse_code=None, atomic=None, hints=None, elidable=False)[source]

在历史上下文中运行自定义 Python 代码。code(如果提供则为 reverse_code)应为可调用对象,它们接受两个参数;第一个是 django.apps.registry.Apps 的实例,其中包含与操作在项目历史记录中的位置匹配的历史模型,第二个是 SchemaEditor 的实例。

撤消迁移时会调用 reverse_code 参数。此可调用对象应撤消 code 可调用对象中所做的操作,以便迁移可逆。如果 reverse_codeNone(默认值),则 RunPython 操作是不可逆的。

可选的 hints 参数将作为 **hints 传递给数据库路由器的 allow_migrate() 方法,以帮助它们做出路由决策。有关数据库提示的更多详细信息,请参见 提示

可选的 elidable 参数决定在 压缩迁移 时是否会删除(消除)该操作。

建议您在迁移文件中的 Migration 类上方编写代码作为单独的函数,并将其传递给 RunPython。这是一个使用 RunPythonCountry 模型上创建一些初始对象的示例。

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 操作是 非原子迁移 的一部分,则只有在向 RunPython 操作传递 atomic=True 时,才会在事务中执行该操作。

警告

RunPython 不会神奇地更改模型的连接;您调用的任何模型方法都会转到默认数据库,除非您为它们提供当前的数据库别名(可从 schema_editor.connection.alias 获取,其中 schema_editor 是函数的第二个参数)。

static RunPython.noop()[source]

当您希望操作在给定方向上不执行任何操作时,将 RunPython.noop 方法传递给 codereverse_code。这在使操作可逆时特别有用。

SeparateDatabaseAndState

class SeparateDatabaseAndState(database_operations=None, state_operations=None)[source]

一个高度专业化的操作,允许您混合和匹配操作的数据库(模式更改)和状态(自动检测供电)方面。

它接受两个操作列表。当需要应用状态时,它将使用 state_operations 列表(这是 RunSQLstate_operations 参数的通用版本)。当需要将更改应用到数据库时,它将使用 database_operations 列表。

如果数据库的实际状态和 Django 对状态的视图不同步,这可能会破坏迁移框架,甚至可能导致数据丢失。值得谨慎行事,仔细检查您的数据库和状态操作。您可以使用 sqlmigratedbshell 检查您的数据库操作。您可以使用 makemigrations,尤其是在使用 --dry-run 时,检查您的状态操作。

有关使用 SeparateDatabaseAndState 的示例,请参阅 将 ManyToManyField 更改为使用 through 模型

操作类别

Django 5.1 新功能。
class OperationCategory[source]

makemigrations 命令使用的迁移操作类别,用于显示有意义的符号。

ADDITION

符号+

REMOVAL

符号-

ALTERATION

符号~

PYTHON

符号p

SQL

符号s

MIXED

符号?

编写您自己的

操作具有相对简单的 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

    # This categorizes the operation. The corresponding symbol will be
    # displayed by the makemigrations command.
    category = OperationCategory.ADDITION

    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.
        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.db.migrations.operations 中的内置 Django 操作 - 它们涵盖了迁移框架许多半内部方面的示例用法,例如 ProjectState 和用于获取历史模型的模式,以及 ModelState 和用于在 state_forwards() 中变异历史模型的模式。

需要注意的一些事项

  • 您不需要学习太多关于 ProjectState 的知识来编写迁移;只需知道它具有一个 apps 属性,该属性可以访问应用程序注册表(然后您可以对其调用 get_model)。

  • database_forwardsdatabase_backwards 都接收两个传递给它们的状态;这些表示 state_forwards 方法将应用的差异,但出于方便和速度的原因而提供给您。

  • 如果您想使用 database_forwards()database_backwards() 中的 from_state 参数中的模型类或模型实例,则必须使用 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()
        ...
    
  • database_backwards 方法中的 to_state较旧的状态;也就是说,一旦迁移完成反转,它将成为当前状态。

  • 您可能会在内置操作中看到 references_model 的实现;这是自动检测代码的一部分,对于自定义操作并不重要。

警告

出于性能原因,ModelState.fields 中的 Field 实例在迁移中被重用。您绝不能更改这些实例上的属性。如果您需要在 state_forwards() 中变异字段,则必须从 ModelState.fields 中删除旧实例,并在其位置添加新实例。ModelState.managers 中的 Manager 实例也是如此。

例如,让我们创建一个加载 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
返回顶部