迁移¶ T0>

迁移是Django将您对模型进行的更改(添加字段,删除模型等)传播到数据库模式的方式。 它们被设计成大部分是自动的,但是你需要知道什么时候进行迁移,什么时候运行,以及可能遇到的常见问题。

命令

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

  • migrate, which is responsible for applying and unapplying migrations.
  • makemigrations,负责根据您对模型所做的更改创建新的迁移。
  • sqlmigrate,它显示迁移的SQL语句。
  • showmigrations, which lists a project’s migrations and their status.

您应该将迁移看作数据库模式的版本控制系统。 makemigrations is responsible for packaging up your model changes into individual migration files - analogous to commits - and migrate is responsible for applying those to your database.

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

注意

可以通过修改MIGRATION_MODULES设置来覆盖每个应用程序包含迁移的包的名称。

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

Django将会对你的模型或者字段进行任何修改,甚至是不会影响数据库的选项,因为它能够正确地重建一个字段的唯一方法就是对历史进行所有的修改,你可能需要这些修改稍后进行一些数据迁移(例如,如果您设置了自定义验证器)。

后端支持

在Django附带的所有后端以及任何第三方后端(如果已经编程支持架构变更(通过SchemaEditor类))上支持迁移。

但是,有些数据库在架构迁移方面比其他数据库更有能力;一些注意事项如下所述。

的PostgreSQL ¶ T0>

在模式支持方面,PostgreSQL是所有数据库中最有能力的;唯一需要注意的是,添加具有默认值的列将导致表格的完全重写,时间与其大小成正比。

出于这个原因,建议您始终使用null=True创建新列,这样它们将立即添加。

MySQL的¶ T0>

MySQL的缺乏周边的架构修改操作事务的支持,这意味着如果迁移逾期不申请则必须手动拆洗,以再次尝试改变(这是不可能回滚到一个较早点)。

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

最后,MySQL对列,表和索引的名称长度的限制相对较小,并且索引涵盖的所有列的组合大小也是有限制的。 这意味着其他后端可能的索引将无法在MySQL下创建。

SQLite的¶ T0>

SQLite内置模式更改支持很少,所以Django试图通过以下方式来模拟它:

  • 用新模式创建一个新表
  • 跨数据复制
  • 丢下旧桌子
  • 重命名新表以匹配原始名称

这个过程通常效果不错,但速度可能会很慢,偶尔也会有问题。 不建议您在生产环境中运行和迁移SQLite,除非您非常清楚风险及其限制; Django提供的支持旨在允许开发人员在其本地机器上使用SQLite来开发不太复杂的Django项目,而不需要完整的数据库。

工作流¶ T0>

使用迁移很简单。 对模型进行更改 - 比如添加一个字段并删除模型 - 然后运行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会提示你并给你一些选择。 如果认为它足够安全,它会提供自动线性化你的两个迁移。 如果没有,你必须自己进入并修改迁移 - 不用担心,这并不困难,在下面的Migration files中有更多解释。

依赖¶ T0>

虽然迁移是按应用程序进行的,但您的模型隐含的表格和关系过于复杂,一次只能为一个应用程序创建。 当你做一个需要别的东西运行的迁移 - 例如,你在你的books应用程序中添加一个ForeignKey给你的authors应用程序 - 由此产生的迁移将包含对authors中迁移的依赖。

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

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

迁移文件

迁移以磁盘格式存储,在此称为“迁移文件”。 这些文件实际上只是一个普通的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,这个依赖的迁移列表。
  • operationsOperation类的列表,用于定义此迁移的功能。

操作是关键;它们是一组声明性指令,它们告诉Django需要进行哪些模式更改。 Django对它们进行扫描,并将所有模式更改的内存中表示形成到所有应用程序,并使用它来生成使模式更改的SQL。

这个内存结构也被用来确定你的模型和你的迁移的当前状态之间的区别。 Django依次执行所有更改,以便在内存中的一组模型中,在上次运行makemigrations时提供模型的状态。 然后使用这些模型与models.py文件中的模型进行比较,以确定您所做的更改。

