Factory Boy and related objects creation
Asked Answered
D

3

7

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.

Doomsday answered 24/11, 2017 at 13:49 Comment(0)
B
14

You want to use factoryboy's parents and SelfAttribute

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=factory.SelfAttribute('..restaurant')
    )

with this test app I get

In [1]: from service.tests import ServiceFactory
In [2]: s1 = ServiceFactory.create()
In [3]: s1.restaurant == s1.hist_day_period.restaurant
Out[3]: True

In [4]: s1.restaurant_id
Out[4]: 4

In [5]: s1.hist_day_period.restaurant_id
Out[5]: 4
Brisbane answered 28/11, 2017 at 15:16 Comment(4)
It's not clear how this solves the OP's problem of creating three Service records but only one Restaurant record.Knawel
OP states " It should create all 3 models, but use the same restaurant.". I read this as Service, Resturant and DayPeriod but only one Restaurant, the code sample he provides looks like it backs this upBrisbane
Good answer, I think it also makes sense to mention lazyattribute and explain a reason to use or not to use RelatedFactory here.Conant
Thanks a lot, exactly what I needed. You deserve this bounty :)Doomsday
K
4

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.

Knawel answered 28/11, 2017 at 1:43 Comment(1)
Thanks @shacker, your answer helped me to take some height, and I think you're right about the flexibility. However, the dinosaurwaltz answer is exactly the technical way to do what I wanted to do :)Doomsday
H
0

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).

Harlamert answered 29/6, 2018 at 18:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.