Browse Source

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
Brian Rosner 14 years ago
parent
commit
b7f60045fe

+ 1 - 1
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
+from django.dispatch.dispatcher import Signal, receiver

+ 16 - 0
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

+ 15 - 3
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.
 

+ 34 - 0
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),