Factory Boy and related objects creation

Viewed 5063

Let's say you have these Django models related this way:

class Service:
   restaurant = models.ForeignKey(Restaurant)
   hist_day_period = models.ForeignKey(DayPeriod)

class DayPeriod:
   restaurant = models.ForeignKey(Restaurant)

I want to create a Service object, using a Factory. It should create all 3 models but use the same restaurant.

Using this code:

class ServiceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Service

    restaurant = factory.SubFactory('restaurants.factories.RestaurantFactory')

    hist_day_period = factory.SubFactory(
        'day_periods.factories.DayPeriodFactory', restaurant=restaurant)

Factory boy will create 2 different restaurants:

s1 = ServiceFactory.create()
s1.restaurant == s1.hist_day_period.restaurant
>>> False

Any idea on how to do this? It's unclear to me if I should use related factors instead of SubFactory to accomplish this.

3 Answers

I've gone back and forth on whether it makes sense to create related or FK objects from within a factory, or separately. As you've already found, you don't necessarily want to get a new restaurant every time you call ServiceFactory(). I think you're better off keeping them simple, at the expense of having to use slightly more verbose calling code.

Comment out the restaurant = factory.SubFactory line, then call your factory like:

restaurant = Restaurant.objects.get(foo='bar')
ServiceFactory.create_batch(3, restaurant=restaurant)

Or, if you do want to use a factory to create the restaurant but you only want ONE restaurant created:

restaurant = RestaurantFactory()
ServiceFactory.create_batch(3, restaurant=restaurant)

IOTW, have your factories make as few assumptions as possible, so you have the flexibility to build up whatever data structures you need from tests or different parts of your system.

Instead of using a SubFactory you can instead use an Iterator with a queryset.

For example:

class ServiceFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Service

    restaurant = factory.Iterator(models.Restaurant.object.all())

    hist_day_period = factory.SubFactory(
        'day_periods.factories.DayPeriodFactory', restaurant=restaurant)

I'm not sure about the best place to create that Restaurant, though. In our codebase I override DiscoverRunner and create models used for this purpose there (in setup_databases, which feels like a hack).

Related