Skip to main content

迁移操作

迁移文件由一个或多个 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) 的2元组列表。字段实例应该是未绑定字段(因此只是 models.CharField(...),而不是从另一个模型获取的字段)。

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

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

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

DeleteModel

class DeleteModel(name)[源代码]

从数据库中删除项目历史中的模型及其表。

RenameModel

class RenameModel(old_name, new_name)[源代码]

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

如果您立即更改模型的名称及其几个字段,则可能必须手动添加此字段;到自动检测器,这看起来像您删除了一个具有旧名称的模型,并添加了一个具有不同名称的模型,并且其创建的迁移将丢失旧表中的任何数据。

AlterModelTable

class AlterModelTable(name, table)[源代码]

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

AlterUniqueTogether

class AlterUniqueTogether(name, unique_together)[源代码]

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

AlterIndexTogether

class AlterIndexTogether(name, index_together)[源代码]

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

AlterOrderWithRespectTo

class AlterOrderWithRespectTo(name, order_with_respect_to)[源代码]

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

AlterModelOptions

class AlterModelOptions(name, options)[源代码]

存储对其他模型选项(模型的 Meta 上的设置)的更改,如 permissionsverbose_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代码。

RemoveField

class RemoveField(model_name, name)[源代码]

从模型中删除字段。

记住,当颠倒时,这实际上是一个字段添加到模型。该操作是可逆的(除了任何数据丢失,当然是不可逆的)如果字段是可空的或者如果它有一个默认值,可以用来填充重新创建的列。如果字段不可为空,并且没有默认值,则操作是不可逆的。

AlterField

class AlterField(model_name, name, field, preserve_default=True)[源代码]

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

preserve_default 参数指示字段的默认值是否是永久的,并且应该保存到项目状态(True)中,或者如果它是临时的,并且仅用于此迁移(False) - 通常是因为迁移正在将可空字段更改为非空字段,可空的一个,需要一个默认值来放入现有的行。它不影响在数据库中直接设置默认值的行为 - Django从不设置数据库默认值,并始终将它们应用于Django ORM代码。

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

RenameField

class RenameField(model_name, old_name, new_name)[源代码]

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

特别行动

RunSQL

class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)[源代码]

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

sqlreverse_sql (如果提供)应该是在数据库上运行的SQL字符串。在大多数数据库后端(除PostgreSQL之外),Django将在执行它们之前将SQL拆分为单独的语句。这需要安装 sqlparse Python库。

您还可以传递字符串或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 查询在迁移未应用时执行,因此您可以反转在转发查询中完成的更改:

migrations.RunSQL(
    [("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
    [("DELETE FROM musician where name=%s;", ['Reinhardt'])],
)

state_operations 参数是这样的,您可以提供与项目状态方面的SQL等效的操作;例如,如果您手动创建列,则应在此处传递包含 AddField 操作的列表,以便自动检测器仍然具有模型的最新状态(否则,当您下次运行 makemigrations 时, t看到任何添加该字段的操作,因此将尝试再次运行它)。例如:

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。这在使操作可逆时特别有用。

New in Django 1.10:

添加了 elidable 参数。

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 可调用中执行的操作,以便迁移是可逆的。

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

可选的 elidable 参数确定当 挤压迁移 时操作是否将被删除(省略)。

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

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models

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代码的任何其他操作。

如果你从South升级,这基本上是作为一个操作的南模式 - 一个或两个前进和后退的方法,可用的ORM和模式操作。大多数时候,您应该能够将 orm.Modelorm["appname", "Model"] 引用从South直接转换为 apps.get_model("appname", "Model") 引用,并使大部分代码保持不变,以便进行数据迁移。但是,apps 只会引用当前应用程序中的模型,除非将其他应用程序中的迁移添加到迁移的依赖关系中。

很像 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 方法传递到 codereverse_code。这在使操作可逆时特别有用。

New in Django 1.10:

添加了 elidable 参数。

Changed in Django 1.10:

atomic 参数默认值更改为 None,表示原子性由迁移的 atomic 属性控制。

SeparateDatabaseAndState

class SeparateDatabaseAndState(database_operations=None, state_operations=None)[源代码]

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

它接受两个操作列表,并且当被要求应用状态时将使用状态列表,并且当被要求对数据库应用更改时将使用数据库列表。不要使用此操作,除非你非常确定你知道你在做什么。

写你自己

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

你可以使用这个模板,并从中工作,虽然我们建议看看内置的Django操作在 django.db.migrations.operations - 它们很容易阅读,并涵盖了许多示例使用半内部方面的迁移框架,如 ProjectState 和用于获得历史模型的模式,以及 ModelState 和用于突变 state_forwards() 中的历史模型的模式。

有些事情要注意:

  • 你不需要学习太多关于 ProjectState 只写简单迁移;只是知道它有一个 apps 属性,提供访问应用程序注册表(你可以打电话给 get_model)。

  • database_forwardsdatabase_backwards 都获得两个状态传递给他们;这些只是代表 state_forwards 方法将应用的差异,但是为了方便和速度的原因给你。

  • to_state 中的database_backwards方法是 older 状态;即,一旦迁移已经完成反转,将是当前状态的状态。

  • 您可能会看到 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