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):
        ...
This commit is contained in:
Dmitry Medvinsky 2012-06-08 14:00:51 +04:00 committed by Florian Apolloner
parent 946d3d9f84
commit d4da08375b
5 changed files with 52 additions and 7 deletions

View File

@ -257,14 +257,21 @@ class Signal(object):
def receiver(signal, **kwargs): def receiver(signal, **kwargs):
""" """
A decorator for connecting receivers to signals. Used by passing in the 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) @receiver(post_save, sender=MyModel)
def signal_receiver(sender, **kwargs): def signal_receiver(sender, **kwargs):
... ...
@receiver([post_save, post_delete], sender=MyModel)
def signals_receiver(sender, **kwargs):
...
""" """
def _decorator(func): 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 func
return _decorator return _decorator

View File

@ -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 * In the localflavor for Canada, "pq" was added to the acceptable codes for
Quebec. It's an old abbreviation. 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 Backwards incompatible changes in 1.5
===================================== =====================================

View File

@ -52,10 +52,10 @@ called when the signal is sent by using the
:meth:`.Signal.connect` method: :meth:`.Signal.connect` method:
.. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None]) .. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None])
:param receiver: The callback function which will be connected to this :param receiver: The callback function which will be connected to this
signal. See :ref:`receiver-functions` for more information. signal. See :ref:`receiver-functions` for more information.
:param sender: Specifies a particular sender to receive signals from. See :param sender: Specifies a particular sender to receive signals from. See
:ref:`connecting-to-specific-signals` for more information. :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. 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 .. versionadded:: 1.3
The ``receiver`` decorator was added in Django 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? .. admonition:: Where should this code live?
You can put signal handling and registration code anywhere you like. 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 In some circumstances, the module in which you are connecting signals may be
imported multiple times. This can cause your receiver function to 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 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 If this behavior is problematic (such as when using signals to
send an email whenever a model is saved), pass a unique identifier as send an email whenever a model is saved), pass a unique identifier as

View File

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

View File

@ -2,7 +2,7 @@ import gc
import sys import sys
import time import time
from django.dispatch import Signal from django.dispatch import Signal, receiver
from django.utils import unittest from django.utils import unittest
@ -33,6 +33,8 @@ class Callable(object):
return val return val
a_signal = Signal(providing_args=["val"]) a_signal = Signal(providing_args=["val"])
b_signal = Signal(providing_args=["val"])
c_signal = Signal(providing_args=["val"])
class DispatcherTests(unittest.TestCase): class DispatcherTests(unittest.TestCase):
"""Test suite for dispatcher (barely started)""" """Test suite for dispatcher (barely started)"""
@ -123,3 +125,29 @@ class DispatcherTests(unittest.TestCase):
garbage_collect() garbage_collect()
a_signal.disconnect(receiver_3) a_signal.disconnect(receiver_3)
self._testIsClean(a_signal) 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)