Django表单单元测试中无法初始化数据库填充的选择字段
解决Django表单选择字段在单元测试中为空的问题
这个问题我之前也碰到过,核心原因是Django的表单类在模块加载阶段就会被解析,你原来的代码里CLASS_CHOICE是在TestForm类定义时就执行数据库查询生成的,这时候测试数据库还没初始化,后续在setUpClass里创建的Class实例根本不会被包含进去,导致表单验证失败。
下面给你两种可行的解决方案,按需选择:
方案一:用ModelChoiceField动态生成选项(推荐)
Django提供的ModelChoiceField本身就是用来从数据库模型动态获取选项的,我们可以通过label_from_instance自定义显示文本,完全替代手动生成choices的逻辑:
class TestForm(forms.Form): name = forms.CharField() # 注意:不要用'class'作为字段名,这是Python关键字会报错,改成class_field class_field = forms.ModelChoiceField( queryset=Class.objects.filter(std__gte=4), empty_label=None, # 如果不需要空选项可以加上这句 to_field_name='code' # 指定提交时用code作为表单值,而非默认的模型ID ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 自定义选项的显示文本格式 self.fields['class_field'].label_from_instance = lambda obj: f"{obj.code} - {obj.std}" def _post_clean(self): # 你的验证逻辑 pass
对应的单元测试代码只需要把字段名改成class_field即可:
class SampleTest(TestCase): @classmethod def setUpClass(cls): super().setUpClass() # 同样不要用cls.class,改成cls.test_class避免关键字冲突 cls.test_class = Class.objects.create(std=10, code='A') def test_valid_form(self): post_data = {'name':'testing', 'class_field':'A' } f = TestForm(data=post_data) self.assertTrue(f.is_valid())
方案二:在表单__init__方法中动态设置ChoiceField的choices
如果你坚持要用ChoiceField,可以把生成choices的逻辑移到表单的__init__方法里,这样每次实例化表单时都会重新查询数据库,确保拿到最新的数据:
class TestForm(forms.Form): name = forms.CharField() class_field = forms.ChoiceField(choices=[]) # 初始设为空列表 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 每次创建表单实例时动态查询生成选项 classes = Class.objects.filter(std__gte=4) self.fields['class_field'].choices = [ (cls.code, f"{cls.code} - {cls.std}") for cls in classes ] def _post_clean(self): # 你的验证逻辑 pass
测试代码和上面一样,修改字段名后就能正常运行了。
两种方案的核心思路都是延迟数据库查询的时机,从类定义时提前执行,改成表单实例化时才执行,这样就能获取到测试中创建的数据库数据了。
内容的提问来源于stack exchange,提问作者bak2trak
相关产品推荐
相关产品推荐

