---
title: "迁移"
version: 4.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.1/topics/migrations/
canonical: https://djangodocs.dev/zh-hans/4.1/topics/migrations/
---
# 迁移

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

## 命令

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

- [`migrate`](/zh-hans/4.1/ref/django-admin/#django-admin-migrate)，负责应用和撤销迁移。
- [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-makemigrations)，基于模型的修改创建迁移。
- [`sqlmigrate`](/zh-hans/4.1/ref/django-admin/#django-admin-sqlmigrate)，展示迁移使用的 SQL 语句。
- [`showmigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-showmigrations)，列出项目的迁移和迁移的状态。

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

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

> **Note**
>
> 通过修改配置 [`MIGRATION_MODULES`](/zh-hans/4.1/ref/settings/#std-setting-MIGRATION_MODULES) 可以重写包含迁移的应用的包名。

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

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

## 后端支持

所有 Django 支持的数据库后端都支持迁移，还有些支持表修改（通过 [SchemaEditor](/zh-hans/4.1/ref/schema-editor/) 类实现）的第三方后端也支持。

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

### PostgreSQL

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

### MySQL

MySQL 缺乏对架构变更操作相关事务的支持，这意味着如果迁移失败，你将必须手动取消更改才能重试（无法回滚到较早的时间）。

此外，MySQL 几乎每一次架构操作都会完全重写表，一般来说，增加或删除列需要的时间与表的行数成正比。在速度较慢的硬件上，这可能比每百万行一分钟还要糟糕——在一个只有几百万行的表中添加几列，可能会让你的网站锁定十几分钟。

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

### SQLite

SQLite 几乎没有内置的架构更改支持，因此 Django 尝试通过以下方式对其进行模拟：

- 使用新架构创建新表
- 复制数据
- 删除旧表
- 重新命名新表，使之与原表名相匹配。

此过程一般工作的很好，但它可能很慢，偶尔也会出现问题。除非你非常清楚风险和它的局限性，否则不建议你在生产环境中运行和迁移 SQLite；Django 自带的支持是为了让开发人员在本地计算机上使用 SQLite 来开发较不复杂的 Django 项目，而无需完整的数据库。

## 工作流程

Django 可以为你创建迁移。对你的模型进行修改——比如说，添加一个字段和删除一个模型——然后运行 [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-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`](/zh-hans/4.1/ref/django-admin/#cmdoption-makemigrations-name) 选项：

```
$ python manage.py makemigrations --name changed_my_model your_app_label
```

### 版本控制

由于迁移存储在版本控制中，因此你有时会遇到这样的情况：你和另一个开发人员都同时向同一应用提交了迁移，从而导致两次迁移的编号相同。

别担心——这些数字只是给开发者参考的，Django 只在乎每个迁移都有不同的名称。 迁移在文件中指定了它们所依赖的其他哪些迁移——包括同一应用中的早期迁移，所以可以检测到同一应用有两个新的迁移没有排序。

当这种情况发生时，Django 会提示你，并给你一些选项。如果它认为足够安全，它将为你自动线性化两个迁移。如果不安全，你就得自己去修改迁移——别担心，这并不难，有关更多信息，请参见下面的 [迁移文件](#migration-files)。

## 事务

在支持 DDL 事务的数据库上（SQLite 和 PostgreSQL），所有的迁移操作默认都会在一个事务中运行。相反，如果一个数据库不支持 DDL 事务（如 MySQL、Oracle），那么所有的操作将在没有事务的情况下运行。

你可以通过将 `atomic` 属性设置为 `False` 来防止迁移在事务中运行。例如：

```
from django.db import migrations

class Migration(migrations.Migration):
    atomic = False
```

也可以使用 [`atomic()`](/zh-hans/4.1/topics/db/transactions/#django.db.transaction.atomic) 或者通过传递 `atomic=True` 到 [`RunPython`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 来在事务中执行部分迁移。更多细节请参见 [非原子性迁移](/zh-hans/4.1/howto/writing-migrations/#non-atomic-migrations)。

## 依赖

虽然迁移是按应用进行的，但你的模型所隐含的表和关系太复杂，不可能同时为一个应用创建。当你进行迁移时，需要运行其他的东西——例如，你在你的 `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`）是尽最大努力的承诺，而不是保证；任何其他需要用来正确获取依赖关系的应用程序都将是。

没有迁移的应用不得与有迁移的应用有关系（`ForeignKey`，`ManyToManyField` 等）。有时可能可行，但不受支持。

## 迁移文件

迁移以磁盘格式存储，这里称为“迁移文件”。这些文件实际上是普通的 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`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 操作中使用它们。这是通过在 manager 类上定义一个 `use_in_migrations` 属性来实现的：

```
class MyManager(models.Manager):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()
```

如果你使用 [`from_queryset()`](/zh-hans/4.1/topics/db/managers/#django.db.models.from_queryset) 函数动态生成管理器类，则需要从生成的类继承以使其可导入：

```
class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()
```

请参考关于 [历史模型](#historical-models) 在迁移中的说明，以了解随之而来的影响。

### 初始迁移

#### `Migration.initial`

应用的“初始迁移”是创建该应用首版表的迁移。 通常，一个应用有一个初始迁移，但是在某些情况下，复杂的模型依赖可能会导致两个或更多。

初始迁移在迁移类上标有 `initial = True` 类属性。如果未找到 `initial` 类属性，则如果迁移是应用程序中的第一个迁移（即，如果它不依赖于同一应用程序中的任何其他迁移）则将被视为“初始”。

当使用 [`migrate --fake-initial`](/zh-hans/4.1/ref/django-admin/#cmdoption-migrate-fake-initial) 选项时，将对这些初始迁移进行特殊处理。对于创建一个或多个表（`CreateModel` 操作）的初始迁移，Django 会检查所有这些表是否已经存在于数据库中，如果是，则对迁移进行假应用。 类似地，对于添加了一个或多个字段（`AddField` 操作）的初始迁移，Django 检查数据库中是否已存在所有相应的列，如果存在，则对迁移进行假应用。如果没有 `--fake-initial`，初始迁移的处理方式和其他迁移没有区别。

### 历史一致性

历史一致性前面已经讨论过，当两个开发分支加入时，你可能需要手动线性化迁移。在编辑迁移依赖关系时，你可能会无意中创建一个不一致的历史状态，即一个迁移已经被应用，但它的一些依赖关系还没有应用。这强烈地表明依赖关系不正确，所以 Django 会拒绝运行迁移或进行新的迁移，直到它被修复。当使用多个数据库时，可以使用 [database routers](/zh-hans/4.1/topics/db/multi-db/#topics-db-multi-db-routing) 的 [`allow_migrate()`](/zh-hans/4.1/topics/db/multi-db/#allow_migrate) 方法来控制 [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-makemigrations) 检查哪些数据库的历史一致。

## 向应用添加迁移

新的应用已预先配置为接受迁移，因此你可以在进行一些更改后通过运行 [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-makemigrations) 添加迁移。

如果你的应用已经具有模型和数据库表，并且还没有迁移（例如，你是在先前的 Django 版本中创建的），则需要通过运行以下命令将其转换为使用迁移：

```
$ python manage.py makemigrations your_app_label
```

这将为你的应用程序进行新的初始迁移。现在，运行 `python manage.py migrate --fake-initial`，Django 将检测到你有一个初始迁移 *并且* 它要创建的表已经存在，而将迁移标记为已应用。（如果没有 [`migrate --fake-initial`](/zh-hans/4.1/ref/django-admin/#cmdoption-migrate-fake-initial) 标志，该命令将出错，因为它要创建的表已经存在。）

请注意，这只适用于以下两种情况：

- 自从你建立了表之后，你就没有改变过你的模型。要使迁移生效，你必须 *首先* 进行初始迁移，然后再进行更改，因为 Django 将变更与迁移文件（而不是数据库）进行比较。
- 你尚未手动编辑数据库——Django 无法检测到你的数据库与你的模型不匹配，当迁移尝试修改这些表时，你只会得到错误。

## 撤销迁移

可以通过 [`migrate`](/zh-hans/4.1/ref/django-admin/#django-admin-migrate) 传递上一次迁移的编号来撤销迁移。例如，要撤销迁移 `books.0003`：

```console
$ 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
```

*Windows*

```doscon
...\> 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`：

```console
$ 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
```

*Windows*

```doscon
...\> 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`：

```console
$ 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
```

*Windows*

```doscon
...\> 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`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 操作编写 Python 代码，或者你的数据库路由上有 `allow_migrate` 方法，则你 **需要使用** 这些模型的历史版本而不是直接导入它们。

> **Warning**
>
> If you import models directly rather than using the historical models,
> your migrations *may work initially* but will fail in the future when you
> try to rerun old migrations (commonly, when you set up a new installation
> and run through all the migrations to set up the database).
>
> 这意味着历史模型的问题可能不会立即显现。如果遇到这种故障，可以编辑迁移以使用历史模型，而不是直接导入并提交这些更改。

因为不可能序列化任意的 Python 代码，这些历史模型不会有你定义的任何自定义方法。然而，它们将具有相同的字段、关系、管理器（仅限于那些具有 `use_in_migrations = True`）和 `Meta` 选项（也有版本控制，因此它们可能与当前的不同）。

> **Warning**
>
> 这意味着在迁移中访问对象时，将不会对对象调用自定义的 `save()` 方法，也不会有任何自定义构造函数或实例方法。适当的计划一下吧！

字段选项中对函数的引用，例如 `upload_to` 和 `limit_choices_to` 以及具有 `use_in_migrations = True` 的模型管理器声明，都会在迁移中序列化，因此只要有迁移引用它们，这些函数和类就需要保留。任何 [自定义模型字段](/zh-hans/4.1/howto/custom-model-fields/) 也需要保留，因为这些都是直接由迁移导入的。

此外，模型的具体基类是以指针的形式存储的，所以只要有一个包含对它们的引用的迁移，你就必须始终将基类保留在身边。从好的方面来说，这些基类的方法和管理器都是正常继承的，所以如果你一定需要访问这些，你可以选择将它们移到一个父类中。

要删除旧的引用，你可以 [压缩迁移](#migration-squashing) 或者，如果引用不多，把它们复制到迁移文件中。

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

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

为了解决这种情况，Django 提供了一些模型字段属性，使用 [系统检查框架](/zh-hans/4.1/topics/checks/) 来协助弃用模型字段。

将 `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](/zh-hans/4.1/ref/migration-operations/) 组成的，你用于数据迁移的主要操作是 [`RunPython`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.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`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 使用它。[`RunPython`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 需要一个可调用对象作为它的参数，这个可调用对象需要两个参数——第一个是 [应用注册表](/zh-hans/4.1/ref/applications/) ，其中加载了所有模型的历史版本，以匹配迁移所在的位置，第二个是 [SchemaEditor](/zh-hans/4.1/ref/schema-editor/)，你可以用它来手动实现数据库架构的变更（但要注意，这样做会混淆迁移自动检测器！）

让我们编写一个迁移，使用 `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 = '%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`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.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),
    ]
```

### 更高级的迁移

如果你对更高级的迁移操作感兴趣，或者希望能够编写自己的迁移操作，请参阅 [迁移操作参考](/zh-hans/4.1/ref/migration-operations/) 和“如何” [编写操作](/zh-hans/4.1/howto/writing-migrations/)。

## 压缩迁移

我们鼓励你自由地进行迁移，而不要担心你有多少迁移；迁移代码经过优化，可以一次处理几百个迁移，而不会有太多的减速。然而，最终你会希望从几百个迁移回归到只有几个，这就是压缩的作用。

压缩是将一组现有的多个迁移减少到一个（有时是几个）迁移，这些迁移仍然代表相同的更改。

Django通过获取所有现有迁移，提取它们的 `Operation` 并将它们按顺序排列，然后对它们运行一个优化器，以尝试减少列表的长度——例如，它知道 [`CreateModel`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.CreateModel) 和 [`DeleteModel`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.DeleteModel) 相互抵消，它还知道 [`AddField`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.AddField) 可以卷入 [`CreateModel`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.CreateModel)。

一旦操作序列被尽可能地减少——可能的数量取决于你的模型有多紧密交织，如果你有任何 [`RunSQL`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunSQL) 或 [`RunPython`](/zh-hans/4.1/ref/migration-operations/#django.db.migrations.operations.RunPython) 操作（除非它们被标记为 `elidable`，否则无法被优化），Django就会把它写回一组新的迁移文件中。

这些文件被标记为替换了先前压缩的迁移，因此它们可以与旧迁移文件共存，Django 将根据你在历史记录中的位置智能地在它们之间切换。如果你仍处于压缩过程中，则它将继续使用它们直到结束，然后切换到压缩历史记录，而新安装将使用新压缩后的迁移并跳过所有旧迁移。

这样你就可以压缩而不至于把目前还没有完全更新的生产系统搞乱。推荐的流程是压缩，保留旧文件，提交并发布，等到所有系统都升级到新版本（或者如果你是第三方项目，确保你的用户按顺序升级版本，不跳过任何一个版本），然后删除旧文件，提交并进行第二次发布。

支持这一切的命令是 [`squashmigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-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`](/zh-hans/4.1/ref/django-admin/#cmdoption-squashmigrations-squashed-name) 选项。

请注意，Django 中的模型相互依赖可能会变得非常复杂，压缩可能会导致迁移无法运行；要么是优化错误（在这种情况下，你可以用 `--no-optimize` 再试一次，不过你也应该报告这个问题），要么是 `CircularDependencyError`，在这种情况下，你可以手动解决它。

要手动解决 `CircularDependencyError` 问题，请将循环依赖中的外键分离到单独的迁移中，并将依赖项移到另一个应用上。如果你不确定，请参见 [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-makemigrations) 在被要求从模型创建全新的迁移时如何处理问题。在未来的 Django 版本中，[`squashmigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-squashmigrations) 将被更新以尝试自己解决这些错误。

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

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

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

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

> **Pruning references to deleted migrations**
>
> > **New in Django 4.1**
>
> If it is likely that you may reuse the name of a deleted migration in the
> future, you should remove references to it from Django’s migrations table
> with the [`migrate --prune`](/zh-hans/4.1/ref/django-admin/#cmdoption-migrate-prune) option.

## 序列化值

迁移是包含模型旧定义的 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()`](https://docs.python.org/3/library/functools.html#functools.partial)  和具有可序列化 `func`、`args` 和 `keywords` 值的 [`functools.partialmethod`](https://docs.python.org/3/library/functools.html#functools.partialmethod) 实例。
- Pure and concrete path objects from [`pathlib`](https://docs.python.org/3/library/pathlib.html#module-pathlib). Concrete paths are
  converted to their pure path equivalent, e.g. [`pathlib.PosixPath`](https://docs.python.org/3/library/pathlib.html#pathlib.PosixPath) to
  [`pathlib.PurePosixPath`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePosixPath).
- [`os.PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instances, e.g. [`os.DirEntry`](https://docs.python.org/3/library/os.html#os.DirEntry), which are
  converted to `str` or `bytes` using [`os.fspath()`](https://docs.python.org/3/library/os.html#os.fspath).
- 包含可序列化值的 `LazyObject` 实例。
- 枚举类型（例如 `TextChoices` 或 `IntegerChoices`）实例。
- 任何 Django 字段
- 任何函数或方法引用（如 `datetime.datetime.today`）（必须在模块的顶层范围内）
- 在类主体内部使用的未绑定方法
- 任何类引用（必须在模块的顶层范围内）
- 具有自定义 `deconstruct()` 方法的任何东西（[见下文](#custom-deconstruct-method)）

Django 不能序列化：

- 嵌套类
- 任何类实例（例如 `MyClass(4.3, 5.7)`）
- 匿名函数

### 自定义序列化

你可以通过编写一个自定义的序列化器来序列化其他类型。例如，如果 Django 默认没有序列化 [`Decimal`](https://docs.python.org/3/library/decimal.html#decimal.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__` 方法。每个值本身应该是可序列化的。

> **Note**
>
> 此返回值与 [自定义字段](/zh-hans/4.1/howto/custom-model-fields/#custom-field-deconstruct-method) 的 `deconstruct()` 方法不同，后者返回四个项组成的元组。

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

为了防止每次运行 [`makemigrations`](/zh-hans/4.1/ref/django-admin/#django-admin-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`](/zh-hans/4.1/ref/django-admin/#django-admin-makemigrations)。

迁移系统会按照与 Django 其他部分相同的策略保持向后兼容，所以在 Django X.Y 上生成的迁移文件在 Django X.Y+1 上运行时应该没有变化。但是，迁移系统并不保证向前兼容。新的功能可能会被添加，而且用新版本的 Django 生成的迁移文件可能无法在旧版本上运行。

> **See also**
>
> **[迁移操作参考](/zh-hans/4.1/ref/migration-operations/)**
>
>   涵盖架构操作 API，特殊操作以及编写自己的操作。
>
> **[编写迁移的“方法”](/zh-hans/4.1/howto/writing-migrations/)**
>
>   介绍如何为你可能遇到的不同情况构建和编写数据库迁移。
