在生产 Django 部署中添加不可为空的字段
Adding a non-nullable field on a production Django deployment
我有一个带有 PostgreSQL 数据库的生产 Django 部署 (Django 1.11)。我想向我的模型之一添加一个不可为 null 的字段:
class MyModel(models.Model):
new_field = models.BooleanField(default=False)
为了部署,我需要先更新服务器上的代码或 运行 迁移,但因为这是生产部署,请求可以(并且将会)在我更新数据库之间发生和我更新服务器。如果我先更新服务器,我会得到一个OperationalError no such column
,所以我显然需要先更新数据库。
但是,当我第一次更新数据库时,在用新代码更新之前在服务器上发出的请求出现以下错误:
django.db.utils.IntegrityError: NOT NULL constraint failed: myapp_mymodel.new_field
从表面上看,这没有任何意义,因为该字段有一个默认值。进一步深入研究,似乎默认值是由 Django 逻辑单独提供的,实际上并未存储在 SQL 级别。如果服务器没有更新代码,它不会将列传递给 SQL 进行更新,SQL 将其解释为 NULL。
鉴于此,我如何将这个新的不可空字段部署到我的应用程序而不让我的用户收到任何错误?
通常,django 升级过程如下所示:
本地发展环境:
- 在本地更改模型
- 迁移模型(python manage.py makemigrations)
- 在本地测试您的更改
- 提交并将您的更改推送到 (git) 服务器
在生产服务器上:
- 设置环境参数
- 从您的版本控制系统中拉取(git fetch --all;git reset --hard origin/master)
- 更新 python 依赖项(例如 pip install -r requirements.txt)
- 迁移 (manage.py migrate_schemas)
- 更新静态文件(python manage.py collectstatic)
- 重新启动 django 服务器(取决于服务器,但可能类似于 'python manage.py runserver')
您可以从 NullBooleanField
开始:
- 将
new_field = models.NullBooleanField(default=False)
添加到您的模型中
- 使用 makemigrations 创建架构迁移 1
- 更改模型以具有
new_field = models.BooleanField(default=False)
- 使用 makemigrations 创建架构迁移 2
- 运行 架构迁移 1
- 更新生产代码
- 运行 架构迁移 2
如果旧的生产代码在第 5 步和第 6 步之间写入 table,将写入空值 new_field
。在第 6 步和第 7 步之间会有一段时间,BooleanField 可以有空值,当读取该字段时,它将为空。如果您的代码可以处理这个问题,那么您会没事的,然后第 7 步会将所有这些空值转换为 False。如果您的新代码无法处理这些空值,您可以执行以下步骤:
- 将
new_field = models.NullBooleanField(default=False)
添加到您的模型中
- 使用 makemigrations 创建架构迁移 1
- 运行 架构迁移 1
- 更新生产代码
- 更改模型以具有
new_field = models.BooleanField(default=False)
- 使用 makemigrations 创建架构迁移 2
- 运行 架构迁移 2
- 更新生产代码
*请注意,这些方法仅使用 Postgres 进行了测试。
迁移应始终 运行 在部署开始时进行,否则您会遇到其他问题。这个问题的解决方案是将更改拆分为两个部署。
在部署 1 中,该字段需要可以为空(NullBooleanField
或 null=True
)。您应该对处于此状态的代码进行迁移,并确保如果该字段的值为 None
,则您的其余代码不会崩溃。这是必要的,因为请求可以发送到还没有新代码的服务器;如果这些服务器创建模型的实例,它们将创建该模型且字段为空。
在部署 2 中,您将字段设置为不可为空,为此进行迁移,并删除您为处理字段值为 None
的情况编写的任何额外代码。如果该字段没有默认值,您为第二次部署进行的迁移将需要为该字段中具有 None
的对象填写值。
安全删除字段也需要两种部署技术,尽管它看起来有点不同。在第一次部署中,您从模型文件中删除了该字段,并从您的代码中删除了对它的所有引用,但是您没有部署默认迁移来删除该字段。相反,部署 1 具有将字段设置为可为空的自定义迁移。然后,在部署 2 中,部署迁移以实际从数据库中删除该字段。
我有一个带有 PostgreSQL 数据库的生产 Django 部署 (Django 1.11)。我想向我的模型之一添加一个不可为 null 的字段:
class MyModel(models.Model):
new_field = models.BooleanField(default=False)
为了部署,我需要先更新服务器上的代码或 运行 迁移,但因为这是生产部署,请求可以(并且将会)在我更新数据库之间发生和我更新服务器。如果我先更新服务器,我会得到一个OperationalError no such column
,所以我显然需要先更新数据库。
但是,当我第一次更新数据库时,在用新代码更新之前在服务器上发出的请求出现以下错误:
django.db.utils.IntegrityError: NOT NULL constraint failed: myapp_mymodel.new_field
从表面上看,这没有任何意义,因为该字段有一个默认值。进一步深入研究,似乎默认值是由 Django 逻辑单独提供的,实际上并未存储在 SQL 级别。如果服务器没有更新代码,它不会将列传递给 SQL 进行更新,SQL 将其解释为 NULL。
鉴于此,我如何将这个新的不可空字段部署到我的应用程序而不让我的用户收到任何错误?
通常,django 升级过程如下所示:
本地发展环境:
- 在本地更改模型
- 迁移模型(python manage.py makemigrations)
- 在本地测试您的更改
- 提交并将您的更改推送到 (git) 服务器
在生产服务器上:
- 设置环境参数
- 从您的版本控制系统中拉取(git fetch --all;git reset --hard origin/master)
- 更新 python 依赖项(例如 pip install -r requirements.txt)
- 迁移 (manage.py migrate_schemas)
- 更新静态文件(python manage.py collectstatic)
- 重新启动 django 服务器(取决于服务器,但可能类似于 'python manage.py runserver')
您可以从 NullBooleanField
开始:
- 将
new_field = models.NullBooleanField(default=False)
添加到您的模型中 - 使用 makemigrations 创建架构迁移 1
- 更改模型以具有
new_field = models.BooleanField(default=False)
- 使用 makemigrations 创建架构迁移 2
- 运行 架构迁移 1
- 更新生产代码
- 运行 架构迁移 2
如果旧的生产代码在第 5 步和第 6 步之间写入 table,将写入空值 new_field
。在第 6 步和第 7 步之间会有一段时间,BooleanField 可以有空值,当读取该字段时,它将为空。如果您的代码可以处理这个问题,那么您会没事的,然后第 7 步会将所有这些空值转换为 False。如果您的新代码无法处理这些空值,您可以执行以下步骤:
- 将
new_field = models.NullBooleanField(default=False)
添加到您的模型中 - 使用 makemigrations 创建架构迁移 1
- 运行 架构迁移 1
- 更新生产代码
- 更改模型以具有
new_field = models.BooleanField(default=False)
- 使用 makemigrations 创建架构迁移 2
- 运行 架构迁移 2
- 更新生产代码
*请注意,这些方法仅使用 Postgres 进行了测试。
迁移应始终 运行 在部署开始时进行,否则您会遇到其他问题。这个问题的解决方案是将更改拆分为两个部署。
在部署 1 中,该字段需要可以为空(NullBooleanField
或 null=True
)。您应该对处于此状态的代码进行迁移,并确保如果该字段的值为 None
,则您的其余代码不会崩溃。这是必要的,因为请求可以发送到还没有新代码的服务器;如果这些服务器创建模型的实例,它们将创建该模型且字段为空。
在部署 2 中,您将字段设置为不可为空,为此进行迁移,并删除您为处理字段值为 None
的情况编写的任何额外代码。如果该字段没有默认值,您为第二次部署进行的迁移将需要为该字段中具有 None
的对象填写值。
安全删除字段也需要两种部署技术,尽管它看起来有点不同。在第一次部署中,您从模型文件中删除了该字段,并从您的代码中删除了对它的所有引用,但是您没有部署默认迁移来删除该字段。相反,部署 1 具有将字段设置为可为空的自定义迁移。然后,在部署 2 中,部署迁移以实际从数据库中删除该字段。