From 72fd943513b0e516f06c53b13ff35ca814b0a4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 26 Mar 2015 22:37:54 +0100 Subject: Fix issues between mute_signals() and factory inheritance (Closes #183). Previously, if a factory was decorated with ``@mute_signals`` and one of its descendant called another one of its descendant, signals weren't unmuted properly. --- docs/changelog.rst | 4 ++++ factory/django.py | 9 +++++++-- tests/test_django.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 13fdd68..0cf8368 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,10 @@ ChangeLog - Add support for getting/setting :mod:`factory.fuzzy`'s random state (see :issue:`175`, :issue:`185`). - Support lazy evaluation of iterables in :class:`factory.fuzzy.FuzzyChoice` (see :issue:`184`). +*Bugfix:* + + - Avoid issues when using :meth:`factory.django.mute_signals` on a base factory class (see :issue:`183`). + *Deprecation:* - Remove deprecated features from :ref:`v2.4.0` diff --git a/factory/django.py b/factory/django.py index 7050366..e823ee9 100644 --- a/factory/django.py +++ b/factory/django.py @@ -269,6 +269,9 @@ class mute_signals(object): signal.receivers = receivers self.paused = {} + def copy(self): + return mute_signals(*self.signals) + def __call__(self, callable_obj): if isinstance(callable_obj, base.FactoryMetaClass): # Retrieve __func__, the *actual* callable object. @@ -277,7 +280,8 @@ class mute_signals(object): @classmethod @functools.wraps(generate_method) def wrapped_generate(*args, **kwargs): - with self: + # A mute_signals() object is not reentrant; use a copy everytime. + with self.copy(): return generate_method(*args, **kwargs) callable_obj._generate = wrapped_generate @@ -286,7 +290,8 @@ class mute_signals(object): else: @functools.wraps(callable_obj) def wrapper(*args, **kwargs): - with self: + # A mute_signals() object is not reentrant; use a copy everytime. + with self.copy(): return callable_obj(*args, **kwargs) return wrapper diff --git a/tests/test_django.py b/tests/test_django.py index 0cbef19..4653305 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -592,6 +592,27 @@ class PreventSignalsTestCase(unittest.TestCase): self.assertSignalsReactivated() + def test_class_decorator_with_subfactory(self): + @factory.django.mute_signals(signals.pre_save, signals.post_save) + class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): + class Meta: + model = models.WithSignals + + @factory.post_generation + def post(obj, create, extracted, **kwargs): + if not extracted: + WithSignalsDecoratedFactory.create(post=42) + + # This will disable the signals (twice), create two objects, + # and reactivate the signals. + WithSignalsDecoratedFactory() + + self.assertEqual(self.handlers.pre_init.call_count, 2) + self.assertFalse(self.handlers.pre_save.called) + self.assertFalse(self.handlers.post_save.called) + + self.assertSignalsReactivated() + def test_class_decorator_build(self): @factory.django.mute_signals(signals.pre_save, signals.post_save) class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): -- cgit v1.2.3