Recipes

If you’re not comfortable with random data or even if you just want to improve the semantics of the generated data, there’s hope for you.

You can define a Recipe, which is a set of rules to generate data for your models.

It’s also possible to store the Recipes in a module called baker_recipes.py at your app’s root directory. This recipes can later be used with the make_recipe function:

shop/
  migrations/
  __init__.py
  admin.py
  apps.py
  baker_recipes.py   <--- where you should place your Recipes
  models.py
  tests.py
  views.py

File: baker_recipes.py

from model_bakery.recipe import Recipe
from shop.models import Customer

customer_joe = Recipe(
    Customer,
    name='John Doe',
    nickname='joe',
    age=18,
    birthday=date.today(),
    last_shopping=datetime.now()
)

Note

You don’t have to declare all the fields if you don’t want to. Omitted fields will be generated automatically.

File: test_model.py

from django.test import TestCase

from model_bakery import baker

from shop.models import Customer, Contact

class CustomerTestModel(TestCase):

    def setUp(self):
        # Load the recipe 'customer_joe' from 'shop/baker_recipes.py'
        self.customer_one = baker.make_recipe(
            'shop.customer_joe'
        )

Or if you don’t want a persisted instance:

from model_bakery import baker

baker.prepare_recipe('shop.customer_joe')

Another examples

Note

You can use the _quantity parameter as well if you want to create more than one object from a single recipe.

You can define recipes locally to your module or test case as well. This can be useful for cases where a particular set of values may be unique to a particular test case, but used repeatedly there. For example:

File: baker_recipes.py

company_recipe = Recipe(Company, name='WidgetCo')

File: test_model.py

class EmployeeTest(TestCase):
    def setUp(self):
        self.employee_recipe = Recipe(
            Employee,
            name=seq('Employee '),
            company=baker.make_recipe('app.company_recipe')
        )

    def test_employee_list(self):
        self.employee_recipe.make(_quantity=3)
        # test stuff....

    def test_employee_tasks(self):
        employee1 = self.employee_recipe.make()
        task_recipe = Recipe(Task, employee=employee1)
        task_recipe.make(status='done')
        task_recipe.make(due_date=datetime(2014, 1, 1))
        # test stuff....

Recipes with foreign keys

You can define foreign_key relations:

from model_bakery.recipe import Recipe, foreign_key
from shop.models import Customer, PurchaseHistory

customer = Recipe(Customer,
    name='John Doe',
    nickname='joe',
    age=18,
    birthday=date.today(),
    appointment=datetime.now()
)

history = Recipe(PurchaseHistory,
    customer=foreign_key(customer)
)

Notice that customer is a recipe.

You may be thinking: “I can put the Customer model instance directly in the owner field”. That’s not recommended.

Using the foreign_key is important for 2 reasons:

  • Semantics. You’ll know that attribute is a foreign key when you’re reading;
  • The associated instance will be created only when you call make_recipe and not during recipe definition;

You can also use related, when you want two or more models to share the same parent:

from model_bakery.recipe import related, Recipe
from shop.models import Customer, PurchaseHistory

history = Recipe(PurchaseHistory)
customer_with_2_histories = Recipe(Customer,
    name='Albert',
    purchasehistory_set=related('history', 'history'),
)

Note this will only work when calling make_recipe because the related manager requires the objects in the related_set to be persisted. That said, calling prepare_recipe the related_set will be empty.

If you want to set m2m relationship you can use related as well:

from model_bakery.recipe import related, Recipe

pencil = Recipe(Product, name='Pencil')
pen = Recipe(Product, name='Pen')
history = Recipe(PurchaseHistory)

history_with_prods = history.extend(
    products=related(pencil, pen)
)

Recipes with callables

It’s possible to use callables as recipe’s attribute value.

from datetime import date
from model_bakery.recipe import Recipe
from shop.models import Customer

customer = Recipe(
    Customer,
    birthday=date.today,
)

When you call make_recipe, Model Bakery will set the attribute to the value returned by the callable.

Recipes with iterators

You can also use iterators (including generators) to provide multiple values to a recipe.

from itertools import cycle

names = ['Ada Lovelace', 'Grace Hopper', 'Ida Rhodes', 'Barbara Liskov']
customer = Recipe(Customer,
    name=cycle(names)
)

Model Bakery will use the next value in the iterator every time you create a model from the recipe.

Sequences in recipes

Sometimes, you have a field with an unique value and using make can cause random errors. Also, passing an attribute value just to avoid uniqueness validation problems can be tedious. To solve this you can define a sequence with seq

>>> from model_bakery.recipe import Recipe, seq
>>> from shop.models import Customer

>>> customer = Recipe(Customer,
    name=seq('Joe'),
    age=seq(15)
)

>>> customer = baker.make_recipe('shop.customer')
>>> customer.name
'Joe1'
>>> customer.age
16

>>> new_customer = baker.make_recipe('shop.customer')
>>> new_customer.name
'Joe2'
>>> new_customer.age
17

This will append a counter to strings to avoid uniqueness problems and it will sum the counter with numerical values.

Sequences and iterables can be used not only for recipes, but with baker.make as well:

# it can be imported directly from model_bakery
>>> from model_bakery import seq
>>> from model_bakery import baker

>>> customer = baker.make('Customer', name=seq('Joe'))
>>> customer.name
'Joe1'

>>> customers = baker.make('Customer', name=seq('Chad'), _quantity=3)
>>> for customer in customers:
...     print(customer.name)
'Chad1'
'Chad2'
'Chad3'

You can also provide an optional increment_by argument which will modify incrementing behaviour. This can be an integer, float, Decimal or timedelta. If you want to start your increment differently, you can use the start argument, only if it’s not a sequence for date, datetime or time objects.

>>> from datetime import date, timedelta
>>> from model_bakery.recipe import Recipe, seq
>>> from shop.models import Customer


>>> customer = Recipe(Customer,
    age=seq(15, increment_by=3)
    height_ft=seq(5.5, increment_by=.25)
    # assume today's date is 21/07/2014
    appointment=seq(date(2014, 7, 21), timedelta(days=1)),
    name=seq('Custom num: ', increment_by=2, start=5),
)

>>> customer = baker.make_recipe('shop.customer')
>>> customer.age
18
>>> customer.height_ft
5.75
>>> customer.appointment
datetime.date(2014, 7, 22)
>>> customer.name
'Custom num: 5'

>>> new_customer = baker.make_recipe('shop.customer')
>>> new_customer.age
21
>>> new_customer.height_ft
6.0
>>> new_customer.appointment
datetime.date(2014, 7, 23)
>>> customer.name
'Custom num: 7'

Overriding recipe definitions

Passing values when calling make_recipe or prepare_recipe will override the recipe rule.

from model_bakery import baker

baker.make_recipe('shop.customer', name='Ada Lovelace')

This is useful when you have to create multiple objects and you have some unique field, for instance.

Recipe inheritance

If you need to reuse and override existent recipe call extend method:

customer = Recipe(
    Customer,
    bio='Some customer bio',
    age=30,
    happy=True,
)
sad_customer = customer.extend(
    happy=False,
)