您应该很少需要手动编辑迁移文件,但是如果需要,完全可以手动编写它们。 一些更复杂的操作不能自动检测,只能通过手写的移植来使用,所以如果必须的话,不要害怕编辑它们。

自定义字段

您不能修改已经迁移的自定义字段中的位置参数的数量,而不会引发TypeError 旧迁移将调用具有旧签名的修改过的__init__方法。 So if you need a new argument, please create a keyword argument and add something like assert 'argument_name' in kwargs in the constructor.

模型经理

您可以选择序列化管理器进行迁移,并使它们在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()

请参考迁移中关于Historical models的注释,以查看出现的含义。

初始迁移

移民。初始 T0> ¶ T1>

应用程序的“初始迁移”是创建该应用程序表的第一个版本的迁移。 通常一个应用程序只有一个初始迁移,但在一些复杂模型相互依赖的情况下,它可能有两个或更多。

初始迁移在迁移类中用初始 = 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 迁移 - 假初始 Django会检测到你有一个初始的迁移,它想创建的表已经存在,并且将迁移标记为已经应用。 (没有migrate --fake-initial标志,该命令会出错,因为它想创建的表已经存在。

请注意,这只适用于两件事情:

  • 自从你制作表格之后,你还没有改变过你的模型。 对于迁移工作,您必须首先进行初始迁移,然后进行更改,因为Django会比较迁移文件而不是数据库的更改。
  • 你还没有手动编辑你的数据库 - Django将无法检测到你的数据库与你的模型不匹配,只是在迁移尝试修改这些表时才会出错。

历史模型

在运行迁移时,Django正在从迁移文件中存储的模型的历史版本开始工作。 如果您使用RunPython操作编写Python代码,或者如果您的数据库路由器上有allow_migrate方法,那么您将会看到这些版本的模型。

由于无法序列化任意Python代码,因此这些历史模型将不会有您定义的任何自定义方法。 然而,他们将拥有相同的领域,关系,管理者(仅限于use_in_migrations = True)和Meta选项(也是版本化的,所以它们可能与您当前的版本不同)。

警告

这意味着当你在迁移中访问它们时,你将不会有自定义的save()方法被调用,你将不会有任何自定义的构造函数或实例方法。 计划适当!

upload_tolimit_choices_to等字段选项中的函数以及具有use_in_migrations = t6 > 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 = [
    ]

Now, all you need to do is create a new function and have RunPython use it. RunPython expects a callable as its argument which takes two arguments - the first is an app registry that has the historical versions of all your models loaded into it to match where in your history the migration sits, and the second is a SchemaEditor, which you can use to manually effect database schema changes (but beware, doing this can confuse the migration autodetector!)

我们来写一个简单的迁移,用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 迁移,数据迁移将运行在与其他迁移一起放置。

您可以将第二个可调用对象传递给RunPython以运行向后迁移时要执行的任何逻辑。 如果省略此可调用,向后移动将引发异常。

从其他应用程序访问模型

在编写使用除迁移所在应用程序之外的应用程序的模型的RunPython函数时,迁移的dependencies属性应包含所涉及的每个应用程序的最新迁移,否则可能会得到类似于以下的错误: LookupError: 没有 安装 应用 标签 'myappname' 当您尝试使用apps.get_model()检索RunPython函数中的模型时。

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

更高级的迁移

如果您对更高级的迁移操作感兴趣,或想要自己编写代码,请参阅writing migrations上的migration operations reference和“操作方法” >。

挤压迁移

鼓励你自由地进行移民,不用担心你有多少人;迁移代码经过优化,可以一次处理数百个,而不会太慢。 但是,最终你会想从几百次迁移回到几次,那就是压扁的地方。

挤压是将现有的许多迁移集合减少到一个(或者有时少数)迁移,这些迁移仍然表现出相同的变化。

Django does this by taking all of your existing migrations, extracting their Operations and putting them all in sequence, and then running an optimizer over them to try and reduce the length of the list - for example, it knows that CreateModel and DeleteModel cancel each other out, and it knows that AddField can be rolled into CreateModel.

Once the operation sequence has been reduced as much as possible - the amount possible depends on how closely intertwined your models are and if you have any RunSQL or RunPython operations (which can’t be optimized through unless they are marked as elidable) - Django will then write it back out into a new set of migration files.

这些文件被标记为可以替代之前压缩的迁移,因此它们可以与旧的迁移文件共存,Django将根据您在历史中的位置智能切换它们。 如果你仍然是通过你压缩的迁移的一部分,它将继续使用它们,直到它结束,然后切换到压扁的历史,而新的安装将只使用新的压扁的迁移,并跳过所有旧的那些。

这使您可以压缩,而不是混淆当前生产中尚未完全更新的系统。 推荐的过程是压缩,保留旧文件,提交和发布,等到所有系统都升级到新版本(或者如果您是第三方项目,只需确保用户升级版本,而不跳过任何) ,然后删除旧文件,提交并执行第二个版本。

The command that backs all this is squashmigrations - just pass it the app label and migration name you want to squash up to, and it’ll get to work:

$ ./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_somthing.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,请将循环依赖关系循环中的一个ForeignKeys分解为单独的迁移,然后将其依赖关系移动到另一个应用程序。 如果您不确定,请参阅makemigrations在要求从模型创建全新迁移时如何处理此问题。 在未来的Django版本中,squashmigrations将被更新以尝试自己解决这些错误。

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

然后,你必须通过以下途径将被压扁的移民迁移到正常移民:

  • 删除它所替换的所有迁移文件。
  • 更新所有依赖已删除迁移的迁移取决于压扁的迁移。
  • Removing the replaces attribute in the Migration class of the squashed migration (this is how Django tells that it is a squashed migration).

注意

一旦你压制了一个移民,你就不应该重新挤压这个挤压的移民,直到你完全转移到正常的移民。

序列化值

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

虽然Django可以序列化大部分东西,但有些东西我们不能将其序列化成一个有效的Python表示形式 - 对于如何将一个值转换回代码来说,没有Python标准(repr()只适用于基本值,不指定导入路径)。

Django可以序列化以下内容:

  • int, float, bool, str, bytes, None
  • listsettupledict
  • datetime.datedatetime.timedatetime.datetime实例(包括可识别时区的实例)
  • decimal.Decimal实例
  • enum.Enum instances
  • uuid.UUID instances
  • functools.partial instances which have serializable func, args, and keywords values.
  • LazyObject instances which wrap a serializable value.
  • 任何Django字段
  • Any function or method reference (e.g. datetime.datetime.today) (must be in module’s top-level scope)
  • 从类体内使用的未绑定方法
  • 任何类的引用(必须在模块的顶级范围内)
  • 任何使用自定义的deconstruct()方法(see below
在Django 1.11中更改:

已添加对uuid.UUID的序列化支持。

Django无法序列化:

  • 嵌套的类
  • 任意类实例(例如MyClass(4.3, 5.7)
  • Lambda表达式

添加一个deconstruct()方法

您可以让Django通过给类指定一个deconstruct()方法来序列化您自己的自定义类实例。 It takes no arguments, and should return a tuple of three things (path, args, kwargs):

  • path应该是类的Python路径,类名包含在最后一部分(例如,myapp.custom_things.MyClass)中。 如果你的类不在模块的顶层,那么它是不可序列化的。
  • args应该是传递给你的类的__init__方法的位置参数列表。 这个列表中的所有东西本身都是可序列化的。
  • kwargs应该是关键字参数的字典传递给你的类的__init__方法。 每个值本身都应该是可序列化的。

注意

这个返回值不同于自定义字段的deconstruct()方法for custom fields

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,特殊操作和编写自己的操作。
写作迁移“如何做”
说明如何为可能遇到的不同情况构建和编写数据库迁移。