迁移

迁移是 Django 将您对模型所做的更改(添加字段、删除模型等)传播到数据库模式的方式。它们的设计主要是自动的,但您需要知道何时进行迁移、何时运行迁移以及可能遇到的常见问题。

命令

有几个命令可用于与迁移和 Django 对数据库模式的处理进行交互

您应该将迁移视为数据库模式的版本控制系统。 makemigrations 负责将您的模型更改打包到单独的迁移文件中 - 类比于提交 - 并且 migrate 负责将它们应用到您的数据库。

每个应用程序的迁移文件都位于该应用程序内部的“migrations”目录中,旨在提交并作为其代码库的一部分进行分发。您应该在开发机器上进行一次迁移,然后在同事的机器、暂存机器以及最终的生产机器上运行相同的迁移。

注意

可以通过修改 MIGRATION_MODULES 设置,在每个应用程序的基础上覆盖包含迁移的包的名称。

迁移将在相同的数据集上以相同的方式运行并产生一致的结果,这意味着您在开发和暂存中看到的内容在相同情况下与生产中发生的情况完全相同。

Django 将为模型或字段的任何更改(即使是不影响数据库的选项)进行迁移,因为重建字段的唯一方法是拥有历史记录中的所有更改,并且您可能需要在以后的一些数据迁移中使用这些选项(例如,如果您设置了自定义验证器)。

后端支持

迁移在 Django 附带的所有后端以及任何第三方后端(如果它们已编程支持模式更改(通过 SchemaEditor 类完成))上受支持。

但是,在模式迁移方面,一些数据库比其他数据库更强大;下面介绍了一些注意事项。

PostgreSQL

在模式支持方面,PostgreSQL 是所有数据库中最强大的。

MySQL

MySQL 不支持围绕模式更改操作的事务,这意味着如果迁移无法应用,您将不得不手动撤消更改才能重试(无法回滚到更早的点)。

MySQL 8.0 为 DDL 操作 引入了显著的性能增强,使其更加高效,并减少了对完整表重建的需求。但是,它不能保证完全没有锁或中断。在仍然需要锁的情况下,这些操作的持续时间将与涉及的行数成正比。

最后,MySQL 对索引覆盖的所有列的组合大小有相对较小的限制。这意味着在其他后端上可能存在的索引在 MySQL 下将无法创建。

SQLite

SQLite 的内置模式更改支持非常少,因此 Django 尝试通过以下方式模拟它:

  • 使用新的模式创建新表
  • 复制数据
  • 删除旧表
  • 将新表重命名为与原始名称匹配

此过程通常运行良好,但速度可能很慢,偶尔也会出现错误。不建议您在生产环境中运行和迁移 SQLite,除非您非常了解其风险和局限性;Django 附带的支持旨在允许开发人员在本地机器上使用 SQLite 来开发不太复杂的 Django 项目,而无需完整的数据库。

工作流程

Django 可以为您创建迁移。对您的模型进行更改 - 例如,添加一个字段并删除一个模型 - 然后运行 makemigrations

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0003_auto.py:
    - Alter field author on book

您的模型将被扫描并与当前包含在迁移文件中的版本进行比较,然后将写入一组新的迁移。请务必阅读输出以查看 makemigrations 认为您进行了哪些更改 - 它并不完美,对于复杂的更改,它可能无法检测到您期望的内容。

获得新的迁移文件后,您应该将它们应用到数据库,以确保它们按预期工作

$ python manage.py migrate
Operations to perform:
  Apply all migrations: books
Running migrations:
  Rendering model states... DONE
  Applying books.0003_auto... OK

应用迁移后,将迁移和模型更改作为单个提交提交到您的版本控制系统 - 这样,当其他开发人员(或您的生产服务器)检出代码时,他们将同时获得模型更改和相应的迁移。

如果您想为迁移指定一个有意义的名称而不是生成的名称,可以使用 makemigrations --name 选项

