Search code examples
djangofactory-boy

factory_boy: passing model instances for a RelatedFactory


I figured instead of passing data to a factory's RelatedFactory in the form of ATTR__SUBATTR, you should also be able to just directly pass already existing instances. Unless I'm missing something very obvious, this just doesn't seem to work. Have a look:

class Owner(models.Model):
    name = models.CharField()

class Item(models.Model):
    name = models.CharField()
    owner = models.ForeignKey(Owner, null = True, related_name = 'items')

class ItemFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Item

class OwnerFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Owner
    items = factory.RelatedFactory(ItemFactory, 'owner')

item = Item.objects.create(name='Foo')
alice = OwnerFactory(name='Alice', items__name='Bar')
alice.items.all()
<QuerySet [<Item: Bar>]>
bob = OwnerFactory(name='Bob', items=item) # or items = [item] doesn't matter
bob.items.all()
<QuerySet []>

Been working on making my factories nice and DRY and hit this roadblock. Wrote my own adaption of RelatedFactory that allows for multiple values to be handled at once, which works fine if you are creating new objects in the process - but not if you are using already existing ones.

Example that works: OwnerFactory(items__name=['Foo','Bar'])=> Foo and Bar in owner.items.
Example that does not work: OwnerFactory(items=[foo,bar])=> owner.items is empty
Note that I have used the default RelatedFactory in the big example at the top.

I have been all over the documentation for factory_boy the entire day, but couldn't find a solution and tunnel vision has taken over now, prohibiting any new insight.


Solution

  • You are looking for http://factoryboy.readthedocs.io/en/latest/recipes.html#simple-many-to-many-relationship

    class ItemFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Item
    
    class OwnerFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Owner
    
        @factory.post_generation
        def items(self, create, extracted, **kwargs):
            if not create:
                # Simple build, do nothing.
                return
    
            if extracted:
                # A list of items were passed in, use them
                for item in extracted:
                    self.items.add(item)