Getting Id Of Associated Child Records In Factory_boy
Solution 1:
The factory.SubFactory
is intended to follow a ForeignKey
; if you want to use it the other way around, you should use a RelatedFactory
instead.
For your example, I'd go with the following factories:
classFunctionFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.Function
name = factory.Sequence(lambda n: "Function %d" % n)
classFunctionParameterFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.FunctionParameter
function = factory.SubFactory(FunctionFactory)
classFunctionInstantiationFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.FunctionInstantiation
function = factory.SubFactory(FunctionFactory)
classParameterSettingFactory(factory.django.DjangoModelFactory):
classMeta:
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 extra factory, FunctionWithParametersFactory
, that creates parameters along:
class FunctionWithParametersFactory(FunctionFactory):
parameter1 = factory.RelatedFactory(ParameterSettingFactory, 'function')
parameter2 = factory.RelatedFactory(ParameterSettingFactory, 'function')
Calling that factory will perform the following:
- Create a Function object (through FunctionFactory)
- Call ParameterSettingFactory, pointing it to the created Function object
- Call ParameterSettingFactory a second time, still pointing it to the same Function object
- Return that Function object.
Solution 2:
This is Xelnor's answer, but fixes the bug so that only one function_instantiation
is created, rather than one for each parameter
/parameter_setting
pair.
classFunctionFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.Function
name = factory.Sequence(lambda n: "Function %d" % n)
classFunctionParameterFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.FunctionParameter
function = factory.SubFactory(FunctionFactory)
classFunctionInstantiationFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.FunctionInstantiation
function = factory.SubFactory(FunctionFactory)
classParameterSettingFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.ParameterSetting
function_instantiation = factory.SubFactory(FunctionInstantiationFactory)
function_parameter = factory.SubFactory(FunctionParameterFactory,
function=factory.SelfAttribute('..function_instantiation.function'))
classFunctionToParameterSettingsFactory(FunctionInstantiationFactory):
classMeta:
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')
The following demonstrates the solutions to a few other problems anyone using this pattern will probably encounter, such as overriding related objects' values, and links to other tables, themselves linked. It draws largely from techniques Xelnor introduced in his answer.
classFunctionFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.Function
name = factory.Sequence(lambda n: "Function %d" % n)
classFunctionParameterFactory(factory.django.DjangoModelFactory):
classMeta:
model = models.FunctionParameter
name = factory.Sequence(lambda n: "Function %d" % n)
function = factory.SubFactory(FunctionFactory)
classParameterSettingFactory(factory.django.DjangoModelFactory):
classMeta:
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'))
classDatasetAnd2ColumnsFactory(factory.django.DjangoModelFactory):
classMeta:
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.classFunctionToParameterSettingsFactory(factory.django.DjangoModelFactory):
classMeta:
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))
I now need to create a dataset with some columns of data, and join the parameter_setting records with those columns. To do so, this goes at the end of FunctionToParameterSettingsFactory
:
@factory.post_generationdefpost(self, create, extracted, **kwargs):
ifnot 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 hacky. I tried passing column=column_1
in the RelatedFactory calls, but that triggered creation of multiple datasets, each column linked to a different one. I tried all sorts of acrobatics with SelfAttribute and LazyAttribute, but you can't use either in a RelatedFactory call, and you can't create something with SubFactory(SelfAttribute()) and then pass it into RelatedFactory, as that breaks SelfAttribute (see my other question).
In my real code I had several more models with a foreign key to dataset and it all tied up fine.
Post a Comment for "Getting Id Of Associated Child Records In Factory_boy"