升级Django至2.2后Factory Boy的post_generation钩子无法设置模型属性
解决Factory Boy动态属性丢失的AttributeError问题
嘿,这个问题我升级Django 2.2的时候也踩过坑!来给你捋捋原因和解决办法:
问题根源
你碰到的AttributeError: 'ModelA' object has no attribute 'post_generated_objs'主要有这几个原因:
- 你的
ModelA数据库模型里并没有定义post_generated_objs字段,这个属性是通过Factory Boy的post_generation钩子动态添加到实例内存中的,并没有持久化到数据库。 - 升级Django 2.2后,当Factory Boy以
create=True模式创建ModelA实例(也就是保存到数据库),后续如果ORM重新从数据库加载这个实例(比如FactoryB中访问obj.model_a时),动态添加的属性会直接丢失——因为数据库里根本没有这个字段,重新加载的实例自然不会携带它。 - 另外,Factory Boy的
post_generation钩子是在实例保存到数据库之后才运行的,这意味着你设置的属性只存在于当时的内存实例上,一旦实例被刷新(比如调用refresh_from_db()),属性就没了。
可行的解决方案
方案1:修改ModelA,预初始化属性(简单直接)
在ModelA的__init__方法里预先初始化这个属性,这样不管实例是新建的还是从数据库加载的,都会有这个属性,不会触发AttributeError:
from django.db import models class ModelA(models.Model): name = models.CharField(max_length=255) # 你的现有字段 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 预先初始化属性,避免不存在的情况 self.post_generated_objs = []
方案2:利用Factory Boy的_results=True(无需改动模型)
如果你不想修改业务模型代码,可以用Factory Boy SubFactory的_results=True参数,直接捕获post_generation钩子返回的对象,完全绕开依赖模型实例属性的问题:
class FactoryA(factory.django.DjangoModelFactory): class Meta: model = ModelA name = 'John' @factory.post_generation def post_hook(self, create, extracted, **kwargs): post_generated_objs = [] for i in range(3): post_generated_objs.append(SomeOtherFactory(id=i+100)) # 这里依然可以设置属性,但我们后续不会依赖它 self.post_generated_objs = post_generated_objs # 关键:返回这些对象,后续通过SubFactory的结果获取 return post_generated_objs class FactoryB(factory.django.DjangoModelFactory): class Meta: model = ModelB # 使用_results=True,同时获取SubFactory创建的实例和post_generation的返回值 model_a, post_generated_objs = factory.SubFactory(FactoryA, _results=True) # 直接用捕获到的返回值,而不是从model_a实例读取 some_data = factory.LazyAttribute(lambda obj: obj.post_generated_objs[0])
这个方案更优雅,完全在测试工厂层解决问题,不需要改动业务模型,尤其适合测试场景下的临时数据关联。
另外,我注意到你的FactoryB代码里写的是obj.model_a.post_generated_objects[0],而FactoryA里设置的是post_generated_objs——如果这是笔误的话,也要记得修正哦!
内容的提问来源于stack exchange,提问作者Sudhanshu Mishra
相关产品推荐
相关产品推荐

