获取 factory_boy 中关联子记录的 ID

Getting id of associated child records in factory_boy

我有一个 function 和许多 parameters,然后是该函数的一个专用 instantiation,每个函数的参数都有一些 settings。所以我有如下结构:

class Function(models.Model):
    name = models.CharField()

class FunctionParameter(models.Model):
    function = models.ForeignKey(Function)

class FunctionInstantiation(models.Model):
    function = models.ForeignKey(Function)

class ParameterSetting(models.Model):
    function_instantiation = models.ForeignKey(FunctionInstantiation)
    function_parameter = models.ForeignKey(FunctionParameter)

FunctionFactory 中,我可以使用 factory.RelatedFactory 来创建 parameters

但在 FunctionInstantiationFactory 中我无法使用 factory.RelatedFactory(ParameterSetting) 创建 ParameterSettings,因为我无权访问在 [=16] 中创建的 parameter 对象=], 所以我无法设置 parameter_setting.function_parameter_id.

如何FunctionInstantiationFactory查找在FunctionFactory中创建的参数parameter_id?我可以从 RelatedFactory(FunctionFactory) 的 return 值得到它们吗?还是需要看数据库?

factory.SubFactory 旨在 跟随 一个 ForeignKey;如果你想反过来使用它,你应该使用 RelatedFactory 代替。

以你的例子为例,我会选择以下工厂:

class FunctionFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Function
    name = factory.Sequence(lambda n: "Function %d" % n)


class FunctionParameterFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionParameter
    function = factory.SubFactory(FunctionFactory)


class FunctionInstantiationFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionInstantiation
    function = factory.SubFactory(FunctionFactory)


class ParameterSettingFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.ParameterSetting
        exclude = ['function']

    # We'll need a FunctionFactory; this field is part of 'exclude',
    # thus available while building the factory but not passed to the
    # target Django model
    function = factory.SubFactory(FunctionFactory)

    # Use the function from our Factory for both
    # function_instantiation and function_parameter
    function_instantiation = factory.SubFactory(FunctionInstantiationFactory,
        function=factory.SelfAttribute('..function'))
    function_parameter = factory.SubFactory(FunctionParameterFactory,
        function=factory.SelfAttribute('..function'))

并且您可以添加一个额外的工厂,FunctionWithParametersFactory,它创建参数:

class FunctionWithParametersFactory(FunctionFactory):
    parameter1 = factory.RelatedFactory(ParameterSettingFactory, 'function')
    parameter2 = factory.RelatedFactory(ParameterSettingFactory, 'function')

调用该工厂将执行以下操作:

  1. 创建函数对象(通过 FunctionFactory)
  2. 调用ParameterSettingFactory,将其指向创建的Function对象
  3. 第二次调用 ParameterSettingFactory,仍然指向同一个 Function 对象
  4. Return 函数对象。

这是 Xelnor 的答案,但修复了错误,以便只创建一个 function_instantiation,而不是每个 parameter/parameter_setting 对创建一个。

class FunctionFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Function
    name = factory.Sequence(lambda n: "Function %d" % n)


class FunctionParameterFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionParameter
    function = factory.SubFactory(FunctionFactory)


class FunctionInstantiationFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionInstantiation
    function = factory.SubFactory(FunctionFactory)


class ParameterSettingFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.ParameterSetting

    function_instantiation = factory.SubFactory(FunctionInstantiationFactory)
    function_parameter = factory.SubFactory(FunctionParameterFactory,
        function=factory.SelfAttribute('..function_instantiation.function'))


class FunctionToParameterSettingsFactory(FunctionInstantiationFactory):
    class Meta:
        model = models.FunctionInstantiation

    # This overrides the function_instantiation created inside
    # ParameterSettingFactory, which then overrides the Function creation,
    # with the SelfAttribute('..function_instantiation.function') syntax.
    parameter_setting_1 = factory.RelatedFactory(ParameterSettingFactory, 
        'function_instantiation')
    parameter_setting_2 = factory.RelatedFactory(ParameterSettingFactory, 
        'function_instantiation')

