factory_boy icon indicating copy to clipboard operation
factory_boy copied to clipboard

Fields depending on one another

Open Gagaro opened this issue 5 years ago • 6 comments

HI,

I'm upgrading an old project which has factories with fields depending on one another (if the first one is set, the second one takes its value from it and the other way around). It was done by overwriting the _prepare method (which has been remove in 2.9.0).

Is there a proper way to do that with declarations? I overwrote _create instead but it wouldn't work for building (and is not very clean).

The current implementation is done that way:

@classmethod
def _create(cls, model_class, *args, **kwargs):
    """ Fields depending on one another """

    if 'initiator' in kwargs and 'agency' not in kwargs:
        kwargs['agency'] = kwargs['initiator'].agency

    elif 'initiator' not in kwargs and 'agency' in kwargs:
        kwargs['initiator'] = JobUserFactory(agency=kwargs['agency'])

    elif 'initiator' not in kwargs and 'agency' not in kwargs:
        kwargs['initiator'] = JobUserFactory()
        kwargs['agency'] = kwargs['initiator'].agency

    return super(MyFactory, cls)._create(model_class, *args, **kwargs)

I also tried creating my own declaration (a lazy_attribute with the parameters passed to the factory), but the parameters are not passed all the way to it.

Thanks

Gagaro avatar Jul 22 '20 16:07 Gagaro

Would _adjust_kwargs suit your use case?

francoisfreitag avatar Jul 31 '20 07:07 francoisfreitag

It doesn't work for every case. For example, with SelfAttribute:

class InfrastructureInterventionFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.InfrastructureIntervention

    rootequipment = factory.SubFactory(InfrastructureToolFactory, agency=factory.SelfAttribute('..agency'))

    @classmethod
    def _adjust_kwargs(cls, **kwargs):
        if 'initiator' in kwargs and 'agency' not in kwargs:
            kwargs['agency'] = kwargs['initiator'].agency
        elif 'initiator' not in kwargs and 'agency' in kwargs:
            kwargs['initiator'] = UserFactory(agency=kwargs['agency'])
        elif 'initiator' not in kwargs and 'agency' not in kwargs:
            kwargs['initiator'] = UserFactory()
            kwargs['agency'] = kwargs['initiator'].agency
        return kwargs

# Called without parameters
InfrastructureInterventionFactory()

# Raises
AttributeError: The parameter 'agency' is unknown.

Gagaro avatar Jul 31 '20 10:07 Gagaro

Would something along these lines work for you?

class InfrastructureInterventionFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.InfrastructureIntervention

    agency = factory.SubFactory(AgencyFactory)
    initiator = factory.SubFactory(UserFactory, agency=factory.SelfAttribute('..agency'))
    rootequipment = factory.SubFactory(InfrastructureToolFactory, agency=factory.SelfAttribute('..agency'))

    @classmethod
    def _adjust_kwargs(cls, **kwargs):
        if 'initiator' in kwargs and 'agency' not in kwargs:
            kwargs['agency'] = kwargs['initiator'].agency
        return kwargs

francoisfreitag avatar Jul 31 '20 15:07 francoisfreitag

I think it might do it but I won't be able to try it before late next month.

Gagaro avatar Jul 31 '20 17:07 Gagaro

So I actually get kwargs for all attributes, even the one not passed as parameters:

InfrastructureInterventionFactory()

# kwargs = {
#     'agency': <Agency: AGENCY 0>,
#     'initiator': <JobUser: Tom 0 Blob 0>,
#     'rootequipment': <InfrastructureTool: A0-MOUT-0000 - Tool é 0>
# }

Gagaro avatar Aug 24 '20 07:08 Gagaro

bump

I have a similar issue that might be a pretty common use case:

With SQLAlchemy models, I want the client code to be able to pass either an id for the DB column, or an object for the corresponding relationship, and I cannot figure out a way of making it work, see:

[EDIT: I was trying to make a minimal example but I'm having trouble setting it up on the fly, I'll make one and come back]

Lacrymology avatar Nov 23 '22 14:11 Lacrymology