迁移操作
迁移文件由一个或多个 Operation
组成,这些对象声明性地记录了迁移对数据库的作用。
Django 也使用这些 Operation
对象来计算出你的模型在历史上的样子,并计算出自上次迁移以来你对模型做了哪些改变,这样它就可以自动写出你的迁移;这就是为什么它们是声明式的,因为这意味着 Django 可以很容易地将它们全部加载到内存中,并在不接触数据库的情况下运行它们,以计算出你的项目应该是什么样子。
还有更专业的 Operation
对象,用于诸如 数据迁移 和进阶的手动数据库操作。如果你想封装你经常进行的自定义更改,你也可以编写自己的 Operation
类。
如果你需要一个空的迁移文件来编写你自己的 Operation
对象,使用 python manage.py makemigrations --empty yourappname
,但是要注意手动添加架构变化的操作会混淆迁移自动检测器,使 makemigrations 的运行结果输出错误的代码。
所有的 Django 核心操作都可以在 django.db.migrations.options
模块中找到。
关于介绍性材料,见 迁移专题指南。
架构操作
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)
组成的二元元组的列表。在迁移过程中,列表中的第一个管理器将是这个模型的默认管理器。
DeleteModel
class DeleteModel
(name)
从项目历史中删除模型,并从数据库中删除它的表。
RenameModel
class RenameModel
(old_name, new_name)
将模型从旧名称改名为新名称。
如果你一次更改了模型的名称和相当多的字段,你可能必须手动添加这个;对自动检测器来说,这看起来就像你删除了一个使用旧名称的模型,并添加了一个使用不同名称的新模型,它创建的迁移将丢失旧表中的任何数据。
AlterModelTable
class AlterModelTable
(name, table)
更改模型的表名(Meta
子类上的 db_table 选项)。
AlterModelTableComment
New in Django 4.2.
class AlterModelTableComment
(name, table_comment)
Changes the model’s table comment (the db_table_comment option on the Meta
subclass).
AlterUniqueTogether
class AlterUniqueTogether
(name, unique_together)
改变模型的唯一约束集(Meta
子类上的 unique_together 选项)。
AlterIndexTogether
class AlterIndexTogether
(name, index_together)
Changes the model’s set of custom indexes (the index_together
option on the Meta
subclass).
警告
AlterIndexTogether
is officially supported only for pre-Django 4.2 migration files. For backward compatibility reasons, it’s still part of the public API, and there’s no plan to deprecate or remove it, but it should not be used for new migrations. Use AddIndex and RemoveIndex operations instead.
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
是一个未绑定的字段实例(你会在 models.py
中的字段声明中放入的东西——例如,models.IntegerField(null=True)
)。
preserve_default
参数表示字段的默认值是否是永久的,应该被内置到项目状态中(True
),或者它是否是临时的,只适用于这次迁移(False
)——通常是因为迁移在表中添加一个不可空的字段,需要一个默认值放到现有的行中。它不影响直接在数据库中设置默认值的行为——Django 从不设置数据库默认值,而总是在 Django ORM 代码中应用。
警告
在旧的数据库中,添加一个具有默认值的字段可能会导致表的完全重写。即使是对于可空字段,也会发生这种情况,可能会对性能产生负面影响。为了避免这种情况,应采取以下步骤。
- 添加可空字段,不含默认值,并运行 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()
这样的数字型字段。
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)
从带有 model_name
的模型中删除名为 name
的索引。
RenameIndex
class RenameIndex
(model_name, new_name, old_name=None, old_fields=None)
Renames an index in the database table for the model with model_name
. Exactly one of old_name
and old_fields
can be provided. old_fields
is an iterable of the strings, often corresponding to fields of index_together.
On databases that don’t support an index renaming statement (SQLite and MariaDB < 10.5.2), the operation will drop and recreate the index, which can be expensive.
AddConstraint
class AddConstraint
(model_name, constraint)
在数据库表中为带有 model_name
的模型创建一个 约束。
RemoveConstraint
class RemoveConstraint
(model_name, name)
从带有 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 语句之前将其分割成独立的语句。
警告
在 PostgreSQL 和 SQLite上,只有在 非原子性迁移 中的 SQL 中使用 BEGIN
或 COMMIT
,才能避免破坏 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
参数决定了当 压缩迁移 时,是否会删除(elided)该操作。
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.app.registry.Apps
的实例,包含与操作在项目历史中的位置相匹配的历史模型,第二个是 SchemaEditor 的实例。
reverse_code
参数在取消应用迁移时被调用。这个可调用对象参数应该撤销在 code
可调用对象参数中所做的事情,这样迁移才是可逆的。如果 reverse_code
是``None`` (默认),则 RunPython
操作是不可逆的。
可选的 hints
参数将作为 **hints
传递给数据库路由器的 allow_migrate() 方法,以帮助它们做出路由决策。参见 提示 了解更多关于数据库提示的细节。
可选的 elidable
参数决定了当 压缩迁移 时,是否会删除(elided)该操作。
建议你把这些代码写成一个单独的函数,放在迁移文件中的 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
是你函数的第二个参数)。
static RunPython.noop
()
当你希望操作在给定的方向上不做任何事情时,将 RunPython.noop
方法传递给 code
或 reverse_code
。这在使操作可逆时特别有用。
SeparateDatabaseAndState
class SeparateDatabaseAndState
(database_operations=None, state_operations=None)
一个高度专业化的操作,让你混合和匹配数据库(架构改变)和状态(自动检测器支持)方面的操作。
它接受两个操作列表。当要求它应用状态时,它将使用 state_operations
列表(这是 RunSQL 的 state_operations
参数的通用版本)。当要求它对数据库进行更改时,它将使用 database_operations
列表。
如果数据库的实际状态和 Django 的状态视图不同步,就会破坏迁移框架,甚至导致数据丢失。值得谨慎行事,仔细检查你的数据库和状态操作。你可以使用 sqlmigrate 和 dbshell 来检查你的数据库操作。你可以使用 makemigrations,特别是使用 --dry-run,来检查你的状态操作。
关于使用 SeparateDatabaseAndState
的例子,请参见 changing-a-manytomanyfield to use-a-through-model。
自己写
操作有一个相对简单的 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"
@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.options
中内置的 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
是指 旧 的状态;也就是反向迁移完成后将成为当前状态的状态。你可能会在内置操作上看到
references_model
的实现,这是自动检测代码的一部分,对自定义操作并不重要。
警告
由于性能原因,ModelState.fields.Field
中的 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