Repository: FactoryBoy/factory_boy

Issue:
Unusable password generator for Django
#### The problem

The recently added `Password` generator for Django is helpful, but it's not clear how to use it to create an unusable password (similar to calling `set_unusable_password` on the generated user).

#### Proposed solution

Django's `set_unusable_password` is a call to `make_password` with `None` as the password argument:

https://github.com/django/django/blob/0b506bfe1ab9f1c38e439c77b3c3f81c8ac663ea/django/contrib/auth/base_user.py#L118-L120

Using `password = factory.django.Password(None)` will actually work (and will allow factory users to override the password if desired). However, currently the password argument to this factory is documented as a string and this option is not mentioned.

#### Extra notes

The default value of the `password` argument to `factory.django.Password` could also be set to `None`. This would make that factory generate unusable passwords by default, which may or may not be desired.


Comments:

Patch:
diff --git a/docs/orms.rst b/docs/orms.rst
index 3839707..5967e1b 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -128,7 +128,11 @@ Extra fields
 
     .. method:: __init__(self, password)
 
-        :param str password: Default password.
+        :param str or None password: Default password.
+
+    .. note:: When the ``password`` argument is ``None``, the resulting password is
+              unusable as if ``set_unusable_password()`` were used. This is distinct
+              from setting the password to an empty string.
 
     .. code-block:: python
 
@@ -149,6 +153,10 @@ Extra fields
         >>> other_user = UserFactory.create(password='other_pw')
         >>> check_password('other_pw', other_user.password)
         True
+        >>> # Set unusable password
+        >>> no_password_user = UserFactory.create(password=None)
+        >>> no_password_user.has_usable_password()
+        False
 
 
 .. class:: FileField
diff --git a/factory/django.py b/factory/django.py
index 87b6fd5..9526b77 100644
--- a/factory/django.py
+++ b/factory/django.py
@@ -311,7 +311,7 @@ class mute_signals:
             logger.debug('mute_signals: Restoring signal handlers %r',
                          receivers)
 
-            signal.receivers += receivers
+            signal.receivers = receivers + signal.receivers
             with signal.lock:
                 # Django uses some caching for its signals.
                 # Since we're bypassing signal.connect and signal.disconnect,


Test patch:
diff --git a/tests/test_django.py b/tests/test_django.py
index 19729e0..066d792 100644
--- a/tests/test_django.py
+++ b/tests/test_django.py
@@ -941,6 +941,22 @@ class PreventSignalsTestCase(django_test.TestCase):
 
         self.assertTrue(self.handlers.created_during_instantiation.called)
 
+    def test_signal_receiver_order_restored_after_mute_signals(self):
+        def must_be_first(*args, **kwargs):
+            self.handlers.do_stuff(1)
+
+        def must_be_second(*args, **kwargs):
+            self.handlers.do_stuff(2)
+
+        signals.post_save.connect(must_be_first)
+        with factory.django.mute_signals(signals.post_save):
+            WithSignalsFactory(post_save_signal_receiver=must_be_second)
+            self.assertEqual(self.handlers.do_stuff.call_args_list, [mock.call(2)])
+
+        self.handlers.reset_mock()
+        WithSignalsFactory(post_save_signal_receiver=must_be_second)
+        self.assertEqual(self.handlers.do_stuff.call_args_list, [mock.call(1), mock.call(2)])
+
     def test_signal_cache(self):
         with factory.django.mute_signals(signals.pre_save, signals.post_save):
             signals.post_save.connect(self.handlers.mute_block_receiver)