2015-10-12 6 views
6

Я использую factory.LazyAttribute в пределах SubFactory вызов для передачи в объект, созданный в factory_parent. Это прекрасно работает.Передача объекта, созданного с помощью SubFactory и LazyAttribute, в relatedFactory в factory_boy

Но если я передаю объект, созданный на RelatedFactory, LazyAttribute больше не может видеть factory_parent и не удается.

Это прекрасно работает:

class OKFactory(factory.DjangoModelFactory): 
    class = Meta: 
     model = Foo 
     exclude = ['sub_object'] 

    sub_object = factory.SubFactory(SubObjectFactory) 

    object = factory.SubFactory(ObjectFactory, 
     sub_object=factory.LazyAttribute(lambda obj: obj.factory_parent.sub_object)) 

Идентичный вызов LazyAttribute терпит неудачу здесь:

class ProblemFactory(OKFactory): 
    class = Meta: 
     model = Foo 
     exclude = ['sub_object', 'object'] 

    sub_object = factory.SubFactory(SubObjectFactory) 

    object = factory.SubFactory(ObjectFactory, 
     sub_object=factory.LazyAttribute(lambda obj: obj.factory_parent.sub_object)) 

    another_object = factory.RelatedFactory(AnotherObjectFactory, 'foo', object=object) 

Идентичный LazyAttribute вызова больше не может видеть factory_parent, и может получить доступ только AnotherObject значения. LazyAttribute выдает ошибку:

AttributeError: The parameter sub_object is unknown. Evaluated attributes are...[then lists all attributes of AnotherObjectFactory] 

Есть ли способ обойти это?

Я не могу просто поставить sub_object = sub_object в ObjectFactory вызова, то есть:

sub_object = factory.SubFactory(SubObjectFactory) 
    object = factory.SubFactory(ObjectFactory, sub_object=sub_object) 

, потому что если я тогда делать:

object2 = factory.SubFactory(ObjectFactory, sub_object=sub_object) 

второй sub_object создается, в то время как мне нужно, как объекты для ссылки на один и тот же sub_object. Я пробовал SelfAttribute безрезультатно.

+0

Я думаю, если вы укажете один и тот же pk, он решит проблему. –

+0

Вы имеете в виду 'sub_object_id = sub_object_id'? Или 'LazyAttribute (lambda obj: obj.factory_parent.sub_object.id)'? – Chris

+0

Ответы на [этот вопрос] (http://stackoverflow.com/questions/32995158/getting-id-of-associated-child-records-in-factory-boy/33043428#33043428), похоже, указывают на альтернативный подход, но я не могу заставить его соответствовать этому требованию. (Этот вопрос должен исправить ошибку в одном из этих ответов.) – Chris

ответ

4

Я думаю, что вы можете использовать возможность переопределения параметров, переданных в RelatedFactory, для достижения того, чего вы хотите.

Например, если:

class MyFactory(OKFactory): 

    object = factory.SubFactory(MyOtherFactory) 
    related = factory.RelatedFactory(YetAnotherFactory) # We want to pass object in here 

Если бы мы знали, что значение object собирается быть заранее, мы могли бы заставить его работать с чем-то вроде:

object = MyOtherFactory() 
thing = MyFactory(object=object, related__param=object) 

Мы можем использовать это же соглашение об именовании для передачи объекта на RelatedFactory в пределах основного Factory:

class MyFactory(OKFactory): 

    class Meta: 
     exclude = ['object'] 

    object = factory.SubFactory(MyOtherFactory) 
    related__param = factory.SelfAttribute('object') 
    related__otherrelated__param = factory.LazyAttribute(lambda myobject: 'admin%d_%d' % (myobject.level, myobject.level - 1)) 
    related = factory.RelatedFactory(YetAnotherFactory) # Will be called with {'param': object, 'otherrelated__param: 'admin1_2'} 
2

Я решил это, просто позвонив фабрикам в пределах @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..(). Это делает хорошее разделение обязанностей довольно чистым для кода.