Skip to content

Instantly share code, notes, and snippets.

@thomlinton
Forked from dcramer/track_data.py
Created November 23, 2011 07:25
Show Gist options
  • Select an option

  • Save thomlinton/1388097 to your computer and use it in GitHub Desktop.

Select an option

Save thomlinton/1388097 to your computer and use it in GitHub Desktop.
Tracking changes on properties in Django
from django.db.models.signals import post_init
def track_data(*fields):
"""
Tracks property changes on a model instance.
The changed list of properties is refreshed on model initialization
and save.
>>> @track_data('name')
>>> class Post(models.Model):
>>> name = models.CharField(...)
>>>
>>> @classmethod
>>> def post_save(cls, sender, instance, created, **kwargs):
>>> if instance.has_changed('name'):
>>> print "Hooray!"
"""
UNSAVED = dict()
def _store(self):
"Updates a local copy of attributes values"
if self.id:
self.__data = {}
for f in fields:
field_info = self._meta.get_field_by_name(f)[0]
if hasattr(field_info, 'related') and field_info.related.parent_model == field_info.related.model and field_info.value_from_object(self) == getattr(self, field_info.rel.field_name):
logger.warning("Not tracking attribute '%s' on %s because it is self-referential." % (f, repr(self)))
continue
self.__data[f] = getattr(self, f)
else:
self.__data = UNSAVED
def inner(cls):
# contains a local copy of the previous values of attributes
cls.__data = {}
def has_changed(self, field):
"Returns ``True`` if ``field`` has changed since initialization."
if self.__data is UNSAVED:
return False
return self.__data.get(field) is not getattr(self, field)
cls.has_changed = has_changed
def old_value(self, field):
"Returns the previous value of ``field``"
return self.__data.get(field)
cls.old_value = old_value
def whats_changed(self):
"Returns a list of changed attributes."
changed = {}
if self.__data is UNSAVED:
return changed
for k, v in self.__data.iteritems():
if v != getattr(self, k):
changed[k] = v
return changed
cls.whats_changed = whats_changed
# Ensure we are updating local attributes on model init
def _post_init(sender, instance, **kwargs):
_store(instance)
post_init.connect(_post_init, sender=cls, weak=False)
# Ensure we are updating local attributes on model save
def save(self, *args, **kwargs):
save._original(self, *args, **kwargs)
_store(self)
save._original = cls.save
cls.save = save
return cls
return inner
@thomlinton
Copy link
Author

Update to avoid blowing up your stack when monitoring fields that could be self-referential (i.e., foreign key fields to self)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment