Pytest-Django导入表单时触发数据库访问不允许错误的求助
为什么导入时会触发数据库操作?
Python在导入模块时,会执行模块内的所有顶层代码——包括类的定义和类属性的初始化。你的MyForm类在定义时,直接调用了get_choices_from_db()来给my_choices字段赋值choices参数,这就导致导入表单模块的瞬间就执行了数据库查询。
而pytest的django_db标记是作用在测试函数上的,只有当测试函数开始执行时,才会启用数据库访问权限。模块导入发生在测试函数执行之前,这时候数据库访问还被禁止,所以抛出了RuntimeError。
把导入移到测试函数内部能正常运行,是因为此时测试函数已经激活了django_db标记,数据库权限已经开启,这时候执行get_choices_from_db()就不会报错。
合理解决方案
1. 让choices接受可调用对象(推荐)
Django的MultipleChoiceField支持将choices设置为一个可调用对象(不需要加括号执行),这样字段初始化时不会立即查询数据库,而是在表单实例化时才调用该函数获取选项。
修改表单代码:
class MyForm(forms.Form): my_choices = forms.MultipleChoiceField(choices=get_choices_from_db) # 去掉函数后的括号
这样修改后,无论什么时候导入表单类,都不会触发数据库操作,只有当你创建MyForm()实例时,才会执行get_choices_from_db()。测试时只要在标记了django_db的函数里实例化表单即可。
2. 在__init__方法中动态设置choices
如果需要更灵活的控制(比如根据表单实例的上下文调整选项),可以在表单的__init__方法中动态赋值choices:
class MyForm(forms.Form): my_choices = forms.MultipleChoiceField(choices=[]) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 实例化时才查询数据库 self.fields['my_choices'].choices = get_choices_from_db()
这种方式同样能避免导入时触发数据库操作,同时支持动态调整选项。
3. 临时方案:将导入移至测试函数内部
你已经验证过这种方法可行,但它属于临时 workaround——如果多个测试函数需要导入该表单,会重复执行导入逻辑,代码结构也不够清晰,不推荐作为长期解决方案。
内容的提问来源于stack exchange,提问作者Basil

