【问题标题】:Getting id of associated child records in factory_boy获取 factory_boy 中关联子记录的 id
【发布时间】:2015-10-07 14:37:04
【问题描述】:

我有一个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,因为我无权访问在FunctionFactory中创建的parameter对象,所以我不能设置parameter_setting.function_parameter_id .

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

【问题讨论】:

    标签: django python-3.x factory-boy associated-object


    【解决方案1】:

    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. 创建一个 Function 对象(通过 FunctionFactory)
    2. 调用ParameterSettingFactory,将其指向创建的Function对象
    3. 再次调用 ParameterSettingFactory,仍然指向同一个 Function 对象
    4. 返回该函数对象。

    【讨论】:

    • 抱歉,就在几秒钟前,我在您打字时用RelatedFactory 而不是SubFactory 更新了我的问题。刚刚在SelfAttribute 上阅读,但毫无希望地被困在function.parameter[0].id 上。我会花几分钟时间来理解你的代码,所以在我标记为答案之前请耐心等待。非常感谢,你可以告诉我,我是 factory_boy 及其同类的新手。
    • 一些注意事项,对于熟悉 factory_boy 的人来说是不言而喻的。 factory.SelfAttribute('.. 语法告诉 SubFactory (FunctionInstantiationFactory) 从调用工厂 (ParameterSettingFactory) 范围中获取命名对象 (function)。 exclude 阻止 factory_boy 尝试将 function 保存为 ParameterSetting 对象(没有该 FK)上的外键。 FunctionWithParametersFactory,通过从FunctionFactory 继承,首先创建Function,然后使用ParameterSettingFactory 创建其他三个。非常感谢@Xelnor 这教会了我很多东西。
    • 来自the docs 我认为function=factory.LazyAttribute(lambda function_parameter: function_parameter.factory_parent.function) 将是实现第二个function=factory.SelfAttribute('..function') 的替代(劣质)方式。 @Xelnor 的解决方案在各个方面都更好、更整洁,只是注意到一个相关的发现。
    【解决方案2】:

    这是 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))
    

    我现在需要创建一个包含一些数据列的数据集,并将参数设置记录与这些列连接起来。为此,请在 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()
    

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

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

    【讨论】:

    • 我的编辑被拒绝了,对不起@Xelnor,我试图把答案留给你。
    • 上一个代码示例中有一个错误。 dataset 被重复创建,因此 column_1column_2 获得不同的数据集(都具有覆盖的 name 字段)。如果我用dataset=factory.LazyAttribute(lambda col: col.factory_parent.dataset) 创建一个column_3,它工作正常。如果我随后将 column_3 添加到 ParameterSettingFactory 调用中,我会得到 AttributeError: The parameter dataset is unknown. 并且 factory_boy 只找到 ParameterSettingFactory 本地,而没有 factory_parent。
    • 我已经解决了上述问题,described here,并更新了答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多