$ python manage.py makemigrations --name changed_my_model your_app_label

版本控制

由于迁移存储在版本控制中,因此您偶尔会遇到您和其他开发人员同时将迁移提交到同一个应用程序的情况,从而导致两个迁移具有相同的编号。

不用担心 - 这些数字只是为了开发人员参考,Django 只关心每个迁移都有不同的名称。迁移在文件中指定了它们依赖于哪些其他迁移 - 包括同一个应用程序中的早期迁移 - 因此,可以检测到同一个应用程序的两个新迁移是否未按顺序排列。

发生这种情况时,Django 会提示您并提供一些选项。如果 Django 认为足够安全,它会提供自动线性化两个迁移的选项。如果不是,您需要自己修改迁移 - 不要担心,这并不困难,并在下面的 迁移文件 中有更详细的解释。

事务

在支持 DDL 事务的数据库(SQLite 和 PostgreSQL)上,默认情况下所有迁移操作都将在单个事务中运行。相反,如果数据库不支持 DDL 事务(例如 MySQL、Oracle),则所有操作都将在没有事务的情况下运行。

您可以通过将 atomic 属性设置为 False 来阻止迁移在事务中运行。例如

from django.db import migrations


class Migration(migrations.Migration):
    atomic = False

也可以使用 atomic() 或将 atomic=True 传递给 RunPython 在事务中执行迁移的一部分。有关更多详细信息,请参阅 非原子迁移

依赖项

虽然迁移是针对每个应用程序的,但模型中隐含的表和关系过于复杂,无法一次为一个应用程序创建。当您进行需要其他内容运行的迁移时 - 例如,您在 books 应用程序中添加了指向 authors 应用程序的 ForeignKey - 生成的迁移将包含对 authors 中迁移的依赖项。

这意味着当您运行迁移时,authors 迁移会首先运行并创建 ForeignKey 引用的表,然后创建 ForeignKey 列的迁移会在之后运行并创建约束。如果没有这样做,迁移将尝试在没有引用的表存在的情况下创建 ForeignKey 列,您的数据库将抛出错误。

这种依赖行为会影响大多数迁移操作,在这些操作中,您将迁移限制为单个应用程序。将迁移限制为单个应用程序(无论是在 makemigrations 还是 migrate 中)都是尽力而为的承诺,并非保证;任何其他需要用于正确获取依赖关系的应用程序都将被使用。

没有迁移的应用程序不能与具有迁移的应用程序建立关系(ForeignKeyManyToManyField 等)。有时它可能有效,但不受支持。

可替换依赖项

django.db.migrations.swappable_dependency(value)

在迁移中使用 swappable_dependency() 函数来声明对被替换模型所在应用程序中的迁移的“可替换”依赖关系,目前是在该应用程序的第一个迁移中。因此,被替换模型应该在初始迁移中创建。参数 value 是一个字符串 "<app label>.<model>",描述了应用程序标签和模型名称,例如 "myapp.MyModel"

通过使用 swappable_dependency(),您告知迁移框架该迁移依赖于另一个迁移,该迁移设置了可替换模型,允许将来用不同的实现替换该模型。这通常用于引用可能被自定义或替换的模型,例如自定义用户模型(settings.AUTH_USER_MODEL,默认值为 "auth.User")在 Django 的身份验证系统中。

迁移文件

迁移以磁盘格式存储,这里称为“迁移文件”。这些文件实际上是具有约定对象布局的普通 Python 文件,以声明式风格编写。

一个基本的迁移文件如下所示

from django.db import migrations, models


class Migration(migrations.Migration):
    dependencies = [("migrations", "0001_initial")]

    operations = [
        migrations.DeleteModel("Tribble"),
        migrations.AddField("Author", "rating", models.IntegerField(default=0)),
    ]

