Получение идентификатора связанных дочерних записей в 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
потому что у меня нет доступа к parameter
объекты, созданные в FunctionFactory
так что я не могу установить parameter_setting.function_parameter_id
,
Как может FunctionInstantiationFactory
посмотрите вверх parameter_id
параметров, созданных в FunctionFactory
? Могу ли я получить на них из возвращаемого значения RelatedFactory(FunctionFactory)
? Или мне нужно посмотреть на базу?
2 ответа
Это ответ Кселнора, но исправляет ошибку так, что только один 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')
Далее демонстрируются решения некоторых других проблем, с которыми, возможно, столкнется любой, кто использует этот шаблон, например переопределение значений связанных объектов и ссылки на другие таблицы, которые сами связаны. Это во многом основано на технике, которую Кселнор представил в своем ответе.
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()
Это по общему признанию немного хакерский. Я пытался пройти column=column_1
в вызовах RelatedFactory, но это вызвало создание нескольких наборов данных, каждый столбец связан с другим. Я попробовал все виды акробатики с SelfAttribute и LazyAttribute, но вы не можете использовать ни один из них в вызове RelatedFactory, и вы не можете создать что-то с SubFactory(SelfAttribute()), а затем передать его в RelatedFactory, поскольку это нарушает SelfAttribute (см. мой другой вопрос).
В моем реальном коде у меня было еще несколько моделей с внешним ключом к набору данных, и все это нормально связывалось.
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')
Вызов этой фабрики будет выполнять следующее:
- Создать объект Function (через FunctionFactory)
- Вызовите ParameterSettingFactory, указав его на созданный объект Function
- Вызовите ParameterSettingFactory во второй раз, все еще указывая его на тот же объект Function
- Вернуть этот объект Function.