本文档解释了如何为可能遇到的不同场景构建和编写数据库迁移。有关迁移的介绍性材料,请参见 the topic guide .
使用多个数据库时,您可能需要确定是否针对特定数据库运行迁移。例如,您可能希望 only 在特定数据库上运行迁移。
为此,可以检查数据库连接的别名 RunPython
通过查看 schema_editor.connection.alias
属性:
from django.db import migrations
def forwards(apps, schema_editor):
if schema_editor.connection.alias != "default":
return
# Your migration code goes here
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards),
]
您还可以提供将传递给 allow_migrate()
数据库路由器的方法 **hints
:
class MyRouter:
def allow_migrate(self, db, app_label, model_name=None, **hints):
if "target_db" in hints:
return db == hints["target_db"]
return True
然后,要在迁移中利用此功能,请执行以下操作:
from django.db import migrations
def forwards(apps, schema_editor):
# Your migration code goes here
...
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards, hints={"target_db": "default"}),
]
如果你 RunPython
或 RunSQL
操作只影响一个模型,通过是很好的做法 model_name
作为一个提示,使它对路由器尽可能透明。这对于可重用和第三方应用程序尤其重要。
应用将唯一不可为空字段添加到具有现有行的表中的“普通”迁移将引发错误,因为用于填充现有行的值只生成一次,因此会破坏唯一约束。
因此,应采取以下步骤。在这个示例中,我们将添加一个不可为空的 UUIDField
使用默认值。根据需要修改相应的字段。
在模型上添加字段 default=uuid.uuid4
和 unique=True
参数(为要添加的字段类型选择适当的默认值)。
运行 makemigrations
命令。这将生成一个带有 AddField
操作。
通过运行为同一应用程序生成两个空迁移文件 makemigrations myapp --empty
两次。在下面的示例中,我们已经重命名了迁移文件,以赋予它们有意义的名称。
复制 AddField
从自动生成的迁移(三个新文件中的第一个)到最后一个迁移的操作,更改 AddField
到 AlterField
,并添加 uuid
和 models
. 例如:
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
("myapp", "0005_populate_uuid_values"),
]
operations = [
migrations.AlterField(
model_name="mymodel",
name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
编辑第一个迁移文件。生成的迁移类应该类似于:
class Migration(migrations.Migration):
dependencies = [
("myapp", "0003_auto_20150129_1705"),
]
operations = [
migrations.AddField(
model_name="mymodel",
name="uuid",
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
变化 unique=True
到 null=True
--这将创建中间空字段并推迟创建唯一约束,直到我们在所有行上填充了唯一值。
在第一个空迁移文件中,添加 RunPython
或 RunSQL
为每个现有行生成唯一值(示例中为UUID)的操作。同时添加导入 uuid
. 例如:
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
import uuid
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model("myapp", "MyModel")
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=["uuid"])
class Migration(migrations.Migration):
dependencies = [
("myapp", "0004_add_uuid_field"),
]
operations = [
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
]
现在,您可以像往常一样使用 migrate
命令。
注意,如果允许在迁移运行时创建对象,则存在争用条件。在 AddField
以前 RunPython
会有他们的原件 uuid
的覆盖。
在支持DDL事务(sqlite和postgresql)的数据库上,默认情况下迁移将在事务内部运行。对于在大型表上执行数据迁移等用例,您可能希望通过设置 atomic
属性到 False
::
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
在这种迁移中,所有操作都是在没有事务的情况下运行的。可以在事务内部使用 atomic()
或通过 atomic=True
到 RunPython
.
下面是一个非原子数据迁移的示例,它以较小的批处理更新一个大表:
import uuid
from django.db import migrations, transaction
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model("myapp", "MyModel")
while MyModel.objects.filter(uuid__isnull=True).exists():
with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4()
row.save()
class Migration(migrations.Migration):
atomic = False
operations = [
migrations.RunPython(gen_uuid),
]
这个 atomic
属性对不支持DDL事务的数据库(如MySQL、Oracle)没有影响。(MySQL的 atomic DDL statement support 引用单个语句,而不是在可以回滚的事务中包装的多个语句。)
Django确定应用迁移的顺序,而不是根据每个迁移的文件名,而是使用 Migration
类: dependencies
和 run_before
.
如果你使用了 makemigrations
你可能已经看到的命令 dependencies
因为自动创建的迁移将其定义为创建过程的一部分。
这个 dependencies
属性声明如下:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("myapp", "0123_the_previous_migration"),
]
通常这就足够了,但有时您可能需要确保迁移运行 之前 其他迁移。例如,这对于运行第三方应用程序的迁移非常有用。 之后 你的 AUTH_USER_MODEL
替换。
要实现这一点,请将所有应依赖于您的迁移放置在 run_before
你的属性 Migration
类:
class Migration(migrations.Migration):
...
run_before = [
("third_party_app", "0001_do_awesome"),
]
喜欢使用 dependencies
结束 run_before
如果可能的话。你应该只使用 run_before
如果不希望或不切实际地指定 dependencies
在您要在写入的迁移之后运行的迁移中。
您可以使用数据迁移将数据从一个第三方应用程序移动到另一个应用程序。
如果您计划稍后删除旧应用程序,则需要设置 dependencies
基于是否安装旧应用的属性。否则,一旦卸载旧应用程序,就会丢失依赖项。同样,你也需要抓住 LookupError
在 apps.get_model()
从旧应用程序中检索模型的调用。这种方法允许您在任何地方部署项目,而无需先安装然后卸载旧应用程序。
以下是迁移示例:
from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
OldModel = apps.get_model("old_app", "OldModel")
except LookupError:
# The old app isn't installed.
return
NewModel = apps.get_model("new_app", "NewModel")
NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all()
)
class Migration(migrations.Migration):
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]
dependencies = [
("myapp", "0123_the_previous_migration"),
("new_app", "0001_initial"),
]
if global_apps.is_installed("old_app"):
dependencies.append(("old_app", "0001_initial"))
还要考虑当迁移未应用时您希望发生什么。您可以不做任何事情(如上面的示例所示),也可以从新应用程序中删除部分或全部数据。调整的第二个参数 RunPython
相应操作。
ManyToManyField
使用A through
模型¶如果你改变 ManyToManyField
使用A through
模型时,默认迁移将删除现有表并创建新表,丢失现有关系。为了避免这种情况,可以使用 SeparateDatabaseAndState
将现有表重命名为新表名,同时通知迁移自动检测器新模型已创建。您可以通过检查现有表名 sqlmigrate
或 dbshell
. 您可以使用through模型的 _meta.db_table
财产。你的新产品 through
模型应使用相同的名称 ForeignKey
就像Django做的那样。另外,如果它需要任何额外的字段,它们应该添加到操作之后 SeparateDatabaseAndState
.
例如,如果我们有一个 Book
模型与A ManyToManyField
链接到 Author
,我们可以添加一个穿透模型 AuthorBook
有了新的领域 is_primary
,像这样::
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
# Old table name from checking with sqlmigrate, new table
# name from AuthorBook._meta.db_table.
migrations.RunSQL(
sql="ALTER TABLE core_book_authors RENAME TO core_authorbook",
reverse_sql="ALTER TABLE core_authorbook RENAME TO core_book_authors",
),
],
state_operations=[
migrations.CreateModel(
name="AuthorBook",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to="core.Author",
),
),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to="core.Book",
),
),
],
),
migrations.AlterField(
model_name="book",
name="authors",
field=models.ManyToManyField(
to="core.Author",
through="core.AuthorBook",
),
),
],
),
migrations.AddField(
model_name="authorbook",
name="is_primary",
field=models.BooleanField(default=False),
),
]
如果要更改非托管模型 (managed=False
)要管理,必须删除 managed=False
并在对模型进行其他与架构相关的更改之前生成迁移,因为在包含要更改的操作的迁移中出现的架构更改 Meta.managed
可能不适用。
12月 18, 2023