0001from sqlobject import *
0002from datetime import datetime
0003
0004class Version(SQLObject):
0005    def restore(self):
0006        values = self.sqlmeta.asDict()
0007        del values['id']
0008        del values['masterID']
0009        del values['dateArchived']
0010        for col in self.extraCols:
0011            del values[col]
0012        self.masterClass.get(self.masterID).set(**values)
0013
0014    def nextVersion(self):
0015        version = self.select(AND(self.q.masterID == self.masterID, self.q.id > self.id), orderBy=self.q.id)
0016        if version.count():
0017            return version[0]
0018        else:
0019            return self.master
0020
0021    def getChangedFields(self):
0022        next = self.nextVersion()
0023        columns = self.masterClass.sqlmeta.columns
0024        fields = []
0025        for column in columns:
0026            if column not in ["dateArchived", "id", "masterID"]:
0027                if getattr(self, column) != getattr(next, column):
0028                    fields.append(column.title())
0029
0030        return fields
0031
0032    @classmethod
0033    def select(cls, clause=None, *args, **kw):
0034        if not getattr(cls, '_connection', None):
0035            cls._connection = cls.masterClass._connection
0036        return super(Version, cls).select(clause, *args, **kw)
0037
0038    def __getattr__(self, attr):
0039        if attr in self.__dict__:
0040            return self.__dict__[attr]
0041        else:
0042            return getattr(self.master, attr)
0043
0044def getColumns(columns, cls):
0045    for column, defi in cls.sqlmeta.columnDefinitions.items():
0046        if column.endswith("ID") and isinstance(defi, ForeignKey):
0047            column = column[:-2]
0048
0049        #remove incompatible constraints
0050        kwds = dict(defi._kw)
0051        for kw in ["alternateID", "unique"]:
0052            if kw in kwds: del kwds[kw]
0053        columns[column] = defi.__class__(**kwds)
0054
0055    #ascend heirarchy
0056    if cls.sqlmeta.parentClass:
0057        getColumns(columns, cls.sqlmeta.parentClass)
0058
0059
0060class Versioning(object):
0061    def __init__(self, extraCols = None):
0062        if extraCols:
0063            self.extraCols = extraCols
0064        else:
0065            self.extraCols = {}
0066        pass
0067
0068    def __addtoclass__(self, soClass, name):
0069        self.name = name
0070        self.soClass = soClass
0071
0072        attrs = {'dateArchived': DateTimeCol(default=datetime.now),
0073                 'master': ForeignKey(self.soClass.__name__),
0074                 'masterClass' : self.soClass,
0075                 'extraCols' : self.extraCols
0076                 }
0077
0078        getColumns (attrs, self.soClass)
0079
0080        attrs.update(self.extraCols)
0081
0082        self.versionClass = type(self.soClass.__name__+'Versions',
0083                                 (Version,),
0084                                 attrs)
0085
0086        if  '_connection' in self.soClass.__dict__:
0087            self.versionClass._connection = self.soClass.__dict__['_connection']
0088
0089        events.listen(self.createTable,
0090                      soClass, events.CreateTableSignal)
0091        events.listen(self.rowUpdate, soClass,
0092                      events.RowUpdateSignal)
0093
0094    def createVersionTable(self, cls, conn):
0095         self.versionClass.createTable(ifNotExists=True, connection=conn)
0096
0097    def createTable(self, soClass, connection, extra_sql, post_funcs):
0098        assert soClass is self.soClass
0099        post_funcs.append(self.createVersionTable)
0100
0101    def rowUpdate(self, instance, kwargs):
0102        if instance.childName and instance.childName != self.soClass.__name__:
0103            return #if you want your child class versioned, version it.
0104
0105        values = instance.sqlmeta.asDict()
0106        del values['id']
0107        values['masterID'] = instance.id
0108        self.versionClass(connection=instance._connection, **values)
0109
0110    def __get__(self, obj, type=None):
0111        if obj is None:
0112            return self
0113        return self.versionClass.select(
0114            self.versionClass.q.masterID==obj.id, connection=obj._connection)