迁移操作 — Django 文档
迁移操作
迁移文件由一个或多个 Operation
组成,这些对象声明性地记录迁移应该对您的数据库执行的操作。
Django 还使用这些 Operation
对象来计算您的模型在历史上的样子,并计算自上次迁移以来您对模型所做的更改,以便它可以自动编写您的迁移; 这就是为什么它们是声明性的,因为这意味着 Django 可以轻松地将它们全部加载到内存中并在不接触数据库的情况下运行它们来计算出您的项目应该是什么样子。
还有更专门的 Operation
对象,用于 数据迁移 和高级手动数据库操作。 如果您想封装通常所做的自定义更改,您还可以编写自己的 Operation
类。
如果您需要一个空的迁移文件来写入您自己的 Operation
对象,只需使用 python manage.py makemigrations --empty yourappname
,但请注意,手动添加模式更改操作可能会混淆迁移自动检测器并使结果运行 [ X225X]:djadmin:`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)
的 2 元组列表。 列表中的第一个管理器将是迁移期间此模型的默认管理器。
DeleteModel
- class DeleteModel(name)
从项目历史中删除模型,并从数据库中删除它的表。
RenameModel
- class RenameModel(old_name, new_name)
将模型从旧名称改名为新名称。
如果您一次更改模型的名称及其相当多的字段,则可能需要手动添加它; 对于自动检测器,这看起来就像您删除了具有旧名称的模型并添加了具有不同名称的新模型,并且它创建的迁移将丢失旧表中的所有数据。
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
上的设置)的更改,例如 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 实例(你会在 [X162X ] - 例如,models.IntegerField(null=True)
。
preserve_default
参数表示该字段的默认值是否是永久的并且应该被烘焙到项目状态 (True
),或者它是否是临时的并且仅用于此迁移 (False
) - 通常是因为迁移正在向表中添加不可为空的字段,并且需要将默认值放入现有行中。 它不会直接影响在数据库中设置默认值的行为 - Django 从不设置数据库默认值,而是始终将它们应用到 Django ORM 代码中。
警告
在较旧的数据库上,添加具有默认值的字段可能会导致表的完全重写。 即使对于可为空的字段也会发生这种情况,并且可能会对性能产生负面影响。 为避免这种情况,应采取以下步骤。
- 添加没有默认值的可空字段并运行 :djadmin:`makemigrations` 命令。 这应该会生成一个带有
AddField
操作的迁移。 - 将默认值添加到您的字段并运行 :djadmin:`makemigrations` 命令。 这应该会生成一个带有
AlterField
操作的迁移。
RemoveField
- class RemoveField(model_name, name)
从模型中删除一个字段。
请记住,当相反时,这实际上是向模型添加一个字段。 如果该字段可以为空或者它具有可用于填充重新创建的列的默认值,则该操作是可逆的(除了任何数据丢失,这当然是不可逆的)。 如果该字段不可为空且没有默认值,则该操作是不可逆的。
AlterField
- class AlterField(model_name, name, field, preserve_default=True)
更改字段的定义,包括更改其类型、null、unique、db_column 和其他字段属性。
preserve_default
参数表示该字段的默认值是否是永久的并且应该被烘焙到项目状态 (True
),或者它是否是临时的并且仅用于此迁移 (False
) - 通常是因为迁移将可空字段更改为不可空字段,并且需要将默认值放入现有行。 它不会直接影响在数据库中设置默认值的行为 - Django 从不设置数据库默认值,而是始终将它们应用到 Django ORM 代码中。
请注意,并非所有数据库都可以进行所有更改 - 例如,在大多数数据库上,您不能将 models.TextField()
之类的文本类型字段更改为 models.IntegerField()
之类的数字类型字段。
RemoveIndex
- class RemoveIndex(model_name, name)
从带有 model_name
的模型中删除名为 name
的索引。
AddConstraint
- class AddConstraint(model_name, constraint)
2.2 版中的新功能。
在数据库表中为具有 model_name
的模型创建 约束 。
RemoveConstraint
- class RemoveConstraint(model_name, name)
2.2 版中的新功能。
从具有 model_name
的模型中移除名为 name
的约束。
特殊操作
RunSQL
- class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)
允许在数据库上运行任意 SQL - 对于 Django 不直接支持的数据库后端的更高级功能非常有用,例如部分索引。
sql
和 reverse_sql
(如果提供)应该是在数据库上运行的 SQL 字符串。 在大多数数据库后端(除了 PostgreSQL 之外),Django 会在执行之前将 SQL 拆分为单独的语句。
您还可以传递字符串列表或 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
,它不会看到任何添加该字段的操作,因此将尝试再次运行它)。 例如:
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() 方法,以帮助它们做出路由决策。 有关数据库提示的更多详细信息,请参阅 Hints。
可选的 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
参数在取消应用迁移时被调用。 这个 callable 应该撤消在 code
callable 中所做的事情,以便迁移是可逆的。
可选的 hints
参数将作为 **hints
传递给数据库路由器的 allow_migrate() 方法,以帮助它们做出路由决策。 有关数据库提示的更多详细信息,请参阅 Hints。
可选的 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 代码的任何其他操作的操作。
如果您是从 South 升级,这基本上是作为操作的 South 模式 - 一种或两种向前和向后的方法,以及可用的 ORM 和模式操作。 大多数情况下,您应该能够将来自 South 的 orm.Model
或 orm["appname", "Model"]
引用直接转换为 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
方法传递给code
或reverse_code
。 这在使操作可逆时特别有用。
SeparateDatabaseAndState
- class SeparateDatabaseAndState(database_operations=None, state_operations=None)
一个高度专业化的操作,让你混合和匹配数据库(架构改变)和状态(自动检测器支持)方面的操作。
它接受两个操作列表,当被要求应用状态时将使用状态列表,当被要求对数据库应用更改时将使用数据库列表。 除非您非常确定自己知道自己在做什么,否则不要使用此操作。
自己写
操作有一个相对简单的 API,它们的设计使您可以轻松地编写自己的 API 来补充内置的 Django API。 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.db.migrations.operations
中的内置 Django 操作 - 它们易于阅读并涵盖了许多半内部方面的示例用法迁移框架,如 ProjectState
和用于获取历史模型的模式,以及 ModelState
和用于改变 state_forwards()
中的历史模型的模式。
注意事项:
你不需要学习太多
ProjectState
来编写简单的迁移; 只知道它有一个apps
属性,可以访问应用程序注册表(然后您可以调用get_model
)。database_forwards
和database_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
为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