下面演示了使用此模式的任何人可能会遇到的其他一些问题的解决方案,例如覆盖相关对象的值,以及链接到其他表,它们本身已链接。它主要借鉴了 Xelnor 在他的回答中介绍的技术。

class FunctionFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Function
    name = factory.Sequence(lambda n: "Function %d" % n)


class FunctionParameterFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionParameter
    name = factory.Sequence(lambda n: "Function %d" % n)
    function = factory.SubFactory(FunctionFactory)


class ParameterSettingFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.ParameterSetting
    name = factory.Sequence(lambda n: "Function %d" % n)

    function_instantiation = factory.SubFactory(FunctionInstantiationFactory)
    function_parameter = factory.SubFactory(FunctionParameterFactory,
        function=factory.SelfAttribute('..function_instantiation.function'))


class DatasetAnd2ColumnsFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Function
    dataset = factory.SubFactory(DatasetFactory,
        name=factory.Sequence(lambda n: "Custom dataset %d" % n))
    column_1 = factory.SubFactory(ColumnFactory, dataset=dataset,
        name=factory.Sequence(lambda n: "Column 1 %d" % n))
    column_2 = factory.SubFactory(ColumnFactory, dataset=dataset,
        name=factory.Sequence(lambda n: "Column 2 %d" % n))


# I found it neater not to inherit in the end, due to needing quite a lot of
# additional complexity not included in my original question.
class FunctionToParameterSettingsFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.FunctionInstantiation

    name = factory.Sequence(lambda n: "Custom instantiation name %d" % n)
    # You can call Sequence to pass values to SubFactories
    function = factory.SubFactory(FunctionFactory, 
        name=factory.Sequence(lambda n: "Custom function %d" % n))

    parameter_setting_1 = factory.RelatedFactory(ParameterSettingFactory, 
        'function_instantiation',
        # Note the __ syntax for override values for nested objects:
        parameter__name='Parameter 1',
        name='Parameter Setting 1')
    # Possible to use Sequence here too, and makes looking at data easier
    parameter_setting_2 = factory.RelatedFactory(ParameterSettingFactory, 
        'function_instantiation',
        parameter__name=factory.Sequence(lambda n: "Param 1 for fn %d" % n),
        name=factory.Sequence(lambda n: "Param Setting 1 for fn %d" % n))

我现在需要创建一个包含一些数据列的数据集,并将 parameter_setting 记录与这些列连接起来。为此,请在 FunctionToParameterSettingsFactory:

的末尾进行
@factory.post_generation
def post(self, create, extracted, **kwargs):
    if not create:
         return

    dataset = DatasetAnd2ColumnsFactory()
    column_ids_by_name = 
        dict((column.name, column.id) for column in dataset.column_set.all())

    # self is the `FunctioInstantiation` Django object just created by the `FunctionToParameterSettingsFactory`
    for parameter_setting in self.parametersetting_set.all():
        if parameter_setting.name == 'age_in':
            parameter_setting.column_id = column_ids_by_name['Age']
            parameter_setting.save()
        elif parameter_setting.name == 'income_in':
            parameter_setting.column_id = column_ids_by_name['Income']
            parameter_setting.save()

诚然,这有点老套。我尝试在 RelatedFactory 调用中传递 column=column_1,但这触发了多个数据集的创建,每一列都链接到不同的列。我尝试了各种使用 SelfAttribute 和 LazyAttribute 的杂技,但是你不能在 RelatedFactory 调用中使用任何一个,你也不能使用 SubFactory(SelfAttribute()) 创建一些东西然后将它传递给 RelatedFactory,因为这会破坏 SelfAttribute(请参阅my other question).

在我的真实代码中,我还有几个带有数据集外键的模型,它们都很好。