FactoryBoy中post_generation无法访问关联模型对象的问题
解决FactoryBoy中Questionnaire关联Question与Dimension的分配问题
先明确模型结构(基于你的描述)
# models.py from django.db import models class Dimension(models.Model): name = models.CharField(max_length=100) class Questionnaire(models.Model): title = models.CharField(max_length=200) dimensions = models.ManyToManyField(Dimension, blank=True) class Question(models.Model): text = models.CharField(max_length=200) questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, related_name='question_set') dimension = models.ForeignKey(Dimension, on_delete=models.SET_NULL, null=True, blank=True)
核心问题分析
- post_generation中question_set为空:Trait里用
RelatedFactory创建的Question是在Questionnaire保存后才生成的,而post_generation钩子执行时,Question还未创建,所以self.question_set.all()返回空。 - Trait中直接设置Question.dimension失败:创建Question时,Questionnaire的dimensions多对多关系还未建立(或Dimension未添加),无法获取可用的Dimension对象。
解决方案1:在Question工厂中添加钩子自动分配Dimension
这种方式最灵活,能确保Question创建后自动从关联Questionnaire的dimensions中随机选一个分配:
# factories.py import factory import random from factory.django import DjangoModelFactory from .models import Dimension, Questionnaire, Question class DimensionFactory(DjangoModelFactory): class Meta: model = Dimension name = factory.Faker('word') class QuestionFactory(DjangoModelFactory): class Meta: model = Question text = factory.Faker('sentence') questionnaire = factory.SubFactory(QuestionnaireFactory) @factory.post_generation def auto_assign_dimension(self, create, extracted, **kwargs): if not create: return # 从关联问卷的维度列表中随机选一个分配 available_dims = list(self.questionnaire.dimensions.all()) if available_dims: self.dimension = random.choice(available_dims) self.save() class QuestionnaireFactory(DjangoModelFactory): class Meta: model = Questionnaire title = factory.Faker('sentence') # 批量添加维度的钩子 @factory.post_generation def dimensions(self, create, extracted, **kwargs): if not create: return if extracted: self.dimensions.add(*extracted) # 带问题的Trait class Trait: with_questions = factory.Trait( question_set=factory.RelatedFactoryList( QuestionFactory, size=3, factory_related_name='questionnaire' ) )
使用示例
# 先创建维度,再创建带维度和问题的问卷 dim1 = DimensionFactory() dim2 = DimensionFactory() questionnaire = QuestionnaireFactory(with_questions=True, dimensions=[dim1, dim2]) # 验证每个问题都分配了维度 for question in questionnaire.question_set.all(): assert question.dimension in [dim1, dim2]
解决方案2:自定义Trait同时创建维度和问题
如果需要一次性创建带维度和关联问题的问卷,可以写一个自定义Trait,确保维度先被添加,再创建问题并分配:
class QuestionnaireFactory(DjangoModelFactory): class Meta: model = Questionnaire title = factory.Faker('sentence') @classmethod def _create(cls, model_class, *args, **kwargs): instance = super()._create(model_class, *args, **kwargs) # 检查是否启用了带维度和问题的Trait if kwargs.get('with_dims_and_questions'): # 创建指定数量的维度并添加到问卷 dim_count = kwargs.get('dim_count', 2) dimensions = DimensionFactory.create_batch(dim_count) instance.dimensions.add(*dimensions) # 创建问题并随机分配维度 q_count = kwargs.get('question_count', 3) for _ in range(q_count): QuestionFactory( questionnaire=instance, dimension=random.choice(dimensions) ) return instance class Trait: with_dims_and_questions = factory.Trait()
使用示例
# 创建带2个维度、3个问题的问卷,每个问题自动分配维度 questionnaire = QuestionnaireFactory(with_dims_and_questions=True, dim_count=2, question_count=3)
关键注意事项
RelatedFactory/RelatedFactoryList创建的关联对象晚于主对象保存,所以主对象的post_generation钩子无法直接获取到这些关联对象。- 要访问主对象的多对多字段,必须确保多对多关系在关联对象创建前就已建立,或者在关联对象的
post_generation钩子中处理(此时主对象已完全创建,多对多字段可访问)。
内容的提问来源于stack exchange,提问作者sean
相关产品推荐
相关产品推荐

