Django将抽象Symbol模型改为非抽象并调整外键的迁移问题
解决Django抽象模型改非抽象+外键迁移的数据保留问题
我之前也遇到过一模一样的场景,把抽象模型改成非抽象时,Django的多表继承机制会自动给子类添加symbol_ptr外键字段,但现有数据没有对应的Symbol记录,所以才会弹出这个缺少默认值的提示。下面是一步步不丢失数据的解决方法:
步骤1:处理Symbol从抽象到非抽象的迁移,迁移现有DetailSymbol1的field1数据到Symbol表
1.1 临时修改模型,允许symbol_ptr为空
首先我们先让Django能顺利生成迁移,暂时手动给DetailSymbol1添加允许为空的symbol_ptr字段:
class Symbol(BaseModel): field1 = models.CharField(max_length=50) class DetailSymbol1(Symbol): field2 = models.CharField(max_length=50) # 手动添加这个字段,暂时允许为空,后续会去掉 symbol_ptr = models.ForeignKey(Symbol, null=True, on_delete=models.CASCADE, editable=False) class DailyData(BaseModel): symbol = models.ForeignKey(DetailSymbol1, on_delete=models.CASCADE) field3 = models.CharField(max_length=50)
1.2 生成并执行初始迁移
运行以下命令生成迁移,这时候不会再提示缺少默认值:
python manage.py makemigrations python manage.py migrate
1.3 创建数据迁移,把DetailSymbol1的field1迁移到Symbol表
生成一个空的迁移文件:
python manage.py makemigrations --empty your_app_name
打开生成的空迁移文件,添加数据迁移函数,把每个DetailSymbol1的field1提取出来创建对应的Symbol记录,并关联symbol_ptr:
from django.db import migrations def migrate_detail_to_symbol(apps, schema_editor): Symbol = apps.get_model('your_app_name', 'Symbol') DetailSymbol1 = apps.get_model('your_app_name', 'DetailSymbol1') # 遍历所有DetailSymbol1,创建对应的Symbol并关联 for detail in DetailSymbol1.objects.all(): symbol = Symbol.objects.create(field1=detail.field1) detail.symbol_ptr = symbol detail.save() class Migration(migrations.Migration): dependencies = [ # 这里替换成上一步生成的迁移文件名,比如'your_app_name.0002_auto_xxxxxx' ('your_app_name', '0002_auto_20240520_1234'), ] operations = [ migrations.RunPython(migrate_detail_to_symbol), ]
执行这个数据迁移:
python manage.py migrate
1.4 把symbol_ptr设为非空,恢复多表继承的默认结构
现在所有DetailSymbol1都有对应的Symbol记录了,我们可以去掉手动添加的symbol_ptr字段,回到原始模型:
class Symbol(BaseModel): field1 = models.CharField(max_length=50) class DetailSymbol1(Symbol): field2 = models.CharField(max_length=50) class DailyData(BaseModel): symbol = models.ForeignKey(DetailSymbol1, on_delete=models.CASCADE) field3 = models.CharField(max_length=50)
生成并执行迁移,这时候Django会自动把symbol_ptr字段设为非空:
python manage.py makemigrations python manage.py migrate
步骤2:修改DailyData的外键为Symbol,迁移现有数据
2.1 修改DailyData的模型外键
更新模型,把DailyData的外键从DetailSymbol1改为Symbol:
class DailyData(BaseModel): symbol = models.ForeignKey(Symbol, on_delete=models.CASCADE) field3 = models.CharField(max_length=50)
2.2 生成并编辑迁移文件
运行makemigrations,当提示是否提供默认值时,选择2) Quit:
python manage.py makemigrations
打开生成的迁移文件,添加数据迁移函数,把现有DailyData的symbol字段从DetailSymbol1关联到对应的Symbol记录:
from django.db import migrations def update_dailydata_symbol(apps, schema_editor): DailyData = apps.get_model('your_app_name', 'DailyData') DetailSymbol1 = apps.get_model('your_app_name', 'DetailSymbol1') for daily in DailyData.objects.all(): # 关联到DetailSymbol1对应的Symbol daily.symbol = daily.symbol.symbol_ptr daily.save() class Migration(migrations.Migration): dependencies = [ # 替换成上一步的迁移文件名 ('your_app_name', '0004_auto_20240520_1235'), ] operations = [ # 先执行数据更新,再修改字段类型 migrations.RunPython(update_dailydata_symbol), migrations.AlterField( model_name='dailydata', name='symbol', field=models.ForeignKey(on_delete=models.db.models.deletion.CASCADE, to='your_app_name.Symbol'), ), ]
2.3 执行最终迁移
运行迁移完成所有修改:
python manage.py migrate
验证数据
现在可以进入Django shell验证数据是否完整:
python manage.py shell
from your_app_name.models import DetailSymbol1, DailyData, Symbol # 查看Symbol数量,应该和原来的DetailSymbol1数量一致 print(Symbol.objects.count()) # 应该是1 # 查看DailyData数量,还是2 print(DailyData.objects.count()) # 应该是2 # 验证关联关系 a = DetailSymbol1.objects.first() print(a.symbol_ptr.field1) # 输出"a" print(a.dailydata_set.all()[0].symbol.field1) # 输出"a"
这样就完成了所有模型修改,同时保留了所有原有数据。
内容的提问来源于stack exchange,提问作者user3595632
相关产品推荐
相关产品推荐

