向现有 Django 模型添加新的唯一字段时的最佳实践
Best practice when add a new unique field to an existing django model
我有一个看起来有点像下面的现有模型...
class Resource(models.Model):
id = models.AutoField(primary_key=True)
我们已经使用它一段时间了,现在我们的数据库中有大约 100 万个这些 Resource
对象的实例(以及相关的 ForeignKey/else 用法)。
我现在需要在此模型上跟踪另一个 ID,我想强制执行的 ID 是唯一的。
other_id = models.IntegerField(unique=True)
此 other_id
信息当前存储在一些外部 CSV 中,我想(在过程中的某个时刻)将此信息加载到所有现有的 Resource
实例中。
添加上面的字段后,Django的makemigrations
就可以正常工作了。但是,当我针对现有数据库应用所述迁移时,我收到一条错误消息,指示我需要提供默认值以用于所有现有 Resource
实例。相信很多人都见过类似的东西。
绕过此限制的最佳方法是什么?我想到的一些方法...
- 删除
unique=True
要求
- 应用迁移
- 将
other_id
值外部加载到所有现有模型(通过一些管理命令或 1-off 脚本)
- 重新添加
unique=True
并应用迁移
- 将所有现有数据转储到 JSON
- 刷新所有表
- 应用迁移(unique=True)
- 编写一个脚本来加载数据,添加正确的
other_id
值
- (不确定这是否可能)- 编写一些自定义迁移逻辑以在我 运行
manage.py migrate
时自动引用这些外部 CSV 以加载 other_id
值。如果(在将来的某个时候)有人重新运行这些迁移并且这部分失败(无法在 CSV 中找到现有资源 id
以提取 other_id
),这可能会遇到问题.
所有这些感觉都很复杂,但我想我想做的也不是最简单的事情。
有什么想法吗?我不得不想象过去有人不得不解决类似的问题。
谢谢!
实际上,来源或您的问题本身并不是唯一约束,而是您的字段不允许空值且没有默认值这一事实 - 您会遇到与 [=21= 完全相同的错误] 字段。
此处正确的解决方案是允许该字段为空 (null=True
) 并将其默认为 None
(这将转换为 sql "null")。由于 null
值被排除在唯一约束之外(至少如果您的数据库供应商遵守 SQL 标准),这允许您应用模式更改,同时仍然确保您不能为 non-null 值。
然后您可能希望进行数据迁移以加载已知的 "other_id" 值,并最终进行第三次架构迁移以禁止此字段的空值 - 当且仅当您知道您已为所有字段填充此字段时记录。
Django 有一个叫做 Data Migrations 的东西,你可以在其中创建一个迁移文件,当你应用迁移时,该文件 modifies/remove/add 数据到你的数据库。
在这种情况下,您将创建 3 个不同的迁移:
- 创建允许空值的迁移
null=True
。
- 创建填充数据的数据迁移。
- 通过删除步骤 1 中添加的
null=True
创建一个不允许空值的迁移。
然后 运行 python manage.py migrate
它将以正确的顺序应用步骤 1-3 中的所有迁移。
您的数据迁移看起来像这样:
from django.db import migrations
def populate_reference(apps, schema_editor):
MyModel = apps.get_model('yourappname', 'MyModel')
for obj in MyModel.objects.all():
obj.other_id = random_id_generator()
obj.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(populate_reference),
]
您可以使用 ./manage.py makemigrations --empty yourappname
命令创建一个空的迁移文件。
我有一个看起来有点像下面的现有模型...
class Resource(models.Model):
id = models.AutoField(primary_key=True)
我们已经使用它一段时间了,现在我们的数据库中有大约 100 万个这些 Resource
对象的实例(以及相关的 ForeignKey/else 用法)。
我现在需要在此模型上跟踪另一个 ID,我想强制执行的 ID 是唯一的。
other_id = models.IntegerField(unique=True)
此 other_id
信息当前存储在一些外部 CSV 中,我想(在过程中的某个时刻)将此信息加载到所有现有的 Resource
实例中。
添加上面的字段后,Django的makemigrations
就可以正常工作了。但是,当我针对现有数据库应用所述迁移时,我收到一条错误消息,指示我需要提供默认值以用于所有现有 Resource
实例。相信很多人都见过类似的东西。
绕过此限制的最佳方法是什么?我想到的一些方法...
- 删除
unique=True
要求 - 应用迁移
- 将
other_id
值外部加载到所有现有模型(通过一些管理命令或 1-off 脚本) - 重新添加
unique=True
并应用迁移
- 删除
- 将所有现有数据转储到 JSON
- 刷新所有表
- 应用迁移(unique=True)
- 编写一个脚本来加载数据,添加正确的
other_id
值
- (不确定这是否可能)- 编写一些自定义迁移逻辑以在我 运行
manage.py migrate
时自动引用这些外部 CSV 以加载other_id
值。如果(在将来的某个时候)有人重新运行这些迁移并且这部分失败(无法在 CSV 中找到现有资源id
以提取other_id
),这可能会遇到问题.
所有这些感觉都很复杂,但我想我想做的也不是最简单的事情。
有什么想法吗?我不得不想象过去有人不得不解决类似的问题。
谢谢!
实际上,来源或您的问题本身并不是唯一约束,而是您的字段不允许空值且没有默认值这一事实 - 您会遇到与 [=21= 完全相同的错误] 字段。
此处正确的解决方案是允许该字段为空 (null=True
) 并将其默认为 None
(这将转换为 sql "null")。由于 null
值被排除在唯一约束之外(至少如果您的数据库供应商遵守 SQL 标准),这允许您应用模式更改,同时仍然确保您不能为 non-null 值。
然后您可能希望进行数据迁移以加载已知的 "other_id" 值,并最终进行第三次架构迁移以禁止此字段的空值 - 当且仅当您知道您已为所有字段填充此字段时记录。
Django 有一个叫做 Data Migrations 的东西,你可以在其中创建一个迁移文件,当你应用迁移时,该文件 modifies/remove/add 数据到你的数据库。
在这种情况下,您将创建 3 个不同的迁移:
- 创建允许空值的迁移
null=True
。 - 创建填充数据的数据迁移。
- 通过删除步骤 1 中添加的
null=True
创建一个不允许空值的迁移。
然后 运行 python manage.py migrate
它将以正确的顺序应用步骤 1-3 中的所有迁移。
您的数据迁移看起来像这样:
from django.db import migrations
def populate_reference(apps, schema_editor):
MyModel = apps.get_model('yourappname', 'MyModel')
for obj in MyModel.objects.all():
obj.other_id = random_id_generator()
obj.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(populate_reference),
]
您可以使用 ./manage.py makemigrations --empty yourappname
命令创建一个空的迁移文件。