Django4.0 迁移-序列化值
迁移是包含模型旧定义的 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
实例 -
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
)(必须在模块的顶层范围内) - 在类主体内部使用的未绑定方法
- 任何类引用(必须在模块的顶层范围内)
- 具有自定义
deconstruct()
方法的任何东西(见下文)
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()
时准确返回这些参数。