Browse Source

Fixed #18454 -- Added ability to pass a list of signals to `receiver`.

Added ability to use receiver decorator in the following way:

    @receiver([post_save, post_delete], sender=MyModel)
    def signals_receiver(sender, **kwargs):
        ...
Dmitry Medvinsky 12 years ago
parent
commit
d4da08375b

+ 9 - 2
django/dispatch/dispatcher.py

@@ -257,14 +257,21 @@ class Signal(object):
 def receiver(signal, **kwargs):
     """
     A decorator for connecting receivers to signals. Used by passing in the
-    signal and keyword arguments to connect::
+    signal (or list of signals) and keyword arguments to connect::
 
         @receiver(post_save, sender=MyModel)
         def signal_receiver(sender, **kwargs):
             ...
 
+        @receiver([post_save, post_delete], sender=MyModel)
+        def signals_receiver(sender, **kwargs):
+            ...
+
     """
     def _decorator(func):
-        signal.connect(func, **kwargs)
+        if isinstance(signal, (list, tuple)):
+            [s.connect(func, **kwargs) for s in signal]
+        else:
+            signal.connect(func, **kwargs)
         return func
     return _decorator

+ 3 - 0
docs/releases/1.5.txt

@@ -103,6 +103,9 @@ Django 1.5 also includes several smaller improvements worth noting:
 * In the localflavor for Canada, "pq" was added to the acceptable codes for
   Quebec. It's an old abbreviation.
 
+* The :ref:`receiver <connecting-receiver-functions>` decorator is now able to
+  connect to more than one signal by supplying a list of signals.
+
 Backwards incompatible changes in 1.5
 =====================================
 

+ 10 - 3
docs/topics/signals.txt

@@ -52,10 +52,10 @@ called when the signal is sent by using the
 :meth:`.Signal.connect` method:
 
 .. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None])
-    
+
     :param receiver: The callback function which will be connected to this
         signal. See :ref:`receiver-functions` for more information.
-    
+
     :param sender: Specifies a particular sender to receive signals from. See
         :ref:`connecting-to-specific-signals` for more information.
 
@@ -129,10 +129,17 @@ receiver:
 
 Now, our ``my_callback`` function will be called each time a request finishes.
 
+Note that ``receiver`` can also take a list of signals to connect a function
+to.
+
 .. versionadded:: 1.3
 
 The ``receiver`` decorator was added in Django 1.3.
 
+.. versionchanged:: 1.5
+
+The ability to pass a list of signals was added.
+
 .. admonition:: Where should this code live?
 
     You can put signal handling and registration code anywhere you like.
@@ -182,7 +189,7 @@ Preventing duplicate signals
 In some circumstances, the module in which you are connecting signals may be
 imported multiple times. This can cause your receiver function to be
 registered more than once, and thus called multiples times for a single signal
-event. 
+event.
 
 If this behavior is problematic (such as when using signals to
 send an email whenever a model is saved), pass a unique identifier as

+ 1 - 1
tests/regressiontests/dispatch/tests/__init__.py

@@ -4,5 +4,5 @@ Unit-tests for the dispatch project
 
 from __future__ import absolute_import
 
-from .test_dispatcher import DispatcherTests
+from .test_dispatcher import DispatcherTests, ReceiverTestCase
 from .test_saferef import SaferefTests

+ 29 - 1
tests/regressiontests/dispatch/tests/test_dispatcher.py

@@ -2,7 +2,7 @@ import gc
 import sys
 import time
 
-from django.dispatch import Signal
+from django.dispatch import Signal, receiver
 from django.utils import unittest
 
 
@@ -33,6 +33,8 @@ class Callable(object):
         return val
 
 a_signal = Signal(providing_args=["val"])
+b_signal = Signal(providing_args=["val"])
+c_signal = Signal(providing_args=["val"])
 
 class DispatcherTests(unittest.TestCase):
     """Test suite for dispatcher (barely started)"""
@@ -123,3 +125,29 @@ class DispatcherTests(unittest.TestCase):
         garbage_collect()
         a_signal.disconnect(receiver_3)
         self._testIsClean(a_signal)
+
+
+class ReceiverTestCase(unittest.TestCase):
+    """
+    Test suite for receiver.
+
+    """
+    def testReceiverSingleSignal(self):
+        @receiver(a_signal)
+        def f(val, **kwargs):
+            self.state = val
+        self.state = False
+        a_signal.send(sender=self, val=True)
+        self.assertTrue(self.state)
+
+    def testReceiverSignalList(self):
+        @receiver([a_signal, b_signal, c_signal])
+        def f(val, **kwargs):
+            self.state.append(val)
+        self.state = []
+        a_signal.send(sender=self, val='a')
+        c_signal.send(sender=self, val='c')
+        b_signal.send(sender=self, val='b')
+        self.assertIn('a', self.state)
+        self.assertIn('b', self.state)
+        self.assertIn('c', self.state)