factory_boy: add several dependent objects

django, factory-boy, python

Solution

To be able to specify the number of related objects in parent factory:

models.py

class Company(models.Model):
    name = models.CharField(max_length=255)


class ContactPerson(models.Model):
    name = models.CharField(max_length=255)
    company = models.ForeignKey(Company, on_delete=CASCADE, related_name='contacts')

factories.py

class CompanyFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('company')

    class Meta:
        model = Company

    @factory.post_generation
    def add_contacts(self, create, how_many, **kwargs):
        # this method will be called twice, first time how_many will take the value passed
        # in factory call (e.g., add_contacts=3), second time it will be None
        # (see factory.declarations.PostGeneration#call to understand how how_many is populated)
        # ContactPersonFactory is therefore called +1 times but somehow we get right amount of objs
        at_least = 1
        if not create:
            return
        for n in range(how_many or at_least):
            ContactPersonFactory(contact=self)



class ContactPersonFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('first_name')

    class Meta:
        model = ContactPerson

tests.py

company = CompanyFactory(company_name='ACME ltd', add_contacts=4)
print(repr(company.name), len(company.contacts.all()))
company = CompanyFactory(company_name='ACME ltd')
print(repr(company.name), len(company.contacts.all()))

---
'ACME ltd' 4
'ACME ltd' 1

If you are ok with always just one child, the docs solution works well:

models.py

class CompanyFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('company')
    whatever_really = factory.RelatedFactory('my_app.factories.ContactPersonFactory', 'contact')

    class Meta:
        model = Company

note the full path to the related factory.

tests.py

company = CompanyFactory(company_name='ACME ltd')
print(repr(company.name), len(company.contacts.all()))
---
'ACME ltd' 1

versions used

$ pip freeze | egrep 'factory|Faker|Django'
Django==2.0.4
factory-boy==2.10.0
Faker==0.8.13
$ python -V
Python 3.6.5

Problem

I'm using factory_boy to replace fixtures in a Django app. I have a Product model that should have many Offers and Merchants. ``` #models.py class Product(models.Model): name = models.CharField() class Merchant(models.Model): product = models.ForeignKey(Product) name = models.CharField() class Offer(models.Model): product = models.ForeignKey(Product) price = models.DecimalField(max_digits=10, decimal_places=2) ``` I want a factory that creates a Product with several Merchants and several Offers. ``` #factories.py import random from models import Offer, Merchant, Product class OfferFactory(factory.django.DjangoModelFactory): FACTORY_FOR = Offer product = factory.SubFactory(ProductFactory) price = random.randrange(0, 50000, 1)/100.0 class MerchantFactory(factory.django.DjangoModelFactory): FACTORY_FOR = Merchant product = factory.SubFactory(ProductFactory) name = factory.Sequence(lambda n: 'Merchant %s' % n) url = factory.sequence(lambda n: 'www.merchant{n}.com'.format(n=n)) class ProductFactory(factory.django.DjangoModelFactory): FACTORY_FOR = Product name = "test product" offer = factory.RelatedFactory(OfferFactory, 'product') offer = factory.RelatedFactory(OfferFactory, 'product') # add a second offer offer = factory.RelatedFactory(OfferFactory, 'product') # add a third offer merchant = factory.RelatedFactory(MerchantFactory, 'product') merchant = factory.RelatedFactory(MerchantFactory, 'product') # add a second merchant merchant = factory.RelatedFactory(MerchantFactory, 'product') # add a third merchant ``` But when I use ProductFactory to create a Product, it only has one offer and one merchant. ``` In [1]: from myapp.products.factories import ProductFactory In [2]: p = ProductFactory() In [3]: p.offer_set.all() Out[3]: [<Offer: $39.11>] ``` How do I set up a ProductFactory to have more than one dependent of a particular type?

Original source