Search code examples
pythondjangofactory-boy

FactoryBoy overriding Attributes


I want to use FactoryBoy with the User/Profile Pattern as described in its documentation.

class ProfileFactory(DjangoModelFactory):
    class Meta:
        model = Profile     
    first_name = lazy_attribute(lambda o: faker.first_name())
    user = SubFactory('profiles.tests.factories.UserFactory', profile=None)

class UserFactory(DjangoModelFactory):
    class Meta:
        model = settings.AUTH_USER_MODEL
    username = lazy_attribute(lambda o: faker.first_name())
    profile = RelatedFactory(ProfileFactory, 'user')

Now I would like to be able to either create a User by providing only the username like

u = UserFactory(username='alice')

and automatically get the username as the first_name (='Alice')in the profile

or the first_name like

u = UserFactory(profile__first_name='Bob')

and get the username (='bob')set according to the given first_name

or, if I am frisky provide both username and first_name and of course retain both

u = UserFactory(username='alice', profile__first_name='Bob')

So far I did not find the right spot to implement this behaviour. I tried overriding _generate(), but found that this isn't the right spot, as my lazy attributes have already fired at this moment. Also I was not able to override __call__() or __new__() in my UserFactory.

Maybe this is all because I am fairly new to python, so I would be thankful for any help to push me in the right direction.


Solution

  • Even though I feel a bit stupid answering my own first question here, I guess I found the answer myself:

    It looks like the best method to override to achieve what I want is attributes from BaseFactory as all building/creating functions call this and I still get the 'pure' kwargs as extra to fiddle with them.

    Maybe somebody still has a better answer but until then this is my version.

    @classmethod
    def attributes(cls, create=False, extra=None):
        if extra:
            if 'profile__first_name' in extra and 'username' not in extra:
                extra['username'] = extra['profile__first_name'].lower()
            elif 'username' in extra and 'profile__first_name' not in extra:
                extra['profile__first_name'] = extra['username'].title()
    
        return super().attributes(create, extra)