Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- # module pyparsing.py
- #
- # Copyright (c) 2011 Nathan Duthoit
- #
- # Permission is hereby granted, free of charge, to any person obtaining
- # a copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish,
- # distribute, sublicense, and/or sell copies of the Software, and to
- # permit persons to whom the Software is furnished to do so, subject to
- # the following conditions:
- #
- # The above copyright notice and this permission notice shall be
- # included in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- #
- #
- from django.db import models
- from django.db.models.signals import post_init, pre_save, post_save
- from django.dispatch.dispatcher import receiver
- __author__ = 'nduthoit (Nathan Duthoit)'
- """
- Sample usage (using the spy_on class decorator for models)
- def log_change(book, old_title, new_title):
- print "Changed book title (old: %s) (new: %s)" % (old_title, new_title)
- @spy_on([Agent('title', log_change)])
- class Book(models.Model):
- title = models.CharField(max_length=20)
- author = models.CharField(max_length=20)
- The spy_on class decorator takes a list of Agent definitions. An Agent definition consists of:
- - name of the field to be watched/spied on
- - callback function to be called when the value of the field changes
- - optional switch to have the callback called before (default) or after the instance is saved
- Note that the callback function you pass in the agent definition will be passed exactly 3 arguments when called by spy: instance, old field value and new field value
- Known limitation: using the update method on a queryset will prevent django-spy from detecting the field value change (as the save method and related signals are not called)
- """
- def _watch_name(field_name):
- return "_bug_%s" % field_name
- class WatchException(Exception):
- pass
- class Agent(object):
- def __init__(self, field_name, on_change, pre_save=True):
- self.field_name = field_name
- self.on_change = on_change
- self.pre_save = pre_save
- def __unicode__(self):
- return u"%s - %s" % (self.field_name, self.on_change)
- def spy_on_model(model, agents):
- if not issubclass(model, models.Model):
- raise WatchException("'%s' is not a subclass of django.db.models.Model" % (model.__name__, ))
- model_fields = [field.name for field in model._meta.fields]
- pre_save_agents = []
- post_save_agents = []
- for agent in agents:
- field_name = agent.field_name
- is_pre_save = agent.pre_save
- if not field_name in model_fields:
- raise WatchException("'%s' is not a field of '%s'" % (field_name, model.__name__))
- if is_pre_save:
- pre_save_agents.append(agent)
- else:
- post_save_agents.append(agent)
- # Add an attribute for each agent (a 'bug') to keep track of the field values
- def bug_instance(instance):
- for agent in agents:
- field_name = agent.field_name
- agent_name = _watch_name(field_name)
- current_value = getattr(instance, field_name)
- setattr(instance, agent_name, current_value)
- dispatch_uid = 'spy_post_init_%s' % model.__name__
- @receiver(post_init, sender=model, dispatch_uid=dispatch_uid)
- def init_bugs(sender, instance, **kwargs):
- bug_instance(instance)
- def get_detect_change(pre_save=True):
- if pre_save:
- event_agents = pre_save_agents
- else:
- event_agents = post_save_agents
- def detect_change_and_reset_bugs(sender, instance, **kwargs):
- for agent in event_agents:
- field_name = agent.field_name
- agent_name = _watch_name(field_name)
- old_value = getattr(instance, agent_name)
- new_value = getattr(instance, field_name)
- # If the value has changed, call the on_change method of the agent
- if old_value != new_value:
- agent.on_change(instance, old_value, new_value)
- # Reset the bugs for this instance
- bug_instance(instance)
- return detect_change_and_reset_bugs
- detect_changes_pre_save = get_detect_change(True)
- detect_changes_post_save = get_detect_change(False)
- if pre_save_agents:
- dispatch_uid = 'spy_pre_save_%s' % model.__name__
- pre_save.connect(detect_changes_pre_save, sender=model, dispatch_uid=dispatch_uid)
- if post_save_agents:
- dispatch_uid = 'spy_post_save_%s' % model.__name__
- post_save.connect(detect_changes_post_save, sender=model, dispatch_uid=dispatch_uid)
- return model
- def spy_on(agents):
- def _spy_on_model(model):
- return spy_on_model(model, agents)
- return _spy_on_model
Add Comment
Please, Sign In to add comment