Django + Factory Boy: How to use a named parameter to control behavior of factory.Maybe + factory.RelatedFactory

Viewed 4870

Is there a way to define a named parameter (that is not a model attribute) to control the behavior of factory.Maybe?

For example: I want to create a named parameter that controls the creation of a RelatedFactory through a Maybe condition, I tried to do this:

# factories.py
class Purchase(factory.DjangoModelFactory):
    user = factory.SubFactory('project.factories.UserFactory')
    total = uniform(10, 100)

    class Meta:
        model = Purchase

class UserFactory(factory.DjangoModelFactory):
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')

    has_purchase = False
    purchases = factory.Maybe(
        'has_purchase',
        yes_declaration=factory.RelatedFactory(Purchase, 'purchase'),
        no_declaration=None
    )

    class Meta:
        exclude = ['has_purchase', 'purchases']
        model = User

# tests.py
user_without_purchase = UserFactory.create()
user_with_purchase = UserFactory.create(has_purchase=True)

It seems that I can't change the value of has_purchase through the named parameter, it's always set as False. I don't know if it's because both fields are declared in exclude Meta attribute. And I have to put them in exclude or else DjangoModelFactory tries to use these fields when saving it to the database, which causes an Integrity error.

Is there a way to do that?

2 Answers

Elaborating on Clément Denoix's answer, I've been able to add an optional argument to pass a value directly to a sub-factory (a shortcut, sort of):

class Y_Factory(DjangoModelFactory):
    class Meta:
        model = Y  # has a field named 'z'


class X_Factory(DjangoModelFactory):
    class Meta:
        model = X  # has a foreign key to Y

    class Params:
        z = None

    y = factory.lazy_attribute(lambda o: Y_Factory(z=o.z or 'default_z'))

Now the following syntax X_Factory(y=Y_Factory(z=z)) can be simplified to X_Factory(z=z).

There is one caveat though: using this syntax will mislead newcomers as it wrongly depicts the data structure. Still, I decided that in one particular case, such a shortcut conveniently removed some annoying boilerplate code from my fixtures and was worth the trade-off.

Related