Search code examples
pythondjangofactory-boy

How does one create a Factory with foreign key instances that share certain attributes - but that attribute does not exist on parent?


I'm looking to use FactoryBoy to ensure that a model with foreign keys to other models that indirectly share foreign keys are generated with the same instances (I realize that’s pretty hard to grok, here’s a code example):

from django.db import models
import factory


class Foo(models.Model):
    name = models.CharField(max_length=32)

class Bar(models.Model):
    foo = models.ForeignKey(Foo, on_delete=models.PROTECT)

class Baz(models.Model):
    foo = models.ForeignKey(Foo, on_delete=models.PROTECT)

class HasBarAndBaz(models.Model):
    bar = models.ForeignKey(Bar, on_delete=models.PROTECT)
    baz = models.ForeignKey(Baz, on_delete=models.PROTECT)

class HasBarAndBazFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = HasBarAndBaz

    bar = factory.SubFactory(BarFactory)
    baz = factory.SubFactory(BazFactory)

The desire here would be to ensure that the following occurs

has_bar_and_baz = HasBarAndBazFactory()
has_bar_and_baz.bar.foo === has_bar_and_baz.baz.foo # should be True

I can think of a couple solutions, but I’m curious to know if there’s a “FactoryBoy” way to do this, without needing to write a wrapper function that accepts a product_line kwarg and passes it.

I thought about using a RelatedFactory, and then referencing that as the default foo kwarg to the SubFactories, but a RelatedFactory gets generated after the base factory.


Solution

  • I played around with a couple solutions that broke, but so far this seems to be working - also has benefit of the foo kwarg being optional, something that didn’t work in a few of my attempts:

    class HasBarAndBazFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = HasBarAndBaz
            exclude = ('foo',)
    
        foo = factory.SubFactory(FooFactory)
        bar = factory.SubFactory( BarFactory, foo=factory.SelfAttribute('..foo'))
        baz = factory.SubFactory( BazFactory, foo=factory.SelfAttribute('..foo'))
    

    I knew about exclude, but I didn’t know that you could declare subfactory attributes that did not exist on original model - I assumed that RelatedFactory had to be used for those.