Django 在加载迁移文件(作为 Python 模块)时,会查找名为 Migrationdjango.db.migrations.Migration 子类。然后它检查此对象是否有四个属性,其中只有两个属性在大多数情况下使用

  • dependencies,此迁移依赖的迁移列表。
  • operations,定义此迁移执行操作的 Operation 类列表。

操作是关键;它们是一组声明式指令,告诉 Django 需要进行哪些模式更改。Django 会扫描它们并构建所有应用程序模式更改的内存表示,并使用它来生成执行模式更改的 SQL。

该内存结构还用于找出模型与迁移当前状态之间的差异;Django 按顺序运行所有更改,在一个内存模型集上,以得出上次运行 makemigrations 时模型的状态。然后它使用这些模型与 models.py 文件中的模型进行比较,以找出您所做的更改。

您很少需要手动编辑迁移文件,但如果您需要,完全可以手动编写它们。一些更复杂的操作无法自动检测,只能通过手动编写的迁移来实现,因此如果您需要编辑它们,不要害怕。

自定义字段

您不能在已迁移的自定义字段中修改位置参数的数量,否则会引发 TypeError。旧的迁移将使用旧的签名调用修改后的 __init__ 方法。因此,如果您需要新的参数,请创建一个关键字参数并在构造函数中添加类似 assert 'argument_name' in kwargs 的内容。

模型管理器

您可以选择将管理器序列化到迁移中,并在 RunPython 操作中使用它们。这可以通过在管理器类上定义 use_in_migrations 属性来完成

class MyManager(models.Manager):
    use_in_migrations = True


class MyModel(models.Model):
    objects = MyManager()

如果您使用 from_queryset() 函数动态生成管理器类,则需要从生成的类继承以使其可导入

class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True


class MyModel(models.Model):
    objects = MyManager()

请参考迁移中的关于历史模型的说明,了解随之而来的影响。

初始迁移

Migration.initial

应用程序的“初始迁移”是指创建该应用程序表的第一版的迁移。通常应用程序只有一个初始迁移,但在某些情况下,由于模型之间复杂的相互依赖关系,它可能有两个或多个初始迁移。

初始迁移在迁移类上用 initial = True 类属性标记。如果找不到 initial 类属性,则如果迁移是应用程序中的第一个迁移(即它不依赖于同一应用程序中的任何其他迁移),则该迁移将被视为“初始迁移”。

当使用migrate --fake-initial选项时,这些初始迁移将被特殊对待。对于创建了一个或多个表(CreateModel 操作)的初始迁移,Django 检查所有这些表是否已存在于数据库中,如果存在,则模拟应用迁移。类似地,对于添加了一个或多个字段(AddField 操作)的初始迁移,Django 检查所有相应的列是否已存在于数据库中,如果存在,则模拟应用迁移。如果没有 --fake-initial,初始迁移与任何其他迁移的处理方式相同。

历史一致性

如前所述,当两个开发分支合并时,您可能需要手动线性化迁移。在编辑迁移依赖项时,您可能会无意中创建一个不一致的历史状态,其中迁移已应用,但其某些依赖项尚未应用。这强烈表明依赖项不正确,因此 Django 将拒绝运行迁移或创建新迁移,直到它被修复。在使用多个数据库时,您可以使用allow_migrate() 方法的数据库路由器来控制哪些数据库makemigrations 检查一致的历史记录。

向应用程序添加迁移

新应用程序预先配置为接受迁移,因此您可以在进行一些更改后运行 makemigrations 来添加迁移。

如果您的应用程序已经拥有模型和数据库表,但还没有迁移(例如,您是在之前的 Django 版本中创建的),则需要通过运行以下命令将其转换为使用迁移:

$ python manage.py makemigrations your_app_label

这将为您的应用程序创建一个新的初始迁移。现在,运行 python manage.py migrate --fake-initial,Django 将检测到您有一个初始迁移,并且它想要创建的表已经存在,并将标记该迁移为已应用。(如果没有 migrate --fake-initial 标志,该命令将出错,因为要创建的表已经存在。)

