Created
April 21, 2009 11:38
-
-
Save zacharyvoase/99090 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
# -*- coding: utf-8 -*- | |
""" | |
Models with signals. | |
Example:: | |
from django.db import models | |
import modsig | |
class MyModel(modsig.Model): | |
field1 = models.CharField(max_length=100) | |
... | |
@MyModel.signals.pre_save | |
def some_signal_handler(instance, *args, **kwargs): | |
pass # do something here... | |
@MyModels.signals.post_save | |
def some_signal_handler(instance, created=True, *args, **kwargs): | |
if created: | |
print 'RECORD CREATED!' | |
# do something else... | |
Valid signals are ``pre_init``, ``post_init``, ``pre_save``, ``post_save``, | |
``pre_delete`` and ``post_delete``. | |
""" | |
from django.db import models | |
VALID_SIGNALS = ['pre_init', | |
'post_init', | |
'pre_save', | |
'post_save', | |
'pre_delete', | |
'post_delete'] | |
class InvalidSignalError(Exception): | |
pass | |
class ModelSignals(object): | |
def __init__(self, model): | |
self.model = model | |
def connector(self, signal_name): | |
"""Generates a decorator to connect signal handlers.""" | |
if signal_name not in VALID_SIGNALS: | |
raise InvalidSignalError(signal_name + ' is not a valid signal.') | |
# Generate a suitable decorator for the signal. | |
def decorator(function): | |
if hasattr(function, 'signal_wrapper'): | |
# The signal wrapper has already been generated; simply connect | |
# and return the function. | |
(getattr(models.signals, signal_name).connect( | |
function.signal_wrapper, sender=self.model, weak=False)) | |
return function | |
# Define the signal wrapper, which adjusts arguments suitably. | |
@wraps(function) | |
def wrapper(*args, **kwargs): | |
# Remove the 'sender' kwarg because we already know what it is. | |
kwargs.pop('sender', None) | |
if 'instance' in kwargs: | |
# The instance becomes the first positional argument. | |
args = (kwargs.pop('instance'),) + args | |
return function(*args, **kwargs) | |
getattr(models.signals, signal_name).connect(wrapper, | |
sender=self.model, weak=False) | |
# Store the signal wrapper on the function; this prevents it from | |
# being garbage collected, and caches it so it does not need to be | |
# regenerated if the function is wrapped again. | |
function.signal_wrapper = wrapper | |
return function | |
decorator.__name__ = signal_name | |
return decorator | |
def __getattribute__(self, attribute): | |
try: | |
return object.__getattribute__(self, attribute) | |
except AttributeError: | |
if attribute in VALID_SIGNALS: | |
return self.connector(attribute) | |
@classmethod | |
def contribute_to_class(cls, class_, name): | |
setattr(class_, name, cls(class_)) | |
class ModelBase(models.base.ModelBase): | |
def __new__(cls, name, bases, attrs): | |
model = super(ModelBase, cls).__new__(cls, name, bases, attrs) | |
model.add_to_class('signals', ModelSignals) | |
return model | |
class Model(models.Model): | |
__metaclass__ = ModelBase | |
class Meta(object): | |
abstract = True | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment