迁移

迁移是 Django 将你对模型的修改(例如增加一个字段,删除一个模型)应用至数据库架构中的方式。它们被设计的尽可能自动化,但你仍需要知道何时构建和运行迁移,你还需要了解一些常见问题。

命令

以下是几个常用的与迁移交互的命令,即 Django 处理数据库架构的方式:

你应该将迁移看作是数据库架构的版本控制系统。 makemigrations 负责将模型修改打包进独立的迁移文件中——类似提交修改,而 migrate 负责将其应用至数据库。

每个应用的迁移文件位于该应用的 "migrations" 目录中,他们被设计成应用代码的一部分,与应用代码一起被提交,被发布。你只需在开发机上构建一次,就可以在同事的电脑或测试机上运行同样的迁移而保证结果一致。最后在生产环境运行同样的迁移。

注解

通过修改配置 MIGRATION_MODULES 可以重写包含迁移的应用的包名。

从同样的数据集合运行迁移在开发、测试和生产环境都会生成同样的结果。

Django 会在修改模型或字段时生成迁移——即便修改的是不会影响数据库的配置——因为唯一能确保结果正确性的方法时完整记录修改历史,而且这些东西你以后可能在某些数据迁移中用的到(例如,已设置了自定义验证器的时候)。

后端支持

所有 Django 支持的数据库后端都支持迁移,还有些支持表修改(通过 SchemaEditor 类实现)的第三方后端也支持。

然而,有些数据库在表结构变更方面比其它数据库更强;下面介绍一些注意事项。

PostgreSQL

PostgreSQL 在架构支持方面是所有数据库中是最强的。

唯一需要注意的是,在 PostgreSQL 11 之前,添加具有默认值的列会导致表的完全重写,时间长短与表的大小成正比。 因此,建议你始终使用 null=True 创建新列,因为这样可以立即添加它们。

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

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

如果您你给迁移赋予一个有意义的名称而不是生成的名称,则可以使用 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 来在事务中执行部分迁移。更多细节请参见 非原子性迁移

依赖

虽然迁移是按应用进行的,但你的模型所隐含的表和关系太复杂,不可能同时为一个应用创建。当你进行迁移时,需要运行其他的东西——例如,你在你的 books 应用中添加了一个指向 authors 应用的 ForeignKey——最终的迁移将包含对 authors 中迁移的依赖。

This means that when you run the migrations, the authors migration runs first and creates the table the ForeignKey references, and then the migration that makes the ForeignKey column runs afterward and creates the constraint. If this didn't happen, the migration would try to create the ForeignKey column without the table it's referencing existing and your database would throw an error.

这种依赖性行为会影响大多数只限于单个应用的迁移操作。仅限于单个应用(无论是 makemigrations 还是 migrate)是尽最大努力的承诺,而不是保证;任何其他需要用来正确获取依赖关系的应用程序都将是。

没有迁移的应用不得与有迁移的应用有关系(ForeignKeyManyToManyField 等)。有时可能可行,但不受支持。

迁移文件

迁移以磁盘格式存储,这里称为“迁移文件”。这些文件实际上是普通的 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 routersallow_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_tolimit_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_namelast_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 = '%s %s' % (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 并将它们按顺序排列,然后对它们运行一个优化器,以尝试减少列表的长度——例如,它知道 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 问题,请将循环依赖中的外键分离到单独的迁移中,并将依赖项移到另一个应用上。如果你不确定,请参见 makemigrations 在被要求从模型创建全新的迁移时如何处理问题。在未来的 Django 版本中,squashmigrations 将被更新以尝试自己解决这些错误。

一旦你压缩了你的迁移,你应该把它和它所替代的迁移一起提交,并把这个更改分发到你的应用程序的所有运行中的实例,确保它们运行 migrate 来将更改存储在它们的数据库中。

然后,你必须通过以下方法将压缩的迁移过渡到正常迁移:

  • 删除它替换的所有迁移文件。
  • 将所有依赖被删除迁移的迁移更新为依赖被压缩的迁移。
  • 删除压缩迁移的 Migration 类的 replaces 属性(这就是 Django 告诉它是压缩迁移的方式)。

注解

压缩迁移后,在完全将其转换为正常迁移之前,你不应该再重新压缩该压缩的迁移。

序列化值

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

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

Django 可以序列化以下内容:

  • intfloatboolstrbytesNoneNoneType
  • listsettupledictrange
  • datetime.datedatetime.timedatetime.datetime 实例(包括可识别时区的实例)
  • decimal.Decimal 实例
  • enum.Enum 实例
  • uuid.UUID 实例
  • functools.partial() 和具有可序列化 funcargskeywords 值的 functools.partialmethod 实例。
  • Pure and concrete path objects from pathlib. Concrete paths are converted to their pure path equivalent, e.g. pathlib.PosixPath to pathlib.PurePosixPath.
  • os.PathLike instances, e.g. os.DirEntry, which are converted to str or bytes using os.fspath().
  • 包含可序列化值的 LazyObject 实例。
  • 枚举类型(例如 TextChoicesIntegerChoices)实例。
  • 任何 Django 字段
  • 任何函数或方法引用(如 datetime.datetime.today)(必须在模块的顶层范围内)
  • 在类主体内部使用的未绑定方法
  • 任何类引用(必须在模块的顶层范围内)
  • 具有自定义 deconstruct() 方法的任何东西(见下文
Changed in Django 3.2:

Serialization support for pure and concrete path objects from pathlib, and os.PathLike instances was added.

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 生成的迁移文件可能无法在旧版本上运行。

参见

迁移操作参考
涵盖架构操作 API,特殊操作以及编写自己的操作。
编写迁移的“方法”
介绍如何为你可能遇到的不同情况构建和编写数据库迁移。