Created
March 19, 2009 18:27
-
-
Save zacharyvoase/81989 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Index: django/db/models/base.py | |
=================================================================== | |
--- django/db/models/base.py (revision 10088) | |
+++ django/db/models/base.py (working copy) | |
@@ -188,7 +188,11 @@ | |
new_class._prepare() | |
register_models(new_class._meta.app_label, new_class) | |
- | |
+ | |
+ # Add the default signals to the new model. A model's signals will be | |
+ # available as the 'signals' attribute on the model class. | |
+ new_class.add_to_class('signals', signals.ModelSignals) | |
+ | |
# Because of the way imports happen (recursively), we may or may not be | |
# the first time this model tries to register with the framework. There | |
# should only be one class for each model, so we always return the | |
Index: django/db/models/signals.py | |
=================================================================== | |
--- django/db/models/signals.py (revision 10088) | |
+++ django/db/models/signals.py (working copy) | |
@@ -1,5 +1,9 @@ | |
+from functools import partial, wraps | |
+import re | |
+ | |
from django.dispatch import Signal | |
+ | |
class_prepared = Signal(providing_args=["class"]) | |
pre_init = Signal(providing_args=["instance", "args", "kwargs"]) | |
@@ -12,3 +16,46 @@ | |
post_delete = Signal(providing_args=["instance"]) | |
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"]) | |
+ | |
+ | |
+class ModelSignals(object): | |
+ | |
+ """ | |
+ Provides a set of decorators for registering model-specific signal handlers. | |
+ | |
+ A model class's individual ``ModelSignals`` instance is available as its | |
+ `signals` attribute. This has a set of instance methods which may be used as | |
+ decorators for connecting signal handlers to the model's | |
+ """ | |
+ | |
+ def __init__(self, model): | |
+ self.model = model | |
+ | |
+ def deco_connect(self, signal_name, function): | |
+ | |
+ @wraps(function) | |
+ def handler(*args, **kwargs): | |
+ # This ModelSignal instance is attached to a model, so we already | |
+ # know what the sender will be, and can safely discard it. | |
+ kwargs.pop('sender', None) | |
+ # This is the instance which is being initialized, saved or deleted. | |
+ instance = kwargs.pop('instance', None) | |
+ | |
+ if instance: | |
+ return function(instance, *args, **kwargs) | |
+ return function(*args, **kwargs) | |
+ | |
+ # Using weak=False is necessary to prevent the temporarily-created | |
+ # handler function from being garbage collected. | |
+ globals()[signal_name].connect(handler, sender=self.model, weak=False) | |
+ | |
+ return function | |
+ | |
+ def __getattribute__(self, attribute): | |
+ if not re.match(r'^(pre|post)_(init|save|delete)', attribute): | |
+ return object.__getattribute__(self, attribute) | |
+ return partial(self.deco_connect, attribute) | |
+ | |
+ @classmethod | |
+ def contribute_to_class(cls, class_, name): | |
+ setattr(class_, name, cls(class_)) | |
\ No newline at end of file | |
Index: django/dispatch/dispatcher.py | |
=================================================================== | |
--- django/dispatch/dispatcher.py (revision 10088) | |
+++ django/dispatch/dispatcher.py (working copy) | |
@@ -123,7 +123,47 @@ | |
for idx, (r_key, _) in enumerate(self.receivers): | |
if r_key == lookup_key: | |
del self.receivers[idx] | |
- | |
+ | |
+ def receive(self, *args, **kwargs): | |
+ """Connect receiver to sender for signal (decorator) | |
+ | |
+ Can be used in two different ways: | |
+ | |
+ @signal.receive | |
+ def receiver(*args, **kwargs): | |
+ pass | |
+ | |
+ @signal.receive(sender=SenderClass) | |
+ def receiver(*args, **kwargs): | |
+ pass | |
+ | |
+ In the first form, this method simply connects the signal and returns | |
+ the original receiver. In the second form, this method returns a | |
+ connector function which acts as a closure, capturing the passed keyword | |
+ arguments; this will then connect and return the receiver. | |
+ """ | |
+ | |
+ # Requires either only one argument (i.e. the first form in the above | |
+ # docstring), which will be the receiver, or no arguments. | |
+ if len(args) > 1: | |
+ raise TypeError('Illegal number of arguments for decorator') | |
+ | |
+ # This means that the first form of the above docstring was used, so the | |
+ # handler should be connected as-is. Because this is a decorator, it is | |
+ # necessary to return the function afterwards. | |
+ if args: | |
+ self.connect(*args, **kwargs) | |
+ return args[0] | |
+ | |
+ # This means the second form was used, in which case a further decorator | |
+ # must be made and returned which will simply close over the passed | |
+ # kwargs and connect the function it receives. | |
+ else: | |
+ def connector(function): | |
+ self.connect(function, **kwargs) | |
+ return function | |
+ return connector | |
+ | |
def send(self, sender, **named): | |
"""Send signal from sender to all connected receivers. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# The standard imports, along with the signals module. This is used in the very | |
# last signal handler example, where a raw Signal object is used to handle | |
from django.db import models | |
from django.db.models import signals | |
# A pretty standard model definition. | |
class MyModel(models.Model): | |
field1 = models.IntegerField(default=0) | |
field2 = models.CharField(max_length=128) | |
# The following two examples show how the model-specific signals might work; | |
# note the 'signals' class attribute on the MyModel class which provides these | |
# signals and also the fact that the 'instance' keyword argument has | |
# automatically been turned into a positional argument (in the interest of both | |
# brevity and DRought). | |
@MyModel.signals.pre_save | |
def signal_handler1(mymodel, *args, **kwargs): | |
"""Connected to MyModel's pre_save event.""" | |
do_something(mymodel) | |
@MyModel.signals.post_save | |
def signal_handler2(mymodel, *args, **kwargs): | |
"""Connected to MyModel's post_save event.""" | |
do_something_else(mymodel) | |
# Note that the following connects the signal_handler3 function to the global | |
# pre_save signal, although it does so in a more verbose way. This is a more | |
# general pattern of usage which will make it possible to use the same decorator | |
# syntax but with virtually any signal (including the class_prepared and the | |
# post_syncdb signals). | |
@signals.pre_save.receive | |
def signal_handler3(instance=None, sender=None, *args, **kwargs): | |
"""Connected to every pre_save event.""" | |
do_something_again(model_instance) | |
# This is similar to the example above, except some keyword arguments are | |
# specified in the decorator statement. Any additional kwargs will be passed to | |
# the signal's connect() method; in this case the signal_handler4 function will | |
# only receive signals sent by MyModel. This is functionally equivalent to | |
# decorating using ``MyModel.signals.pre_save()``. | |
@signals.pre_save.receive(sender=MyModel) | |
def signal_handler4(instance=None, sender=None, *args, **kwargs): | |
"""Connected to every pre_save event.""" | |
do_something_again(model_instance) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment