From b7f60045fe9e662c8c53a6f7b4e2c410830d5b29 Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Sun, 12 Sep 2010 19:58:05 +0000 Subject: [PATCH] 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 --- django/dispatch/__init__.py | 2 +- django/dispatch/dispatcher.py | 16 ++++++++++++++ docs/topics/signals.txt | 18 +++++++++++++--- tests/modeltests/signals/models.py | 34 ++++++++++++++++++++++++++++++ 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),