Getting the identifier of related child records in factory_boy

I have a function with a number of parameters , and then a specialized instantiation this function with some settings for each of the function parameters. Therefore, I have a structure similar to the following:

 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) 

In FunctionFactory I can use factory.RelatedFactory to create parameters .

But in FunctionInstantiationFactory I cannot use factory.RelatedFactory(ParameterSetting) to create ParameterSettings , because I do not have access to parameter objects created in FunctionFactory , so I can not set parameter_setting.function_parameter_id .

How does a FunctionInstantiationFactory find the parameter_id parameters created in a FunctionFactory ? Can I get the return value of RelatedFactory(FunctionFactory) from them? Or do I need to look at the database?

0
source share
2 answers

This is Xelnor's answer, but it fixes the error, so that only one function_instantiation , not one for each parameter / parameter_setting pair.

 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') 

Below are solutions to several other problems that anyone who uses this template will likely encounter, such as overriding the values ​​of related objects and links to other tables that are themselves related. It relies heavily on the methods that Xelnor presented in his answer.

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

Now I need to create a dataset with some data columns and connect the parameter records to these columns. For this, this happens at the end of 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() 

This is admittedly a bit of hacks. I tried to skip column=column_1 in column=column_1 calls, but this caused the creation of multiple datasets, with each column being associated with a different one. I tried all kinds of acrobatics using SelfAttribute and LazyAttribute, but you cannot use them in a RelatedFactory call, and you cannot create something with SubFactory (SelfAttribute ()) and then pass it to the "LinkedFactory" function, as this violates SelfAttribute (see my other question ).

In my real code, I had a few more models with a foreign key for the data set, and all this was well connected.

-1
source

factory.SubFactory is for; if you want to use it the other way around, you should use RelatedFactory instead.

In your example, I would go with the following factories:

 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')) 

And you can add an additional factory, FunctionWithParametersFactory , which creates parameters along:

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

The call that the factory will do the following:

  • Create a Function object (via FunctionFactory)
  • Call ParameterSettingFactory, pointing to the created Function object
  • Call ParameterSettingFactory a second time, still pointing to the same Function object
  • Gets this Function object.
+2
source

All Articles