请注意,这只有在以下两种情况下才有效:

  • 您在创建表后没有更改模型。为了使迁移正常工作,您必须首先进行初始迁移,然后进行更改,因为 Django 会将更改与迁移文件进行比较,而不是与数据库进行比较。
  • 您没有手动编辑数据库 - Django 将无法检测到您的数据库与您的模型不匹配,您只会遇到迁移尝试修改这些表时的错误。

反转迁移

可以通过传递上一个迁移的编号,使用 migrate 来反转迁移。例如,要反转迁移 books.0003

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK
...\> py manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK

如果要反转为应用程序应用的所有迁移,请使用名称 zero

$ python manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK
...\> py manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK

如果迁移包含任何不可逆操作,则该迁移将不可逆。尝试反转此类迁移将引发 IrreversibleError

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL  sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
...\> py manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL  sql='DROP TABLE demo_books'> in books.0003_auto is not reversible

历史模型

当您运行迁移时,Django 会从迁移文件中存储的模型的历史版本开始工作。如果您使用 RunPython 操作编写 Python 代码,或者您的数据库路由器上有 allow_migrate 方法,则您**需要使用**这些历史模型版本,而不是直接导入它们。

警告

如果您直接导入模型而不是使用历史模型,您的迁移可能最初会起作用,但在将来您尝试重新运行旧迁移时(通常是在您设置新安装并运行所有迁移以设置数据库时)会失败。

这意味着历史模型问题可能不会立即显现。如果您遇到此类故障,可以编辑迁移以使用历史模型而不是直接导入,并提交这些更改。

由于无法序列化任意 Python 代码,因此这些历史模型将不会包含您定义的任何自定义方法。但是,它们将具有相同的字段、关系、管理器(仅限于具有 use_in_migrations = True 的管理器)和 Meta 选项(也已版本化,因此它们可能与您当前的选项不同)。

警告

这意味着在迁移中访问对象时,您将不会调用自定义 save() 方法,并且您将不会拥有任何自定义构造函数或实例方法。请做好相应计划!

对字段选项中函数的引用,例如 upload_tolimit_choices_to,以及具有 use_in_migrations = True 的管理器的模型管理器声明,将在迁移中序列化,因此只要有迁移引用这些函数和类,就需要保留它们。任何 自定义模型字段 也需要保留,因为迁移会直接导入这些字段。

此外,模型的具体基类将作为指针存储,因此只要有迁移包含对它们的引用,就必须始终保留基类。从好的方面来说,这些基类的方法和管理器会正常继承,因此如果您绝对需要访问这些方法和管理器,可以选择将它们移到超类中。

要删除旧引用,您可以 压缩迁移,或者,如果引用不多,可以将它们复制到迁移文件中。

删除模型字段时的注意事项

与上一节中描述的“对历史函数的引用”注意事项类似,如果旧迁移中引用了自定义模型字段,从您的项目或第三方应用程序中删除它们会导致问题。

为了帮助解决这种情况,Django 提供了一些模型字段属性,以便使用 系统检查框架 协助模型字段弃用。

system_check_deprecated_details 属性添加到您的模型字段,类似于以下示例

class IPAddressField(Field):
    system_check_deprecated_details = {
        "msg": (
            "IPAddressField has been deprecated. Support for it (except "
            "in historical migrations) will be removed in Django 1.9."
        ),
        "hint": "Use GenericIPAddressField instead.",  # optional
        "id": "fields.W900",  # pick a unique ID for your field.
    }

在您选择的弃用期(对于 Django 本身中的字段,两个或三个功能版本)之后,将 system_check_deprecated_details 属性更改为 system_check_removed_details 并更新字典,类似于

class IPAddressField(Field):
    system_check_removed_details = {
        "msg": (
            "IPAddressField has been removed except for support in "
            "historical migrations."
        ),
        "hint": "Use GenericIPAddressField instead.",
        "id": "fields.E900",  # pick a unique ID for your field.
    }

