Skip to content

Instantly share code, notes, and snippets.

@zacharyvoase
Created April 21, 2009 11:38
Show Gist options
  • Save zacharyvoase/99090 to your computer and use it in GitHub Desktop.
Save zacharyvoase/99090 to your computer and use it in GitHub Desktop.
# -*- 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