迁徙

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

命令

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

  • migrate ,负责应用和取消应用迁移。

  • makemigrations ,它负责根据对模型所做的更改创建新的迁移。

  • sqlmigrate ,显示迁移的SQL语句。

  • showmigrations 其中列出了项目的迁移及其状态。

您应该将迁移视为数据库模式的版本控制系统。 makemigrations 负责将模型更改打包到单个迁移文件中(类似于提交),以及 migrate 负责将这些应用到数据库。

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

备注

可以通过修改 MIGRATION_MODULES 设置。

迁移将以相同的方式在同一个数据集上运行,并产生一致的结果,这意味着在开发和分段中看到的情况,在相同的情况下,正是在生产中发生的情况。

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

后端支持

Django附带的所有后端以及任何第三方后端都支持迁移,前提是它们已编程为支持模式更改(通过 SchemaEditor 类)。

然而,在模式迁移方面,一些数据库比其他数据库更有能力;下面介绍了一些注意事项。

PostgreSQL

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

MySQL

MySQL缺乏对模式更改操作周围事务的支持,这意味着如果迁移无法应用,则必须手动取消选择更改才能重试(不可能回滚到更早的点)。

此外,MySQL将为几乎每个模式操作完全重写表,并且通常需要与表中的行数成比例的时间来添加或删除列。在速度较慢的硬件上,这可能比每百万行一分钟还要糟糕——向只有几百万行的表中添加几列可能会将站点锁定超过十分钟。

最后,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

一旦应用了迁移,就将迁移和模型作为一个提交提交提交到您的版本控制系统中——这样,当其他开发人员(或生产服务器)签出代码时,他们将同时获得对模型的更改和附带的迁移。

如果希望为迁移(S)指定一个有意义的名称而不是生成的名称,可以使用 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=TrueRunPython 。看见 非原子迁移 了解更多详细信息。

依赖关系

虽然迁移是针对每个应用程序的,但是模型所隐含的表和关系太复杂,不能一次为一个应用程序创建。当您进行需要运行其他程序的迁移时—例如,您添加了一个 ForeignKey 在你 books 应用程序到您的 authors 应用程序-生成的迁移将包含对中迁移的依赖项 authors .

这意味着当您运行迁移时, authors 迁移首先运行,并创建 ForeignKey 引用,然后是使 ForeignKey 列随后运行并创建约束。如果没有发生这种情况,迁移将尝试创建 ForeignKey 列,而没有它引用的现有表,则数据库将抛出错误。

这种依赖行为会影响大多数迁移操作,在这些操作中,您只能使用单个应用程序。限制为单个应用程序(在 makemigrationsmigrate )是一个尽最大努力的承诺,而不是一个保证;任何其他应用程序,需要用来获得正确的依赖关系将是。

没有迁移的应用程序不能有关系 (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模块)时查找的是 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 找不到class属性,如果迁移是应用程序中的第一次迁移(即,如果它不依赖于同一应用程序中的任何其他迁移),则迁移将被视为“初始”。

migrate --fake-initial 使用选项时,将专门处理这些初始迁移。对于创建一个或多个表的初始迁移 (CreateModel 操作),django检查数据库中是否已经存在所有这些表,如果存在,则fake应用迁移。同样,对于添加一个或多个字段的初始迁移 (AddField 操作),django检查数据库中是否已经存在所有相应的列,如果存在,则fake应用迁移。没有 --fake-initial 对初始迁移的处理与任何其他迁移都没有区别。

历史一致性

正如前面所讨论的,当连接两个开发分支时,您可能需要手动线性化迁移。在编辑迁移依赖项时,您可能会无意中创建一个不一致的历史状态,其中应用了迁移,但其某些依赖项没有应用。这强烈表明依赖项不正确,因此Django将拒绝运行迁移或进行新的迁移,直到修复它。使用多个数据库时,可以使用 allow_migrate() 方法 database routers 控制哪些数据库 makemigrations 检查历史记录是否一致。

向应用程序添加迁移

新的应用程序预配置为接受迁移,因此您可以通过运行 makemigrations 一旦你做了一些改变。

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

$ python manage.py makemigrations your_app_label

这将为您的应用程序进行新的初始迁移。现在,运行 python manage.py migrate --fake-initial ,Django将检测到您有一个初始迁移 and 它要创建的表已经存在,并将迁移标记为已应用。(没有 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 手术,或者如果你有 allow_migrate 数据库路由器上的方法, 需要使用 这些历史模型版本而不是直接导入它们。

警告

如果直接导入模型而不是使用历史模型,则您的迁移 may work initially 但是将来当您尝试重新运行旧迁移时(通常是在设置新安装并运行所有迁移以设置数据库时),它将失败。

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

因为不可能序列化任意的Python代码,所以这些历史模型将没有您定义的任何自定义方法。但是,他们将拥有相同的字段、关系、经理(仅限于 use_in_migrations = TrueMeta 选项(也是版本化的,因此它们可能与当前的不同)。

警告

这意味着你不会有习惯 save() 当您在迁移中访问对象时调用了这些方法,并且您将没有任何自定义构造函数或实例方法。适当计划!

对字段选项中的函数的引用,例如 upload_tolimit_choices_to 和模型管理器声明 use_in_migrations = True 是在迁移中序列化的,因此只要存在引用它们的迁移,就需要保留函数和类。任何 custom model fields 还需要保留,因为这些是通过迁移直接导入的。

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

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

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

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

为了帮助解决这种情况,Django提供了一些模型字段属性来帮助使用 system checks framework .

添加 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 需要一个可调用参数作为其参数,该参数采用两个参数-第一个参数是 app registry 它将所有模型的历史版本加载到其中,以匹配迁移在历史中的位置,第二个版本是 SchemaEditor ,您可以使用它手动影响数据库架构更改(但请注意,这样做可能会混淆迁移自动检测器!)

让我们编写一个迁移来填充新的 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' 当您尝试在 RunPython 使用函数 apps.get_model() .

在下面的示例中,我们在 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),
    ]