您应该保留字段在数据库迁移中运行所需的方法,例如 __init__()deconstruct()get_internal_type()。只要存在引用该字段的任何迁移,就保留此存根字段。例如,在压缩迁移并删除旧迁移后,您应该能够完全删除该字段。

数据迁移

除了更改数据库模式之外,您还可以使用迁移来更改数据库本身的数据,如果您需要的话,可以与模式一起更改。

更改数据的迁移通常称为“数据迁移”;最好将它们编写为单独的迁移,与您的模式迁移并排放置。

Django 无法像处理模式迁移那样自动为您生成数据迁移,但编写它们并不难。Django 中的迁移文件由 操作 组成,您用于数据迁移的主要操作是 RunPython

首先,创建一个可以从中工作的空迁移文件(Django 会将文件放在正确的位置,建议一个名称,并为您添加依赖项)

python manage.py makemigrations --empty yourappname

然后,打开文件;它应该看起来像这样

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("yourappname", "0001_initial"),
    ]

    operations = []

现在,您需要做的就是创建一个新函数,并让 RunPython 使用它。 RunPython 期望一个可调用对象作为其参数,该对象接受两个参数 - 第一个是 应用程序注册表,其中加载了所有模型的历史版本以匹配迁移在历史记录中的位置,第二个是 模式编辑器,您可以使用它手动执行数据库模式更改(但请注意,这样做可能会混淆迁移自动检测器!)

让我们编写一个迁移,将新的 name 字段填充为 first_namelast_name 的组合值(我们已经回过神来,意识到并非每个人都有姓和名)。我们只需要使用历史模型并遍历行即可。

from django.db import migrations


