summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Rosner <brosner@gmail.com>2010-09-12 19:58:05 +0000
committerBrian Rosner <brosner@gmail.com>2010-09-12 19:58:05 +0000
commitb7f60045fe9e662c8c53a6f7b4e2c410830d5b29 (patch)
tree804ff0e8df2e8d5b02a1c06c8a534fe44943b2de
parent030c97b119bea450cc4e1f289a95e91aa664221c (diff)
Fixed #9015 -- added a signal decorator for simplifying signal connections
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13773 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/dispatch/__init__.py2
-rw-r--r--django/dispatch/dispatcher.py16
-rw-r--r--docs/topics/signals.txt18
-rw-r--r--tests/modeltests/signals/models.py34
4 files changed, 66 insertions, 4 deletions
diff --git a/django/dispatch/__init__.py b/django/dispatch/__init__.py
index 0798acc7db..9aeb83c5e1 100644
--- a/django/dispatch/__init__.py
+++ b/django/dispatch/__init__.py
@@ -6,4 +6,4 @@ See license.txt for original license.
Heavily modified for Django's purposes.
"""
-from django.dispatch.dispatcher import Signal \ No newline at end of file
+from django.dispatch.dispatcher import Signal, receiver \ No newline at end of file
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index de09334637..c4516d88b0 100644
--- a/django/dispatch/dispatcher.py
+++ b/django/dispatch/dispatcher.py
@@ -235,3 +235,19 @@ class Signal(object):
for idx, (r_key, _) in enumerate(self.receivers):
if r_key == key:
del self.receivers[idx]
+
+
+def receiver(signal, **kwargs):
+ """
+ A decorator for connecting receivers to signals. Used by passing in the
+ signal and keyword arguments to connect::
+
+ @receiver(post_save, sender=MyModel)
+ def signal_receiver(sender, **kwargs):
+ ...
+
+ """
+ def _decorator(func):
+ signal.connect(func, **kwargs)
+ return func
+ return _decorator
diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt
index 35dc3f4c09..78790db071 100644
--- a/docs/topics/signals.txt
+++ b/docs/topics/signals.txt
@@ -80,7 +80,8 @@ must be able to handle those new arguments.
Connecting receiver functions
-----------------------------
-Next, we'll need to connect our receiver to the signal:
+There are two ways you can connect a receiever to a signal. You can take the
+manual connect route:
.. code-block:: python
@@ -88,6 +89,17 @@ Next, we'll need to connect our receiver to the signal:
request_finished.connect(my_callback)
+Alternatively, you can use a decorator used when you define your receiver:
+
+.. code-block:: python
+
+ from django.core.signals import request_finished
+ from django.dispatch import receiver
+
+ @receiver(request_finished)
+ def my_callback(sender, **kwargs):
+ print "Request finished!"
+
Now, our ``my_callback`` function will be called each time a request finishes.
.. admonition:: Where should this code live?
@@ -115,13 +127,13 @@ signals sent by some model:
.. code-block:: python
from django.db.models.signals import pre_save
+ from django.dispatch import receiver
from myapp.models import MyModel
+ @receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
- pre_save.connect(my_handler, sender=MyModel)
-
The ``my_handler`` function will only be called when an instance of ``MyModel``
is saved.
diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py
index ea8137f657..8a500752be 100644
--- a/tests/modeltests/signals/models.py
+++ b/tests/modeltests/signals/models.py
@@ -3,6 +3,7 @@ Testing signals before/after saving and deleting.
"""
from django.db import models
+from django.dispatch import receiver
class Person(models.Model):
first_name = models.CharField(max_length=20)
@@ -11,6 +12,13 @@ class Person(models.Model):
def __unicode__(self):
return u"%s %s" % (self.first_name, self.last_name)
+class Car(models.Model):
+ make = models.CharField(max_length=20)
+ model = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return u"%s %s" % (self.make, self.model)
+
def pre_save_test(signal, sender, instance, **kwargs):
print 'pre_save signal,', instance
if kwargs.get('raw'):
@@ -52,22 +60,44 @@ __test__ = {'API_TESTS':"""
>>> models.signals.pre_delete.connect(pre_delete_test)
>>> models.signals.post_delete.connect(post_delete_test)
+# throw a decorator syntax receiver into the mix
+>>> @receiver(models.signals.pre_save)
+... def pre_save_decorator_test(signal, sender, instance, **kwargs):
+... print "pre_save signal decorator,", instance
+
+# throw a decorator syntax receiver into the mix
+>>> @receiver(models.signals.pre_save, sender=Car)
+... def pre_save_decorator_sender_test(signal, sender, instance, **kwargs):
+... print "pre_save signal decorator sender,", instance
+
>>> p1 = Person(first_name='John', last_name='Smith')
>>> p1.save()
pre_save signal, John Smith
+pre_save signal decorator, John Smith
post_save signal, John Smith
Is created
>>> p1.first_name = 'Tom'
>>> p1.save()
pre_save signal, Tom Smith
+pre_save signal decorator, Tom Smith
post_save signal, Tom Smith
Is updated
+# Car signal (sender defined)
+>>> c1 = Car(make="Volkswagon", model="Passat")
+>>> c1.save()
+pre_save signal, Volkswagon Passat
+pre_save signal decorator, Volkswagon Passat
+pre_save signal decorator sender, Volkswagon Passat
+post_save signal, Volkswagon Passat
+Is created
+
# Calling an internal method purely so that we can trigger a "raw" save.
>>> p1.save_base(raw=True)
pre_save signal, Tom Smith
Is raw
+pre_save signal decorator, Tom Smith
post_save signal, Tom Smith
Is updated
Is raw
@@ -82,12 +112,14 @@ instance.id is None: False
>>> p2.id = 99999
>>> p2.save()
pre_save signal, James Jones
+pre_save signal decorator, James Jones
post_save signal, James Jones
Is created
>>> p2.id = 99998
>>> p2.save()
pre_save signal, James Jones
+pre_save signal decorator, James Jones
post_save signal, James Jones
Is created
@@ -104,6 +136,8 @@ instance.id is None: False
>>> models.signals.pre_delete.disconnect(pre_delete_test)
>>> models.signals.post_save.disconnect(post_save_test)
>>> models.signals.pre_save.disconnect(pre_save_test)
+>>> models.signals.pre_save.disconnect(pre_save_decorator_test)
+>>> models.signals.pre_save.disconnect(pre_save_decorator_sender_test, sender=Car)
# Check that all our signals got disconnected properly.
>>> post_signals = (len(models.signals.pre_save.receivers),