Implement lazy_related for use in override attrs in each TestCase
danizen opened this issue · 1 comments
Current behavior
I'm attempting to address the shortcoming of creating multiple models related to a single instance of a recipe, as best described in #144.
This is in the context of a single django.test.TestCase
, or when implemented as a fixture for pytest-django.
Theory of design
The last work on this identified the essential problem of a mommy remembering what it had created. I think it is better if something like related remembers what it has created, but since an mommy_recipes file is imported once per test run, we'd best do that in the unit test setUp or in a pytest-django fixture.
To do that well, we need some syntactic sugar that makes it easy to use delegate creation to an object that will return the related models once. Here, I take advantage of the tight coupling between model_mommy and Django and use django.utils.functional.SimpleLazyObject
to substitute the results returned by model_mommy.recipe.related
.
Suggested behavior
In myapp/mommy_recipes.py
:
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from faker import Faker
from model_mommy.recipe import Recipe, seq
faker = Faker()
User = get_user_model()
basicuser = Recipe(User,
username=seq('rich'),
email=faker.email,
first_name=faker.first_name,
last_name=faker.last_name,
is_staff=False,
is_superuser=False,
)
staffuser = basicuser.extend(
username=seq('bob'),
is_staff=True,
)
admingroup = Recipe(Group, name='Admin')
In myapp/tests.py
:
from model_mommy import mommy
from model_mommy.recipe import lazy_related
from django.test import TestCase
class TestLazyRelated(TestCase):
def setUp(self):
self.admins = mommy.make_recipe('myapp.staffuser',
groups=lazy_related('admingroup'),
_quantity=2,
)
def test_group_is_unique(self):
user_count = User.objects.count()
assert user_count == 3
group_count = Group.objects.count()
assert group_count == 1
admingroup = Group.objects.filter(name='Admin').first()
assert all(user.groups.count() == 1 for user in User.objects.all())
assert all(group.id == admingroup.id for user in User.objects.all() for group in user.groups.all())
Suggested Implementation
Added to model_mommy\recipes.py
:
class __callable_related(related):
"""Make related callable so we can use SimpleLazyObject"""
# don't export this class; it could interfere with related being called as a recipe
def __init__(self, *args):
super().__init__(*args)
def __call__(self):
return self.make()
# should be at top
from django.utils.functional import SimpleLazyObject
def lazy_related(*args):
"""
If used in mommy_recipes, this solution will backfire, because the database will be rolled back.
Only use lazy_related in class-based test cases or when creating pytest-django fixtures.
{{ additional_documentation }}
"""
return SimpleLazyObject(__callable_related(*args))
Example of pytest-django fixture:
import pytest
from model_mommy import mommy
from model_mommy.recipe import lazy_related
@pytest.mark.django_db
@pytest.fixture
def users_with_group():
return mommy.make_recipe('myapp.staffuser',
groups=lazy_related('admingroup'),
_quantity=2,
)
Reproduction Steps
Lots of instances of the problem in #144 and others.
Versions
For me, I'm working with the following versions at the moment:
Python: CPython 3.6.8
Django: 2.2.5
Model Mommy: 1.6.0
Ok - my code was working because of the Group to User is a many-to-many field, and I need to repeat with multple calls to make_recipe to double check.