Search code examples
djangodjango-ormfactory-boy

Factory Boy - relate two SubFactories


Say I've got a factory that has two SubFactories which need to be related. What hook(s) does FactoryBoy provide either pre/post generation to associate these SubFactories?

class AppointmentFactory(factory.DjangoModelFactory):
    class Meta:
        model = Appointment

    team_member = factory.SubFactory(TeamMemberFactory)
    merchant_location = factory.SubFactory(MerchantLocationFactory)

Inspecting what gets created in a shell session yields a theoretically invalid object - a team member from a different location.

> appt = AppointmentFactory.create()
>
> print(appt.merchant_location)
> <MerchantLocation: object (3)>
>
> print(appt.team_member.merchant_location) 
> <MerchantLocation: object (4)>

Ideally, this hook would give me access to args passed to the factory so I can determine which SubFactory to use as the "source of truth" for the relationship.

Example Logic:

   # handle: AppointmentFactory(team_member=tm_A) or AppointmentFactory(location=loc_A)

   @factory.my_desired_hook
   def associate_stuff(self, *args, **kwargs):
       if 'team_member' in kwargs and 'merchant_location' in kwargs:
           pass . # assume caller taking responsibility for correctness
       elif 'team_member' in kwargs:
           self.merchant_location = team_member.merchant_location
       elif 'merchant_location' in kwargs:
           self.team_member.merchant_location = self.merchant_location

Solution

  • The best hook for this would be to use a factory.SelfAttribute in your subfactory:

    class AppointmentFactory(factory.Factory):
        class Meta:
            model = Appointment
    
        merchant_location = factory.SubFactory(MerchantLocationFactory)
        team_member = factory.SubFactory(
            TeamMemberFactory,
            # Fetch the `merchant_location` from the parent object.
            merchant_location=factory.SelfAttribute('..merchant_location'),
        )
    

    This does not address the possibility to provide either, and have the factory adjust automatically. That part is more complicated, as the library does not provide helpers to break the circular dependency.