def combine_names(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Person = apps.get_model("yourappname", "Person")
    for person in Person.objects.all():
        person.name = f"{person.first_name} {person.last_name}"
        person.save()


class Migration(migrations.Migration):
    dependencies = [
        ("yourappname", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(combine_names),
    ]

完成后,我们可以像往常一样运行 python manage.py migrate,数据迁移将在其他迁移旁边运行。

您可以向 RunPython 传递第二个可调用对象,以在向后迁移时运行您想要执行的任何逻辑。如果省略此可调用对象,则向后迁移将引发异常。

从其他应用程序访问模型

在编写使用除迁移所在的应用程序之外的应用程序中的模型的 RunPython 函数时,迁移的 dependencies 属性应包含每个相关应用程序的最新迁移,否则您可能会遇到类似于以下错误:LookupError: No installed app with label 'myappname',当您尝试使用 apps.get_model()RunPython 函数中检索模型时。

在以下示例中,我们在 app1 中有一个迁移,它需要使用 app2 中的模型。我们不关心 move_m1 的细节,除了它需要访问两个应用程序中的模型。因此,我们添加了一个依赖项,它指定了 app2 的最后一次迁移。

class Migration(migrations.Migration):
    dependencies = [
        ("app1", "0001_initial"),
        # added dependency to enable using models from app2 in move_m1
        ("app2", "0004_foobar"),
    ]

    operations = [
        migrations.RunPython(move_m1),
    ]

更高级的迁移

如果您对更高级的迁移操作感兴趣,或者希望能够编写自己的操作,请参阅 迁移操作参考 和关于 编写迁移 的“操作指南”。

压缩迁移

鼓励您自由地进行迁移,不必担心迁移的次数;迁移代码经过优化,可以一次处理数百次迁移而不会造成明显的减速。但是,最终您会希望将数百次迁移减少到几次,这就是压缩的作用。

压缩是指将一组现有的多个迁移压缩成一个(有时是几个)迁移,这些迁移仍然代表相同的更改。

Django 通过获取所有现有的迁移,提取它们的 Operation 并按顺序排列,然后运行优化器来尝试缩短列表长度 - 例如,它知道 CreateModelDeleteModel 相互抵消,并且它知道 AddField 可以合并到 CreateModel 中。

一旦操作序列尽可能地缩短 - 可缩短的程度取决于模型之间的紧密程度以及是否存在任何 RunSQLRunPython 操作(除非标记为 elidable,否则无法优化) - Django 然后会将其写回一组新的迁移文件。

这些文件被标记为表明它们替换了先前压缩的迁移,因此它们可以与旧的迁移文件共存,并且 Django 会根据您在历史记录中的位置智能地在它们之间切换。如果您仍然处于压缩的迁移集的中间,它将继续使用它们,直到到达末尾,然后切换到压缩的历史记录,而新安装将使用新的压缩迁移并跳过所有旧的迁移。

这使您能够压缩而不会弄乱当前正在生产中但尚未完全更新的系统。推荐的流程是压缩,保留旧文件,提交并发布,等待所有系统升级到新版本(或者如果您是第三方项目,请确保您的用户按顺序升级版本,不要跳过任何版本),然后删除旧文件,提交并进行第二次发布。

支持所有这些操作的命令是 squashmigrations - 将要压缩到的应用程序标签和迁移名称传递给它,它将开始工作。

$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [yN] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.

如果您想设置压缩迁移的名称,而不是使用自动生成的名称,请使用 squashmigrations --squashed-name 选项。

请注意,Django 中的模型相互依赖关系可能非常复杂,压缩可能会导致迁移无法运行;要么是优化不当(在这种情况下,您可以尝试使用 --no-optimize 再次尝试,但您也应该报告问题),要么是出现 CircularDependencyError,在这种情况下,您可以手动解决它。

要手动解决 CircularDependencyError,请将循环依赖关系链中的一个 ForeignKey 分解到单独的迁移中,并将对另一个应用程序的依赖关系与其一起移动。如果您不确定,请查看 makemigrations 在被要求从您的模型创建全新的迁移时如何处理这个问题。在 Django 的未来版本中,squashmigrations 将更新为尝试自行解决这些错误。

压缩迁移后,您应该将它与它替换的迁移一起提交,并将此更改分发到应用程序的所有运行实例,确保它们运行 migrate 以将更改存储到其数据库中。

然后,您必须将压缩的迁移转换为正常的迁移,方法是:

  • 删除它替换的所有迁移文件。
  • 更新所有依赖于已删除迁移的迁移,使其改为依赖于压缩的迁移。
  • 从压缩迁移的 Migration 类中删除 replaces 属性(这是 Django 如何判断它是一个压缩迁移的方式)。

注意

压缩迁移后,您不应该在将它完全转换为正常迁移之前再次压缩该压缩迁移。

修剪对已删除迁移的引用

如果您可能在将来重新使用已删除迁移的名称,则应使用 migrate --prune 选项从 Django 的迁移表中删除对它的引用。

序列化值

迁移是包含模型旧定义的 Python 文件 - 因此,为了编写它们,Django 必须获取模型的当前状态并将它们序列化到文件中。

虽然 Django 可以序列化大多数内容,但有些内容无法序列化为有效的 Python 表示形式 - Python 标准中没有规定如何将值转换回代码(repr() 仅适用于基本值,并且没有指定导入路径)。

Django 可以序列化以下内容:

  • intfloatboolstrbytesNoneNoneType
  • listsettupledictrange
  • datetime.datedatetime.timedatetime.datetime 实例(包括时区感知的实例)
  • decimal.Decimal 实例
  • enum.Enumenum.Flag 实例
  • uuid.UUID 实例
  • functools.partial()functools.partialmethod 实例,这些实例具有可序列化的 funcargskeywords 值。
  • 来自 pathlib 的纯路径和具体路径对象。具体路径将转换为其纯路径等效项,例如 pathlib.PosixPath 转换为 pathlib.PurePosixPath
  • os.PathLike 实例,例如 os.DirEntry,它们使用 os.fspath() 转换为 strbytes
  • LazyObject 实例,它们包装可序列化值。
  • 枚举类型(例如 TextChoicesIntegerChoices)实例。
  • 任何 Django 字段
  • 任何函数或方法引用(例如 datetime.datetime.today)(必须位于模块的顶层范围)
  • 从类体内部使用的未绑定方法
  • 任何类引用(必须位于模块的顶层范围)
  • 任何具有自定义 deconstruct() 方法的项(参见下文
在 Django 4.2 中更改

添加了对 enum.Flag 的序列化支持。

Django 5.0 中的变更

添加了对使用 functools.cache()functools.lru_cache() 装饰的函数的序列化支持。

Django 无法序列化

  • 嵌套类
  • 任意类实例(例如 MyClass(4.3, 5.7)
  • Lambda 表达式

自定义序列化器

您可以通过编写自定义序列化器来序列化其他类型。例如,如果 Django 默认情况下不序列化 Decimal,您可以这样做

from decimal import Decimal

from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter


class DecimalSerializer(BaseSerializer):
    def serialize(self):
        return repr(self.value), {"from decimal import Decimal"}


MigrationWriter.register_serializer(Decimal, DecimalSerializer)

MigrationWriter.register_serializer() 的第一个参数是应该使用序列化器的类型或类型的可迭代对象。

序列化器的 serialize() 方法必须返回一个字符串,该字符串表示值在迁移中应如何显示,以及迁移中所需的任何导入的集合。

添加 deconstruct() 方法

您可以通过为类提供一个 deconstruct() 方法来让 Django 序列化您自己的自定义类实例。它不接受任何参数,并且应该返回一个包含三个元素的元组 (path, args, kwargs)

  • path 应该是类在 Python 中的路径,包括类名作为最后一个部分(例如,myapp.custom_things.MyClass)。如果您的类在模块的顶层不可用,则它不可序列化。
  • args 应该是一个传递给类 __init__ 方法的位置参数列表。此列表中的所有内容本身都应该是可序列化的。
  • kwargs 应该是一个字典,包含传递给你的类 __init__ 方法的关键字参数。每个值本身都应该是可序列化的。

注意

此返回值与 deconstruct() 方法不同 对于自定义字段,它返回一个包含四个项目的元组。

Django 会将该值写入为使用给定参数实例化你的类,类似于它写入对 Django 字段的引用的方式。

为了防止每次运行 makemigrations 时都创建新的迁移,你应该在装饰的类中添加一个 __eq__() 方法。Django 的迁移框架将调用此函数来检测状态之间的更改。

只要你的类构造函数的所有参数本身都是可序列化的,你就可以使用来自 django.utils.deconstruct@deconstructible 类装饰器来添加 deconstruct() 方法。

from django.utils.deconstruct import deconstructible


@deconstructible
class MyCustomClass:
    def __init__(self, foo=1):
        self.foo = foo
        ...

    def __eq__(self, other):
        return self.foo == other.foo

装饰器添加逻辑来捕获和保留进入构造函数的参数,然后在调用 deconstruct() 时准确地返回这些参数。

支持多个 Django 版本

如果你维护一个带有模型的第三方应用程序,你可能需要发布支持多个 Django 版本的迁移。在这种情况下,你应该始终运行 makemigrations **使用你想要支持的最低 Django 版本**。

迁移系统将根据与 Django 其他部分相同的策略维护向后兼容性,因此在 Django X.Y 上生成的迁移文件应该在 Django X.Y+1 上不变地运行。但是,迁移系统不承诺向前兼容性。可能会添加新功能,并且使用较新版本的 Django 生成的迁移文件可能无法在较旧版本上运行。

另请参阅

迁移操作参考
涵盖架构操作 API、特殊操作以及编写自己的操作。
编写迁移“操作指南”
解释如何为可能遇到的不同场景构建和编写数据库迁移。
返回顶部