迁移¶
迁移是 Django 将您对模型所做的更改(添加字段、删除模型等)传播到数据库模式的方式。它们的设计主要是自动化的,但是您需要知道何时进行迁移,何时运行迁移以及可能遇到的常见问题。
命令¶
有几个命令可用于与迁移和 Django 对数据库模式的处理进行交互。
migrate,负责应用和取消应用迁移。makemigrations,负责根据您对模型所做的更改创建新的迁移。sqlmigrate,显示迁移的 SQL 语句。showmigrations,列出项目的迁移及其状态。
您应该将迁移视为数据库模式的版本控制系统。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 将提示您并提供一些选项。如果它认为足够安全,它将主动为您线性化这两个迁移。如果不是,您将必须自己修改迁移——不用担心,这并不困难,在下面的 迁移文件 中有更详细的解释。
事务¶
在支持 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 中)是一个尽力而为的承诺,而不是保证;任何其他需要用于获取正确依赖项的应用都将被使用。
没有迁移的应用不得与有迁移的应用存在关系(ForeignKey、ManyToManyField 等)。有时它可能有效,但不支持。
可交换依赖项¶
- django.db.migrations.swappable_dependency(value)¶
swappable_dependency() 函数用于迁移中声明对已交换模型的应用中的迁移的“可交换”依赖项,当前在该应用的第一个迁移上。因此,应在此初始迁移中创建已交换的模型。参数 value 是一个字符串 "<app label>.<model>",描述应用标签和模型名称,例如 "myapp.MyModel"。
通过使用 swappable_dependency(),您可以告知迁移框架该迁移依赖于另一个设置可交换模型的迁移,从而允许将来用不同的实现替换该模型。这通常用于引用可能需要自定义或替换的模型,例如 Django 身份验证系统中的自定义用户模型(settings.AUTH_USER_MODEL,默认为 "auth.User")。
迁移文件¶
迁移存储为磁盘上的格式,这里称为“迁移文件”。这些文件实际上是普通的 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 模块)时查找的是 django.db.migrations.Migration 的一个子类,名为 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_to 和 limit_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 期望一个可调用对象作为其参数,该可调用对象接受两个参数——第一个是 应用程序注册表,其中加载了所有模型的历史版本以匹配迁移在历史记录中的位置,第二个是 SchemaEditor,您可以使用它来手动执行数据库模式更改(但要注意,这样做可能会混淆迁移自动检测器!)
让我们编写一个迁移,使用 first_name 和 last_name 的组合值填充新的 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,数据迁移将在其他迁移 alongside 运行。
您可以向 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 并将其按顺序排列,然后在其上运行优化器以尝试减少列表的长度来实现这一点——例如,它知道 CreateModel 和 DeleteModel 会相互抵消,并且它知道 AddField 可以合并到 CreateModel 中。
一旦操作序列尽可能减少——可能的减少量取决于您的模型之间的紧密程度以及您是否有任何 RunSQL 或 RunPython 操作(除非标记为 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? [y/N] 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可以序列化以下内容:
int、float、bool、str、bytes、None、NoneTypelist、set、tuple、dict、range。datetime.date、datetime.time和datetime.datetime实例(包括那些具有时区感知的实例)decimal.Decimal实例enum.Enum和enum.Flag实例uuid.UUID实例functools.partial()和functools.partialmethod实例,它们具有可序列化的func、args和keywords值。来自
pathlib的纯路径对象和具体路径对象。具体路径将转换为其纯路径等效项,例如pathlib.PosixPath转换为pathlib.PurePosixPath。os.PathLike实例,例如os.DirEntry,它们使用os.fspath()转换为str或bytes。包装可序列化值的
LazyObject实例。枚举类型(例如
TextChoices或IntegerChoices)实例。任何Django字段
任何函数或方法引用(例如
datetime.datetime.today)(必须位于模块的顶级作用域)如果正确包装,则可以装饰函数,即使用
functools.wraps()
从类体中使用的未绑定方法
任何类引用(必须位于模块的顶级作用域)
任何具有自定义
deconstruct()方法的内容(参见下文)
添加了对使用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 版本的迁移。在这种情况下,您应始终使用**您希望支持的最低 Django 版本**运行 makemigrations。
迁移系统将根据与 Django 其他部分相同的策略维护向后兼容性,因此在 Django X.Y 上生成的迁移文件应在 Django X.Y+1 上保持不变。但是,迁移系统并不保证向前兼容性。可能会添加新功能,并且使用较新版本的 Django 生成的迁移文件可能无法在较旧的版本上运行。
另见
- 迁移操作参考
涵盖模式操作 API、特殊操作以及编写您自己的操作。
- 编写迁移“操作指南”
解释如何构建和编写数据库迁移以应对您可能遇到的不同场景。