Sunday, April 15, 2007

Logging all table changes to another table

Since the site is user-editable, it needs some way to log all changes to certain tables, so they can be undone, if hit by vandals adding bad data. What I came up with this: model classes that need this (not all do) inherit from Journalled (as well as SQLObject, of course). Journalled overrides the __setattr__ and set() methods, and creates entries in the UpdateLog table for each change.
class UpdateLog(SQLObject):
created = DateTimeCol(default=datetime.now)
changed_by = IntCol()
table_name = UnicodeCol(length=12)
table_id = IntCol()
attrib_name = UnicodeCol(length=20)
attrib_old_value = UnicodeCol()
attrib_new_value = UnicodeCol()

def _get_attrib_old_value(self):
return pickle.loads(str(self._SO_get_attrib_old_value()))
def _set_attrib_old_value(self, value):
self._SO_set_attrib_old_value(pickle.dumps(value))
def _get_attrib_new_value(self):
return pickle.loads(str(self._SO_get_attrib_new_value()))
def _set_attrib_new_value(self, value):
self._SO_set_attrib_new_value(pickle.dumps(value))
The old and new values are stored as actual Python objects. IDs are stored as just Ints, no relationships, since entries here can come from more than one table.
# Classes inheriting from this will have changes stored in the UpdateLog table
class Journalled(object):

def __setattr__(self, name, value):
if name in self.sqlmeta.columns.keys():
self._record_update({name:value})
super(Journalled, self).__setattr__(name, value)

def set(self, **kw):
self._record_update(kw)
super(Journalled, self).set(**kw)

def _record_update(self, updates):
for name, value in updates.iteritems():
old_value = getattr(self, name, None)
if old_value != value:
try:
current_user = identity.current.user.id
except:
current_user = None
u = UpdateLog(
changed_by=current_user,
table_name=self.sqlmeta.table,
table_id=self.id,
attrib_name=name,
attrib_old_value=old_value,
attrib_new_value=value
)
Honestly, with this in place, I haven't actually written the code to roll back any vandalism. The data is all in the UpdateLog, so I can put it off until the website actually gets hit ;-)

SQLObject svn trunk also appears to have recently added table versioning support. Sounds good, but I haven't tried it yet.

No comments: