迁移¶
迁移是 Django 将你对模型的修改(例如增加一个字段,删除一个模型)应用至数据库架构中的方式。它们被设计的尽可能自动化,但你仍需要知道何时构建和运行迁移,你还需要了解一些常见问题。
命令¶
以下是几个常用的与迁移交互的命令,即 Django 处理数据库架构的方式:
migrate
,负责应用和撤销迁移。makemigrations
,基于模型的修改创建迁移。sqlmigrate
,展示迁移使用的 SQL 语句。showmigrations
,列出项目的迁移和迁移的状态。
你应该将迁移看作是数据库架构的版本控制系统。 makemigrations
负责将模型修改打包进独立的迁移文件中——类似提交修改,而 migrate
负责将其应用至数据库。
每个应用的迁移文件位于该应用的 "migrations" 目录中,他们被设计成应用代码的一部分,与应用代码一起被提交,被发布。你只需在开发机上构建一次,就可以在同事的电脑或测试机上运行同样的迁移而保证结果一致。最后在生产环境运行同样的迁移。
Note
通过修改配置 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
操作中使用它们。这是通过在 manager 类上定义一个 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 会拒绝运行迁移或进行新的迁移,直到它被修复。当使用多个数据库时,可以使用 database routers 的 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
方法,则你 需要使用 这些模型的历史版本而不是直接导入它们。
Warning
如果您直接导入模型而不是使用历史模型,您的迁移 可能在初始阶段工作,但在将来尝试重新运行旧迁移时将失败(通常是在设置新安装并运行所有迁移以设置数据库时)。
这意味着历史模型的问题可能不会立即显现。如果遇到这种故障,可以编辑迁移以使用历史模型,而不是直接导入并提交这些更改。
因为不可能序列化任意的 Python 代码,这些历史模型不会有你定义的任何自定义方法。然而,它们将具有相同的字段、关系、管理器(仅限于那些具有 use_in_migrations = True
)和 Meta
选项(也有版本控制,因此它们可能与当前的不同)。
Warning
这意味着在迁移中访问对象时,将不会对对象调用自定义的 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 中的迁移文件是由 Operations 组成的,你用于数据迁移的主要操作是 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
,数据迁移将与其他迁移一起运行。
您可以将第二个可调用对象传递给 RunPython
以运行撤销迁移时要执行的任何逻辑。 如果忽略此可调用对象,则撤销迁移将引发异常。
从其他应用访问模型¶
在编写使用来自迁移所在应用以外的其他应用模型的 RunPython
函数时,迁移的 dependencies
属性应包括所涉及的每个应用程序的最新迁移,否则当你尝试使用 apps.get_model()
在 RunPython
函数中获取模型时,你可能会得到 LookupError: No installed app with label 'myappname'
。
在下面的例子中,我们在 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
问题,请将循环依赖中的外键分离到单独的迁移中,并将依赖项移到另一个应用上。如果你不确定,请参见 makemigrations
在被要求从模型创建全新的迁移时如何处理问题。在未来的 Django 版本中,squashmigrations
将被更新以尝试自己解决这些错误。
一旦你压缩了你的迁移,你应该把它和它所替代的迁移一起提交,并把这个更改分发到你的应用程序的所有运行中的实例,确保它们运行 migrate
来将更改存储在它们的数据库中。
然后,你必须通过以下方法将压缩的迁移过渡到正常迁移:
删除它替换的所有迁移文件。
将所有依赖被删除迁移的迁移更新为依赖被压缩的迁移。
删除压缩迁移的
Migration
类的replaces
属性(这就是 Django 告诉它是压缩迁移的方式)。
Note
压缩迁移后,在完全将其转换为正常迁移之前,你不应该再重新压缩该压缩的迁移。
修剪已删除的迁移的引用
如果可能会在将来重复使用已删除的迁移的名称,您应该使用 migrate --prune
选项从 Django 的迁移表中删除对它的引用。
序列化值¶
迁移是包含模型旧定义的 Python 文件,因此,要编写它们,Django 必须获取模型的当前状态并将它们序列化到一个文件中。
虽然 Django 可以序列化大多数内容,但有些内容我们无法序列化为有效的 Python 表示形式——对于如何将值转换回代码,没有 Python 标准(repr()
只适用于基本的值,而且没有指定导入路径)。
Django 可以序列化以下内容:
int
,float
,bool
,str
,bytes
,None
,NoneType
list
,set
,tuple
,dict
,range
。datetime.date
,datetime.time
和datetime.datetime
实例(包括可识别时区的实例)decimal.Decimal
实例enum.Enum
和enum.Flag
实例uuid.UUID
实例functools.partial()
和具有可序列化func
、args
和keywords
值的functools.partialmethod
实例。来自
pathlib
的纯路径和具体路径对象。具体路径将被转换为其纯路径等效项,例如pathlib.PosixPath
到pathlib.PurePosixPath
。os.PathLike
实例,例如os.DirEntry
,可以使用os.fspath()
转换为str
或bytes
。包含可序列化值的
LazyObject
实例。枚举类型(例如
TextChoices
或IntegerChoices
)实例。任何 Django 字段
任何函数或方法引用(如
datetime.datetime.today
)(必须在模块的顶层范围内)如果正确包装,函数可以进行装饰,即使用
functools.wraps()
明确支持
functools.cache()
和functools.lru_cache()
装饰器
在类主体内部使用的未绑定方法
任何类引用(必须在模块的顶层范围内)
具有自定义
deconstruct()
方法的任何东西(见下文)
已添加对使用 functools.cache()
或 functools.lru_cache()
装饰的函数的序列化支持。
Django 不能序列化:
嵌套类
任何类实例(例如
MyClass(4.3, 5.7)
)匿名函数
自定义序列化¶
你可以通过编写一个自定义的序列化器来序列化其他类型。例如,如果 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__
方法。每个值本身应该是可序列化的。
Note
此返回值与 自定义字段 的 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 生成的迁移文件可能无法在旧版本上运行。