假设我有以下型号:
class Product(models.Model):
name = models.CharField(max_length=128)
is_retired = models.BooleanField(default=False)
我想删除
is_retired
字段。我正在使用蓝绿部署来发布对生产的更改,因此我在迁移中使用 SeparateDatabaseAndState。
我从应用程序状态中删除该字段的初始迁移很简单:
class Migration(migrations.Migration):
dependencies = ...
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name="product",
name="is_retired",
),
],
database_operations=[],
),
]
我可以成功运行迁移。但是,当我尝试使用
Product.objects.create(name="Wrench")
创建新产品时,出现以下错误:
self = <django.db.backends.sqlite3.base.SQLiteCursorWrapper object at 0x106bf4050>
query = 'INSERT INTO "core_product" ("name") VALUES (?) RETURNING "core_product"."id"'
params = ('Wrench',)
def execute(self, query, params=None):
if params is None:
return super().execute(query)
# Extract names if params is a mapping, i.e. "pyformat" style is used.
param_names = list(params) if isinstance(params, Mapping) else None
query = self.convert_query(query, param_names=param_names)
> return super().execute(query, params)
E django.db.utils.IntegrityError: NOT NULL constraint failed: core_product.is_retired
看起来
INSERT
查询失败,因为 is_retired
字段默认为 null
,而不是正确的默认值 False
。
我通过在将
Product.is_retired
从状态中删除之前使其可为空来修复它:
class Migration(migrations.Migration):
dependencies = ...
operations = [
migrations.AlterField(
model_name="product",
name="is_retired",
field=models.BooleanField(default=False, null=True),
),
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name="product",
name="is_retired",
),
],
database_operations=[],
),
]
现在我可以使用
Product.objects.create(name="Wrench")
成功创建产品了。
几个问题:
is_retired
默认值更改回 False
,但无法弄清楚。在将 is_retired 字段从状态中删除之前将其设置为空的方法是解决您遇到的问题的有效方法。不过,让我们直接解决您的问题:
你的做法是明智的。通过在将字段从状态中删除之前将其设置为可为空,可以避免在插入新记录时出现 NOT NULL 约束失败。另一种方法可能涉及在运行迁移之前手动设置数据库中字段的默认值以将其从状态中删除,但这可能涉及更多手动数据库操作,并且可能容易出错。
将字段从不可为空更改为可空通常应该是安全的,特别是如果您的数据库可以正常处理空值。就您而言,由于您使用的是蓝绿部署策略,因此可以进一步降低风险,因为您可以在将流量切换到单独的环境之前测试更改。但是,彻底测试此类更改以确保它们不会对应用程序的行为产生意外后果始终是一个好习惯。