Django测试时出现Migration列不存在ProgrammingError问题
问题:pytest测试Django应用时迁移报错(列不存在)
在使用pytest测试Django应用时,执行移除早期迁移字段的操作后,触发django.db.utils.ProgrammingError错误,提示sample_testmodel.original_text列不存在,但非测试环境下无此问题。未添加移除字段的0003迁移时测试正常;注释0002迁移中的RunPython语句可临时解决,但非长久之计。
复现代码环境
models.py
from django.db import models class TestModel(models.Model): # We started with this field, but now longer need it # original_text = models.CharField(max_length=255) # We added this field and then moved over to it new_text = models.CharField(max_length=511)
migrations/0001_initial.py
from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [] operations = [ migrations.CreateModel( name='TestModel', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('original_text', models.CharField(max_length=255)), ], ), ]
migrations/0002_testmodel_new_text.py
from django.db import migrations, models def move_text_to_other_field_and_append_new(apps, schema_editor): TestModel = apps.get_model("sample", "TestModel") for test_model in TestModel.objects.all(): test_model.new_text = test_model.original_text + " new" test_model.save() class Migration(migrations.Migration): dependencies = [('sample', '0001_initial')] operations = [ migrations.AddField( model_name='testmodel', name='new_text', field=models.CharField(default='', max_length=511), preserve_default=False, ), migrations.RunPython(move_text_to_other_field_and_append_new, migrations.RunPython.noop) ]
migrations/0003_remove_testmodel_original_text.py
from django.db import migrations class Migration(migrations.Migration): dependencies = [('sample', '0002_testmodel_new_text')] operations = [ migrations.RemoveField( model_name='testmodel', name='original_text', ), ]
tests.py
from django.test import TestCase class TestModelTestCase(TestCase): def test_example(self): self.assertEqual(1, 1)
问题原因
- 测试环境的迁移优化机制:Django在测试时会自动合并所有迁移文件,生成一个"压缩版"的最终表结构(直接创建不含
original_text的TestModel表),但合并过程中依然会保留并执行0002迁移里的RunPython数据迁移函数。此时数据库中根本不存在original_text字段,函数执行时自然报错。 - 非测试环境的迁移逻辑:生产/开发环境会按顺序执行每一个迁移文件:先通过0001创建带
original_text的表,0002添加new_text并迁移数据,最后0003删除original_text,每一步的表结构都和迁移逻辑匹配,因此不会出现错误。
解决方法
方法1:给数据迁移函数添加字段存在性检查
修改0002迁移中的RunPython函数,先判断模型是否存在目标字段,再执行数据迁移逻辑:
def move_text_to_other_field_and_append_new(apps, schema_editor): TestModel = apps.get_model("sample", "TestModel") # 检查字段是否存在,避免测试环境合并迁移后报错 if hasattr(TestModel, 'original_text'): for test_model in TestModel.objects.all(): test_model.new_text = test_model.original_text + " new" test_model.save()
这种方法适配所有环境,是最稳妥的解决方案。
方法2:使用--keepdb参数运行pytest
测试时保留现有数据库,不重新创建,让迁移按顺序执行(和非测试环境逻辑一致):
pipenv run pytest sample/tests.py --keepdb
该方法适合本地开发测试,但CI/CD等需要全新数据库的场景不适用。
内容的提问来源于stack exchange,提问作者J-Star
相关产品推荐
相关产品推荐

