You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 10:35:21