Я решил это, просто позвонив фабрикам в пределах @factory.post_generation
. Строго говоря, это не решение конкретной заданной проблемы, но я подробно объясню ниже, почему это оказалось лучшей архитектурой. Решение @ rhunwick действительно передает SubFactory(LazyAttribute(''))
в RelatedFactory
, однако ограничения остались, что означало, что это было неправильно для моей ситуации.
Двигается создание sub_object
и object
от ProblemFactory
к ObjectWithSubObjectsFactory
(и удалить пункт exclude
), и добавьте следующий код в конце ProblemFactory
.
@factory.post_generation
def post(self, create, extracted, **kwargs):
if not create:
return # No IDs, so wouldn't work anyway
object = ObjectWithSubObjectsFactory()
sub_object_ids_by_code = dict((sbj.name, sbj.id) for sbj in object.subobject_set.all())
# self is the `Foo` Django object just created by the `ProblemFactory` that contains this code.
for another_obj in self.anotherobject_set.all():
if another_obj.name == 'age_in':
another_obj.attribute_id = sub_object_ids_by_code['Age']
another_obj.save()
elif another_obj.name == 'income_in':
another_obj.attribute_id = sub_object_ids_by_code['Income']
another_obj.save()
Так что, похоже RelatedFactory
вызовы выполняются до PostGeneration
вызовов.
Именование в this question легче понять, так что здесь тот же код, решение этой проблемы образца:
Создание dataset
, column_1
и column_2
перемещаются в новую фабрику DatasetAnd2ColumnsFactory
, а код ниже затем добавляется в конец 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 `FunctionInstantiation` Django object just created by the `FunctionToParameterSettingsFactory` that contains this code.
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()
Затем я расширил этот подход, проходящий в настройках, чтобы настроить завод, как это:
whatever = WhateverFactory(options__an_option=True, options__another_option=True)
Затем этот завод код обнаружен параметрам и данные, генерируемые испытания, предписанные (обратите внимание, что метод переименован в options
в соответствии с префиксом по именам параметров):
@factory.post_generation
def options(self, create, not_used, **kwargs):
# The standard code as above
if kwargs.get('an_option', None):
# code for custom option 'an_option'
if kwargs.get('another_option', None):
# code for custom option 'another_option'
я тогда продлено это. Поскольку мои желаемые модели включали self-join, моя фабрика рекурсивна. Таким образом, для вызова таких как:
whatever = WhateverFactory(options__an_option='xyz',
options__an_option_for_a_nested_whatever='abc')
В @factory.post_generation
у меня есть:
class Meta:
model = Whatever
# self is the top level object being generated
@factory.post_generation
def options(self, create, not_used, **kwargs):
# This generates the nested object
nested_object = WhateverFactory(
options__an_option=kwargs.get('an_option_for_a_nested_whatever', None))
# then join nested_object to self via the self join
self.nested_whatever_id = nested_object.id
Некоторые заметки вам не нужно читать, почему я пошел с этой опцией, а не @ правильное решение rhunwicks к моему вопрос выше. Существовали две причины.
Вещь, которая мешала мне экспериментировать с ней, заключалась в том, что порядок RelatedFactory и пост-поколение не является надежным - на него влияют, по-видимому, несвязанные факторы, по-видимому, следствие ленивой оценки. У меня были ошибки, когда множество заводов вдруг перестали работать без видимых причин. Когда-то это было потому, что я переименовал переменные, которые были присвоены. Это звучит смешно, но я тестировал его до смерти (и опубликовал here), но нет сомнений в том, что переименование переменных надежно переключило последовательность работы по умолчанию и постобработка. Я по-прежнему полагал, что это был некоторый надзор от моего имени, пока это не произошло снова по какой-то другой причине (которую мне никогда не удавалось диагностировать).
Во-вторых, я нашел декларативный код запутанным, негибким и трудно переопределяющим. Не просто передать разные конфигурации во время создания экземпляра, чтобы одна и та же фабрика могла использоваться для разных вариантов тестовых данных, то есть мне пришлось повторять код, object
нужно добавить в список Factory Meta.exclude
- звучит тривиально, но когда у вас есть страницы данных генерации кода была достоверной ошибкой. Как разработчик вам придется несколько раз переходить на несколько заводов, чтобы понять поток управления. Код генерации будет распространяться между декларативным телом, пока вы не исчерпали эти трюки, а остальные останутся в покое поколении или очень запутаны. Общим примером для меня является триада взаимозависимых моделей (например, структура категорий родителей или детей или набор данных/атрибутов/сущностей) в качестве внешнего ключа другой триады межзависимых объектов (например, модели, значения параметров и т. Д., Ссылающиеся к значениям параметров других моделей). Некоторые из этих типов структур, особенно вложенные, быстро становятся неуправляемыми.
Я понимаю, что это действительно не в духе factory_boy, но все в пост-поколение решает все эти проблемы. Я могу передавать параметры, поэтому одна и та же единственная фабрика обслуживает все мои требования к тестовым данным в составной модели, и код не повторяется. Последовательность создания легко увидеть сразу, очевидна и полностью надежна, а не в зависимости от путаницы цепей наследования и переопределения и подверженности некоторой ошибке. Взаимодействия очевидны, поэтому вам не нужно переваривать все, чтобы добавить некоторые функциональные возможности, а различные области funtionality сгруппированы в предложениях post-generation if. Нет необходимости исключать рабочие переменные, и вы можете ссылаться на них на время заводского кода. Единичный тестовый код упрощен, так как описание функций происходит в именах параметров, а не в именах классов Factory - поэтому вы создаете данные с вызовом типа WhateverFactory(options__create_xyz=True, options__create_abc=True..
, а не WhateverCreateXYZCreateABC..()
. Это делает хорошее разделение обязанностей довольно чистым для кода.
Я думаю, если вы укажете один и тот же pk, он решит проблему. –
Вы имеете в виду 'sub_object_id = sub_object_id'? Или 'LazyAttribute (lambda obj: obj.factory_parent.sub_object.id)'? – Chris
Ответы на [этот вопрос] (http://stackoverflow.com/questions/32995158/getting-id-of-associated-child-records-in-factory-boy/33043428#33043428), похоже, указывают на альтернативный подход, но я не могу заставить его соответствовать этому требованию. (Этот вопрос должен исправить ошибку в одном из этих ответов.) – Chris