Получение идентификатора связанных дочерних записей в 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')

Вызов этой фабрики будет выполнять следующее:

  1. Создать объект Function (через FunctionFactory)
  2. Вызовите ParameterSettingFactory, указав его на созданный объект Function
  3. Вызовите ParameterSettingFactory во второй раз, все еще указывая его на тот же объект Function
  4. Вернуть этот объект Function.
Другие вопросы по тегам