Django+Postgres测试时Slug字段触发非空约束错误问题
问题背景
我定义了这样的Django Job模型:
class Job(models.Model): # 其他字段 slug = models.SlugField(verbose_name=_('slug'), max_length=151, null=True, blank=True) def get_absolute_url(self): return reverse('jobs', kwargs={'slug': self.slug})
Slug字段明明设置了允许空值,在Shell里创建实例完全正常:
In [1]: j = Job.objects.create(title='my faked title',date_start=aware_start, date_end=aware_end, amount_to_pay=10, email='fake@gmail.com') In [2]: j Out[2]: <Job: my faked title>
但在测试用例里创建实例却直接报错,测试代码是这样的:
class JobCreationTest(TestCase): def test_simple_creation(self): import datetime import pytz aware_start = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC) aware_end = datetime.datetime(2012, 8, 15, 8, 15, 12, 0, pytz.UTC) Job.objects.create(title='my faked title',date_start=aware_start, date_end=aware_end, amount_to_pay=10, email='fake@gmail.com')
报错信息如下:
django.db.utils.IntegrityError: null value in column 'slug' violates not-null constraint
DETAIL: Failing row contains (e814e054-e273-4316-b670-f39584f0b3ef, 2019-03-19 09:39:31.031514+00, null, 2019-03-19 09:39:31.031566+00, null, f, my faked title, fake@gmail.com, , null, null, null, null, null, null, null, null, 2011-08-15 08:15:12+00, 2012-08-15 08:15:12+00, null, null, null, , , 10, null).
我尝试过执行迁移命令,但没有效果:
$ ./manage.py makemigrations posts No changes detected in app 'posts' $ ./manage.py migrate posts No migrations to apply.
去psql里查看表定义,slug字段确实是允许为空的:
\d+ posts_job
Table "public.posts_job"
Column | Type | Collation | Nullable |
----------------------+--------------------------+-----------+----------+-
id | uuid | | not null |
title | character varying(128) | | not null |
email | character varying(254) | | not null |
slug | character varying(151) | | |
问题分析
从数据库表定义来看,slug字段的Nullable列是空的,说明数据库层面确实允许空值,但测试环境却抛出了非空约束错误,大概率是测试用的临时数据库没有正确应用迁移——Django运行测试时会自动创建一个临时数据库,如果迁移文件有问题或者测试启动时迁移没有正确执行,就会出现这种模型定义和数据库结构不一致的情况。
解决方案
这里有几个可以尝试的解决步骤:
强制测试环境重新应用迁移:可以运行测试时加上
--keepdb参数先清空测试数据库,再重新创建:./manage.py test --keepdb posts.tests.JobCreationTest如果还是不行,直接删除测试数据库(比如postgres里的
test_xxx库),再重新运行测试。重新生成迁移文件:有时候迁移文件会和模型定义不同步,哪怕
makemigrations提示没有变化。可以先删除posts/migrations/下除了__init__.py之外的所有文件,然后重新生成并应用迁移:rm posts/migrations/0*.py ./manage.py makemigrations posts ./manage.py migrate posts之后再运行测试,看看问题是否解决。
显式设置slug为None:在测试用例创建实例时,手动指定
slug=None,确认是否还会报错:Job.objects.create( title='my faked title', date_start=aware_start, date_end=aware_end, amount_to_pay=10, email='fake@gmail.com', slug=None )如果这样不报错,说明测试环境的某个前置操作可能意外修改了slug字段的约束。
检查测试数据库配置:确保
settings.py里的TEST数据库配置和主数据库一致,比如是否用了不同的引擎,或者有特殊的约束设置。
内容的提问来源于stack exchange,提问作者Gonzalo

