Django将OneToOneField转为ForeignKey时如何正确移除唯一约束
解决Django修改OneToOneField为ForeignKey时唯一约束无法自动移除的问题
Django默认不会自动识别OneToOneField转ForeignKey的操作对应的唯一约束移除需求,需要手动在迁移文件中补充逻辑,无需硬编码约束名的动态实现方案如下:
推荐方案:动态获取约束名(兼容所有数据库,无需硬编码)
直接修改自动生成的迁移文件,在原有操作前新增约束移除逻辑即可,完整代码如下:
from django.db import migrations, models def remove_owner_unique_constraint(apps, schema_editor): # 从迁移上下文中获取当前版本的Account模型 Account = apps.get_model('acc', 'Account') owner_field = Account._meta.get_field('owner') # 自动获取字段对应的唯一约束名,适配不同数据库的命名规则 real_constraint_name = schema_editor._constraint_names( Account, [owner_field.column], unique=True )[0] # 构造约束对象调用原生移除方法,自动适配不同数据库的SQL语法 unique_constraint = models.UniqueConstraint( fields=[owner_field.name], name=real_constraint_name ) schema_editor.remove_constraint(Account, unique_constraint) class Migration(migrations.Migration): dependencies = [ ('acc', '0018_auto_20211113_2246'), ] operations = [ # 必须先移除唯一约束,再修改字段类型 migrations.RunPython( remove_owner_unique_constraint, # 回滚操作占位,不需要回滚时保留noop即可避免报错 migrations.RunPython.noop ), # 以下为自动生成的原有操作,无需修改 migrations.AlterField( model_name='account', name='owner', field=models.ForeignKey(null=True, on_delete=models.deletion.PROTECT, related_name='accounts', to='acc.owner'), ), migrations.AlterField( model_name='account', name='internal_account', field=models.BooleanField(blank=True, default=True, editable=False, null=True), ), migrations.AlterField( model_name='historicalaccount', name='internal_account', field=models.BooleanField(blank=True, default=True, editable=False, null=True), ), ]
备选方案:硬编码约束名(仅适合测试/小项目使用)
如果确认项目不会切换数据库,也可以手动查询约束名后直接调用RemoveConstraint操作:
- 进入数据库执行对应命令查询约束名:
- MySQL:
SHOW CREATE TABLE acc_account; - PostgreSQL:
\d acc_account
- MySQL:
- 在迁移操作开头新增以下代码即可:
migrations.RemoveConstraint( model_name='account', name='你查询到的唯一约束名,格式一般为acc_account_owner_id_xxxxx' ),
注意事项
- 操作顺序不能调换,必须先移除唯一约束再执行AlterField修改字段类型,否则迁移会报错
- 生产环境执行迁移前建议先在测试环境验证,确认无数据冲突后再上线
- 历史表(HistoricalAccount)默认不会对OneToOneField加唯一约束,无需额外处理
内容的提问来源于stack exchange,提问作者gawry
相关产品推荐
相关产品推荐

