Django2.2 PostgreSQL下IntegerField转DurationField迁移报错求解
问题原因
PostgreSQL中Django的IntegerField对应数据库的integer类型,DurationField对应interval类型,数据库本身不支持integer到interval的隐式/无规则强转,Django默认生成的AlterField迁移只会直接执行字段类型修改的SQL,没有附带类型转换逻辑,所以会抛出psycopg2.errors.CannotCoerce: cannot cast type integer to interval错误。
解决方案
两种可落地的方案,按需选择即可。
方案1:多步迁移(兼容性最好,适合所有场景)
核心逻辑是通过临时字段做中转,完成数据转换后再替换原字段,全程不会触发类型强转错误,步骤如下:
- 第一步:新增临时字段
在原有模型中保留原整数字段的基础上,新增一个null=True的DurationField临时字段,示例模型:
执行from django.db import models class Record(models.Model): # 原字段,假设存储的是时长对应的秒数整数 spend_time = models.IntegerField() # 新增临时字段 spend_time_tmp = models.DurationField(null=True)python manage.py makemigrations和python manage.py migrate完成字段新增。 - 第二步:编写数据迁移完成历史数据转换
执行命令创建空迁移文件:
编辑生成的空迁移文件,添加数据转换逻辑:python manage.py makemigrations --empty 你的app名称
执行from django.db import migrations from datetime import timedelta def forward_convert(apps, schema_editor): Record = apps.get_model('你的app名称', 'Record') # 批量转换数据,原字段存毫秒就换成timedelta(milliseconds=obj.spend_time) for obj in Record.objects.all(): obj.spend_time_tmp = timedelta(seconds=obj.spend_time) obj.save(update_fields=['spend_time_tmp']) def backward_convert(apps, schema_editor): # 回滚逻辑:把duration转回整数 Record = apps.get_model('你的app名称', 'Record') for obj in Record.objects.all(): obj.spend_time = int(obj.spend_time_tmp.total_seconds()) obj.save(update_fields=['spend_time']) class Migration(migrations.Migration): dependencies = [ # 保留文件自动生成的依赖项,即上一步新增临时字段的迁移 ('你的app名称', 'xxxx_上一步迁移的文件名标识'), ] operations = [ migrations.RunPython(forward_convert, backward_convert), ]python manage.py migrate完成历史数据转换。 - 第三步:删除原字段,重命名临时字段
修改模型,删除原IntegerField,把临时的DurationField重命名为原字段名:
执行from django.db import models class Record(models.Model): # 替换为目标DurationField,字段名和原字段保持一致 spend_time = models.DurationField()python manage.py makemigrations,确认生成的迁移逻辑是删除原整数字段、重命名字段后,执行python manage.py migrate即可完成全部修改。
方案2:单迁移文件修改(更快捷,适合数据量不大的场景)
不需要新增临时字段,直接修改Django自动生成的AlterField迁移,手动指定类型转换规则即可:
- 先直接修改模型,把原
IntegerField替换为DurationField,执行python manage.py makemigrations生成AlterField迁移文件,先不要执行migrate。 - 编辑刚生成的迁移文件,把默认的
AlterField操作替换为带类型转换规则的原生SQL:
from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('你的app名称', 'xxxx_上一步迁移的文件名标识'), ] operations = [ # 注释掉自动生成的AlterField操作 # migrations.AlterField(...) migrations.RunSQL( # 正向迁移SQL:用make_interval完成整数到interval的转换,存毫秒就写secs => spend_time/1000.0 sql="ALTER TABLE 你的app名称_record ALTER COLUMN spend_time TYPE interval USING make_interval(secs => spend_time);", # 反向回滚SQL:把interval转回整数秒,存毫秒就写EXTRACT(EPOCH FROM spend_time)*1000::integer reverse_sql="ALTER TABLE 你的app名称_record ALTER COLUMN spend_time TYPE integer USING EXTRACT(EPOCH FROM spend_time)::integer;" ) ]
- 执行
python manage.py migrate即可完成修改。
注意:无论使用哪种方案,执行迁移前务必备份数据库,避免数据转换规则写错导致历史数据丢失。
内容的提问来源于stack exchange,提问作者paralosreg
相关产品推荐
相关产品推荐

