pythondjangofactory-boyassociated-object

Passing an object created with SubFactory and LazyAttribute to a RelatedFactory in factory_boy


I am using factory.LazyAttribute within a SubFactory call to pass in an object, created in the factory_parent. This works fine.

But if I pass the object created to a RelatedFactory, LazyAttribute can no longer see the factory_parent and fails.

This works fine:

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))

The identical call to LazyAttribute fails here:

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)

The identical LazyAttribute call can no longer see factory_parent, and can only access AnotherObject values. LazyAttribute throws the error:

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

Is there a way round this?

I can't just put sub_object=sub_object into the ObjectFactory call, ie:

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

because if I then do:

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

a second sub_object is created, whereas I need both objects to refer to the same sub_object. I have tried SelfAttribute to no avail.


Solution

  • I think you can leverage the ability to override parameters passed in to the RelatedFactory to achieve what you want.

    For example, given:

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

    If we knew what the value of object was going to be in advance, we could make it work with something like:

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

    We can use this same naming convention to pass the object to the RelatedFactory within the main 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'}