迁移¶
迁移是 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
事务¶
在支持 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
方法,则你 需要使用 这些模型的历史版本而不是直接导入它们。
警告
如果您直接导入模型而不是使用历史模型,您的迁移 可能在初始阶段工作,但在将来尝试重新运行旧迁移时将失败(通常是在设置新安装并运行所有迁移以设置数据库时)。
这意味着历史模型的问题可能不会立即显现。如果遇到这种故障,可以编辑迁移以使用历史模型,而不是直接导入并提交这些更改。
因为不可能序列化任意的 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 中的迁移文件是由 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 告诉它是压缩迁移的方式)。
备注
压缩迁移后,在完全将其转换为正常迁移之前,你不应该再重新压缩该压缩的迁移。
修剪已删除的迁移的引用
如果可能会在将来重复使用已删除的迁移的名称,您应该使用 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__
方法。每个值本身应该是可序列化的。
备注
此返回值与 自定义字段 的 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 生成的迁移文件可能无法在旧版本上运行。