Setting Up A Factory For One To Many Relationships In Factoryboy
Overview
[Factoryboy] is used to replace fixtures with factories for complex objects. It already comes with solutions to handle one-to-one and many-to-many relationships, but it lacks documentation for setting up one-to-many relationships, which is what we will see in this guide.
There are two simple strategies for setting up this kind of relationship:
- Using Factoryboy’s post generation hooks
- Create the relationship objects “manually”
Just Show me the code
You can check this guide full source code at https://github.com/marcanuy/factoryboy_examples repo.
Setting up the example
The following examples will be based in Django models with the
following relationship (in myapp/models.py
):
from django.db import models
class Player(models.Model):
"""
Model representing a player of a team
"""
team = models.ForeignKey(
'team', # class name
on_delete=models.CASCADE,
related_name='players'
)
first_name = models.CharField(max_length=200, help_text="Player's first name")
last_name = models.CharField(max_length=200, help_text="Player's last name")
class Team(models.Model, LineMixin):
"""
Model representing a text Team.
"""
name = models.CharField(max_length=200, help_text="Team name")
Having the above Django models we create a factory for each model, in
myapp/factories.py
.
The Player’s factory is straightforward, having its team
’s related
field set up with
factory.SubFactory,
which calls the related factory, in this case: team = factory.SubFactory(TeamFactory)
.
import factory
from . import models
class PlayerFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Player
team = factory.SubFactory(TeamFactory)
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
SubFactory definition:
class factory.SubFactory(factory, **kwargs)
This attribute declaration calls another Factory subclass, selecting the same build strategy and collecting extra kwargs in the process. The SubFactory attribute should be called with:
- A Factory subclass as first argument, or the fully qualified import path to that Factory
- An optional set of keyword arguments that should be passed when calling that factory
Strategies
Now we can define the Team
factory in one of two ways.
Using post generation hook
To performs actions once the model object has been generated we use
the factory.PostGeneration
hook.
In this case we will create a random amount of Player instances for one Team, or a specific amount of Player’s if passed an argument.
To set up the post generation hook we use the
@factory.post_generation
decorator and define a function whose name
will be used when calling the factory, like this
@factory.post_generation
def players(line, create, extracted, **kwargs):
pass
Now we can call it like: TeamFactory(players=4) it generates a Team with 4 players.
If we call it without players
it generates a random amount of Player’s.
In models/factories.py
:
class TeamFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Team
name = factory.Faker('sentence', nb_words=3, variable_nb_words=True)
class TeamWithPlayersFactory(TeamFactory):
@factory.post_generation
def players(obj, create, extracted, **kwargs):
"""
If called like: TeamFactory(players=4) it generates a Team with 4
players. If called without `players` argument, it generates a
random amount of players for this team
"""
if not create:
# Build, not create related
return
if extracted:
for n in range(extracted):
myapp.factories.PlayerFactory(team=obj)
else:
import random
number_of_units = random.randint(1, 10)
for n in range(number_of_units):
myapp.factories.PlayerFactory(team=obj)
Then we can create some tests to show how it works (in myapp/tests.py
):
from django.test import TestCase
from myapp.factories import TeamFactory, PlayerFactory, TeamWithPlayersFactory
from myapp.models import Team, Player
class FactoriesTests(TestCase):
def test_create_team_with_players(self):
team = TeamWithPlayersFactory.create()
self.assertIsInstance(team, Team)
players = team.players.all()
self.assertTrue(len(players) > 0)
for player in players:
self.assertIsInstance(player, Player)
Specify amount of players to create
And to specify how many players we want, we pass the players
argument: TeamWithPlayersFactory.create(players=5)
.
def test_create_team_with_fixed_amount_of_players(self):
team = TeamWithPlayersFactory.create(players=5)
self.assertIsInstance(team, Team)
players = team.players.all()
self.assertTrue(len(players) == 5)
for player in players:
self.assertIsInstance(player, Player)
Manually creating objects
Without using the post generation hook, we can still create them making sure we create a team before starting to create the players, so we assign the same Team to all players, like:
team = TeamFactory.create()
player1 = PlayerFactory.create(team=team)
player2 = PlayerFactory.create(team=team)
An example with shell
:
$ python manage.py shell Python 3.6.4 (default, Feb 5 2018, 16:52:44) [GCC 7.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from myapp.factories import TeamFactory >>> from myapp.factories import PlayerFactory >>> team = TeamFactory.create() >>> from myapp.models import Team, Player >>> PlayerFactory.create(team=team) <Player: Amber Marshall> >>> PlayerFactory.create(team=team) <Player: Cynthia Howard> >>> team.players.all() <QuerySet [ <Player: Amber Marshall>, <Player: Cynthia Howard>] > >>>
In myapp/tests.py
:
...
def test_create_players_with_same_team(self):
team = TeamFactory.create()
player1 = PlayerFactory.create(team=team)
player2 = PlayerFactory.create(team=team)
self.assertIsInstance(player1, Player)
self.assertIsInstance(player2, Player)
self.assertEqual(player1.team, team)
self.assertEqual(player2.team, team)
Conclusion
Using the post generation hooks, is a bit harder to set up but makes it easier to use the factory in your code.
*[Factoryboy]: http://factoryboy.readthedocs.io/en/latest/
- August 1, 2023
- How to create a reusable Django app and distribute it with PIP or publish to pypi.orgJune 29, 2021
- How To Serve Multiple Django Applications with uWSGI and Nginx in Ubuntu 20.04October 26, 2020
- How to add favicon to Django in 4 stepsSeptember 3, 2020
- Categories in Django with BreadcrumbsAugust 30, 2020
- How To Migrate From SQLite To PostgreSQL In Django In 3 stepsAugust 28, 2020
- Practical guide to internationalize a Django app in 5 steps.August 24, 2020
- Disable new users singup when using Django's allauth packageSeptember 3, 2019
- How to add ads.txt to Django as requested by Google AdsenseAugust 30, 2019
- Have multiple submit buttons for the same Django formJuly 2, 2019
- Better Testing with Page Object Design in DjangoMay 1, 2019
- Generating slugs automatically in Django without packages - Two easy and solid approachesFebruary 14, 2019
- How to set up Django tests to use a free PostgreSQL database in HerokuFebruary 13, 2019
- Dynamically adding forms to a Django FormSet with an add button using jQueryFebruary 6, 2019
- Use of Django's static templatetag in css file to set a background imageFebruary 1, 2019
- Activate Django's manage.py commands completion in Bash in 2 stepsJanuary 29, 2019
- Sending Emails with Django using SendGrid in 3 easy stepsJanuary 9, 2019
- Adding Users to Your Django Project With A Custom User ModelSeptember 21, 2018
- Setting Up A Factory For One To Many Relationships In Factoryboy
- Generate UML class diagrams from django modelsMarch 24, 2018
- Set Up Ubuntu To Serve A Django Website Step By StepJuly 3, 2017
- Django Project Directory StructureJuly 16, 2016
- How to Have Different Django Settings for Development and Production, and environment isolationJune 10, 2016
- Django OverviewJune 2, 2016
Django Forms
- Adding a Cancel button in Django class-based views, editing views and formsJuly 15, 2019
- Using Django Model Primary Key in Custom Forms THE RIGHT WAYJuly 13, 2019
- Django formset handling with class based views, custom errors and validationJuly 4, 2019
- How To Use Bootstrap 4 In Django FormsMay 25, 2018
- Understanding Django FormsApril 30, 2018
- How To Create A Form In DjangoJuly 29, 2016
Articles
Subcategories
Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.
Powered by SimpleIT Hugo Theme
·