Django User Profile Factories

Mike Smith
3 min readDec 1, 2017

Recently, we’ve needed to update our user model in our django project to support additional properties. Having looked at this great post about extending the user model, it was clear that at this point we needed to simply create a Profile object with a one to one relationship on the User model. I duly followed the approach outlined, creating a signal that would automatically create a Profile object when a User was created:

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, raw, **kwargs):
if not raw:
if created:
models.Profile.objects.create(user=instance)
else:
instance.profile.save()

(possibly worthy of note is that I didn’t create separate signals for creating and updating the Profile instance)

And then I ran my tests, and ran into all sorts of trouble with our factories. We’re using FactoryBoy which has been excellent for generating the models we need in our various tests. However, the signals were causing trouble.

The current docs for dealing with this are out of date, suggesting that you override the private _generate method, which since 2.9 is apparently no longer supported. So I followed the new suggestion of using the mute_signals decorator. Which led to the following:

@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
"""
Profile Factory
"""
class Meta:
model = models.Profile

user = factory.SubFactory('main.factories.UserFactory', profile=None)
# ...


@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
"""
User factory
"""

class Meta:
model = User
django_get_or_create = ('username',)

username = factory.Faker('first_name')
# ...
profile = factory.RelatedFactory(ProfileFactory, 'user')

Note that I had to set the mute_signals decorator on both factory classes. This seemed to solve the initial problem of being able to generate a user or a profile. However, we have other models that are ‘owned’ by users, and I was surprised (probably naively) to discover that the decorator was not applied for any other dependent factory classes.

The mute_signals decorator must be applied to every model that has a dependency on a model with a post_save signal. I expect this would also apply to any pre_save signal as well should that be what you are using.

I thought I had everything sorted at this point, but running the tests I still had failures. After some time fiddling around in the interactive shell, I finally realised that the problem lay in my use of django_get_or_create for the User model.

The reason this is in place is so that if the faker call for the username accidentally generates a user that already exists, it won’t fall over with a unique name class.

However, if this did happen, the already existing user object was being retrieved from the DB, and then the ProfileFactory was being called by the RelatedFactory definition.

Confession time: I spent a lot of time fiddling around with post generation hooks and the like in an effort to detect the correct state and prevent the profile generation call. Way longer than I am entirely comfortable quantifying, but there were at least two cups of tea involved.

The solution to this is, of course, to use the exact same pattern on the ProfileFactory as well, so it now looks like:

@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
"""
Profile Factory
"""
class Meta:
model = models.Profile
django_get_or_create = ('user', )

user = factory.SubFactory('main.factories.UserFactory', profile=None)
# ...

And finally, I had a set of factories that worked in the same way as they had before I introduced the Profile model.

--

--