Skip to content

Commit

Permalink
Merge pull request #813 from PetrDlouhy/explicit_bakery_indication
Browse files Browse the repository at this point in the history
Raise exception when usage through model_mommy/model_bakery is not explicitly indicated
  • Loading branch information
matthiask committed Aug 2, 2022
2 parents 7dbfcb6 + e39c9c4 commit 4ccdc1f
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Topic docs:
utilities
changelog
technical_details
testing

.. toctree::
:maxdepth: 3
Expand Down
24 changes: 24 additions & 0 deletions docs/testing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
===================
Testing MPTT models
===================


Using testing generators
========================

Using testing generators such as ``model_mommy`` or ``model_bakery`` is causing random tree fields values, which can cause unexpected behavior.
To prevent that the ``django-mptt.MPTTModel`` will throw an Exception if made throught model_mommy/model_bakery in test environment unless
the ``MPTT_ALLOW_TESTING_GENERATORS`` setting is set to True.

You can set the ``MPTT_ALLOW_TESTING_GENERATORS`` setting to True in your Django testing settings.py file or by the ``@override_settings`` decorator for particular test.
You would probably also have to use recipe and explicitly set the appropriate fields for the model.

.. code-block:: python
from django.test import override_settings
from baker.recipe import Recipe
@override_settings(MPTT_ALLOW_TESTING_GENERATORS=True)
def test_mptt_allow_testing_generators(self):
my_model_recipe = Recipe(MyMPTTModel, lft=None, rght=None)
test_model_instance = my_model_recipe.make()
39 changes: 39 additions & 0 deletions mptt/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import inspect
import operator
import sys
import threading
from functools import reduce, wraps

from django.conf import settings
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.query import Q
Expand Down Expand Up @@ -437,6 +440,9 @@ class Meta:
objects = TreeManager()

def __init__(self, *args, **kwargs):
if hasattr(self, "_check_no_testing_generators"):
self._check_no_testing_generators()

super().__init__(*args, **kwargs)
self._mptt_meta.update_mptt_cached_fields(self)

Expand Down Expand Up @@ -1157,3 +1163,36 @@ def _mptt_refresh(self):
)
for k, v in values.items():
setattr(self, k, v)


def _check_no_testing_generators(self):
"""Check that we are not generationg model from model_mommy or model_bakery"""
if sys.argv[1:2] == ["test"]: # in testing envirnoment
curframe = inspect.currentframe()
call_frame = inspect.getouterframes(curframe, 0)
call_file = call_frame[5][1]
call_directory = call_file.split("/")[-2]
if ("model_mommy" in call_file or "model_bakery" in call_file) and not getattr(
settings, "MPTT_ALLOW_TESTING_GENERATORS", False
):
raise Exception(
f"The {call_directory} populates django-mptt fields with random values which leads to unpredictable behavior. "
"If you really want to generate this model that way, please set MPTT_ALLOW_TESTING_GENERATORS=True in your settings.",
)


# Use _check_no_testing_generators function only if model_mommy or model_bakery is in the path
try:
import model_mommy # noqa

MPTTModel._check_no_testing_generators = _check_no_testing_generators
except ImportError:
pass


try:
import model_bakery # noqa

MPTTModel._check_no_testing_generators = _check_no_testing_generators
except ImportError:
pass
31 changes: 30 additions & 1 deletion tests/myapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from django.db.models import Q
from django.db.models.query_utils import DeferredAttribute
from django.template import Context, Template, TemplateSyntaxError
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings
from model_bakery import baker
from model_mommy import mommy

from mptt.admin import TreeRelatedFieldListFilter
from mptt.querysets import TreeQuerySet
Expand Down Expand Up @@ -3090,3 +3092,30 @@ def test_get_user_field_names_with_not_concrete_fields(self):
instance = NotConcreteFieldModel()
field_names = instance._get_user_field_names()
self.assertEqual(field_names, ["parent"])


class BakeryTest(TestCase):
@override_settings(MPTT_ALLOW_TESTING_GENERATORS=True)
def test_create_by_bakery(self):
book = baker.make("Book")
self.assertQuerysetEqual(book.get_ancestors(), [])
self.assertQuerysetEqual(book.get_descendants(), [])
book_mommy = mommy.make("Book")
self.assertQuerysetEqual(book_mommy.get_ancestors(), [])
self.assertQuerysetEqual(book_mommy.get_descendants(), [])

def test_create_by_bakery_exception(self):
with self.assertRaisesRegex(
Exception, "^The model_bakery populates django-mptt.*"
):
book = baker.make("Book")
self.assertQuerysetEqual(book.get_ancestors(), [])
self.assertQuerysetEqual(book.get_descendants(), [])

def test_create_by_mommy_exception(self):
with self.assertRaisesRegex(
Exception, "^The model_mommy populates django-mptt.*"
):
book = mommy.make("Book")
self.assertQuerysetEqual(book.get_ancestors(), [])
self.assertQuerysetEqual(book.get_descendants(), [])
2 changes: 2 additions & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ mock-django
Django >= 2.2
coverage
django-js-asset
model-bakery
model-mommy
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ deps =
dj32: Django>=3.2,<4.0
dj40: Django>=4.0,<4.1
djmain: https://github.com/django/django/archive/main.tar.gz
model-bakery
model-mommy

[testenv:style]
deps =
Expand Down

0 comments on commit 4ccdc1f

Please sign in to comment.