From da1425e87287ff04ed5c14f50ed9a86ae1128c58 Mon Sep 17 00:00:00 2001 From: Serkan Hosca Date: Thu, 9 Jun 2022 15:43:09 -0400 Subject: [PATCH 1/2] Keep dynamically added model receivers at the end when unmuting signals Receiver order is somewhat important and packages like `django-dirtyfields` adds receivers dynamically where it can reset the model state. This change restores the original receivers first then appends any new dynamically added receivers. --- factory/django.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/factory/django.py b/factory/django.py index 23b93425..5d42e384 100644 --- a/factory/django.py +++ b/factory/django.py @@ -306,8 +306,13 @@ def __exit__(self, exc_type, exc_value, traceback): logger.debug('mute_signals: Restoring signal handlers %r', receivers) - signal.receivers += receivers with signal.lock: + new_receivers = signal.receivers + signal.receivers = receivers + for lookup_key, receiver in new_receivers: + # add dynamic receivers same way django signal adds them + if all(r_key != lookup_key for r_key, _ in signal.receivers): + signal.receivers.append((lookup_key, receiver)) # Django uses some caching for its signals. # Since we're bypassing signal.connect and signal.disconnect, # we have to keep messing with django's internals. From 24bb2241349a34531614df423dc3b2f18eb670f8 Mon Sep 17 00:00:00 2001 From: Serkan Hosca Date: Fri, 10 Jun 2022 07:07:52 -0400 Subject: [PATCH 2/2] Extendd test for checking the order of restored receivers --- tests/test_django.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_django.py b/tests/test_django.py index 2401dae6..e615a84e 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -927,10 +927,17 @@ def test_context_manager(self): self.assertSignalsReactivated() def test_receiver_created_during_model_instantiation_is_not_lost(self): + original_receiver_keys = [r_key for r_key, _ in signals.post_save.receivers] + with factory.django.mute_signals(signals.post_save): instance = WithSignalsFactory(post_save_signal_receiver=self.handlers.created_during_instantiation) self.assertTrue(self.handlers.created_during_instantiation.called) + restored_receiver_keys = [r_key for r_key, _ in signals.post_save.receivers] + self.assertTrue( + all(orig == restored for orig, restored in zip(original_receiver_keys, restored_receiver_keys)) + ) + self.handlers.created_during_instantiation.reset_mock() instance.save()