更高级的迁移

如果您对更高级的迁移操作感兴趣,或者希望能够编写自己的迁移操作,请参见 migration operations reference 以及“操作方法” writing migrations .

挤压迁移

我们鼓励您自由地进行迁移,而不必担心您有多少个迁移代码;迁移代码经过优化,可以一次处理数百个迁移,而不会有太多的速度减慢。然而,最终你会想从几百次迁移移回几次,这就是压扁的原因。

压扁是将一组现有的多个迁移减少到一个(有时是几个)迁移,这些迁移仍然表示相同的更改。

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 在数据库中存储更改。

然后必须通过以下方式将压缩迁移转换为正常迁移:

  • 删除它替换的所有迁移文件。

  • 更新依赖于已删除迁移的所有迁移以依赖于压缩的迁移。

  • 去掉 replaces 属性 Migration 压缩迁移的类(这是Django如何告知它是压缩迁移)。

备注

一旦压扁了一个迁移,就不应该再压扁那个压扁的迁移,直到您将其完全转换为正常迁移为止。

修剪对已删除迁移的引用

如果将来可能会重复使用已删除迁移的名称,则应该使用 migrate --prune 选择。

序列化值

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

虽然django可以序列化大多数东西,但有些东西我们只是无法序列化为有效的python表示形式——对于如何将值转换回代码,没有python标准。 (repr() 仅适用于基本值,不指定导入路径)。

Django可以序列化以下内容:

  • int, float, bool, str, bytes, None, NoneType

  • listsettupledictrange .

  • datetime.datedatetime.timedatetime.datetime 实例(包括那些时区感知的实例)

  • decimal.Decimal 实例

  • enum.Enumenum.Flag 实例

  • uuid.UUID 实例

  • functools.partial()functools.partialmethod 具有可序列化的实例 funcargskeywords 价值观。

  • 来自 pathlib . 具体路径被转换为它们的纯路径等价物,例如。 pathlib.PosixPathpathlib.PurePosixPath .

  • os.PathLike 实例,例如。 os.DirEntry ,转换为 strbytes 使用 os.fspath() .

  • LazyObject 包装可序列化值的实例。

  • 枚举类型(例如。 TextChoicesIntegerChoices )实例。

  • 任何Django字段

  • 任何函数或方法引用(例如 datetime.datetime.today )(必须在模块的顶级范围内)

  • 从类体内部使用的未绑定方法

  • 任何类引用(必须在模块的顶级范围内)

  • 任何有习俗的东西 deconstruct() 方法 (see below

Changed in Django 5.0:

对用 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() 是应使用序列化程序的类型或ITable。

这个 serialize() 序列化程序的方法必须返回一个字符串,说明值在迁移中的显示方式,以及一组迁移中需要的任何导入。

添加一个 deconstruct() 方法

通过给类A,可以让Django序列化自己的自定义类实例 deconstruct() 方法。它不需要参数,应该返回由三个元素组成的元组 (path, args, kwargs)

  • path 应该是类的python路径,最后一部分包括类名(例如, myapp.custom_things.MyClass )如果您的类在模块的顶层不可用,那么它是不可序列化的。

  • args 应该是传递给类的位置参数列表' __init__ 方法。此列表中的所有内容本身都应该是可序列化的。

  • kwargs 应该是要传递给类的关键字参数的dict' __init__ 方法。每个值本身都应该是可序列化的。

备注

此返回值与 deconstruct() 方法 for custom fields 它返回一个由四个项组成的元组。

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

防止每次创建新的迁移 makemigrations 是run,还应添加 __eq__() 修饰类的方法。Django的迁移框架将调用此函数来检测状态之间的更改。

只要类构造函数的所有参数本身都是可序列化的,就可以使用 @deconstructible 类修饰符来自 django.utils.deconstruct 添加 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

decorator添加了逻辑来捕获和保留参数,并将其保存到构造函数中,然后在调用deconstruct()时返回这些参数。

支持多个django版本

如果您是具有模型的第三方应用程序的维护者,则可能需要发送支持多个django版本的迁移。在这种情况下,您应该始终运行 makemigrations 使用您希望支持的最低Django版本 .

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

参见

The Migrations Operations Reference

包括模式操作API、特殊操作和编写自己的操作。

The Writing Migrations "how-to"

解释如何为可能遇到的不同场景构建和编写数据库迁移。