On 10/29/07, mmstud <[EMAIL PROTECTED]> wrote: > > On 29 loka, 09:08, [EMAIL PROTECTED] wrote: > > > I dont need history tracking, just revert documents to older ones. > > > > that is history, just not timed history. > > Most optimal would it be, if i can make rows with updated fields only, > not to copy whole row... but im afraid setting unchanged field to None > would be problematic when retrieving versions. I should retrieve > several rows and collect the latest not None fields... just pondering
That reminded me, I have another project that does automatic changelogs. There is something in the ML archives, but here's some more code. I'm afraid all class, variable and field names are in Icelandic - I put in comments with translations where it matters. I also removed alot of auxiliary tables since this is proprietary code. The base entity that keeps a changelog of itself is Verkefni (means "project"). A changelog entry is generated by calling changelog_entry on a dirty object. This is not done automatically as sometimes one wants to update an object without generating a log entry. Arnar # -*- encoding: UTF-8 -*- import datetime import cherrypy import sqlalchemy from sqlalchemy.ext.sessioncontext import SessionContext from sqlalchemy.ext.assignmapper import assign_mapper from sqlalchemy.orm import MapperExtension, EXT_PASS _engine = None metadata = sqlalchemy.DynamicMetaData() def get_engine(): global _engine dburi = cherrypy.config.get('verkstjorinn.db.uri') encoding = cherrypy.config.get('verkstjorinn.db.encoding', 'utf-8') echo = cherrypy.config.get('verkstjorinn.db.echo', False) if not _engine: _engine = sqlalchemy.create_engine(dburi, encoding=encoding, echo=echo, convert_unicode=True) metadata.connect(_engine) elif not metadata.is_bound(): metadata.connect(_engine) return _engine ctx = SessionContext(lambda:sqlalchemy.create_session(bind_to=get_engine())) def sa_uow_cleanup(): ctx.current.clear() sa_uow_cleanup.failsafe = True cherrypy.tools.sacleanup = cherrypy.Tool('on_end_request', sa_uow_cleanup) class base_model(object): def __repr__(self): props = [] for key in self.c.keys(): props.append("%s=%r" % (key, getattr(self, key))) return "<%s %s>" % (self.__class__.__name__, ' '.join(props)) from sqlalchemy import * ## Verkefni = Projects verkefni_flokkar = Table("verkefni_flokkar", metadata, Column("verkefni", Integer, ForeignKey("verkefni.verkefni"), primary_key=True), Column("flokkur", Integer, ForeignKey("flokkar.flokkur"), primary_key=True) ) # No mapper for this table, it's only a join table for the many-to-many relation verkefni = Table("verkefni", metadata, Column("verkefni", Integer, primary_key=True), Column("skrad", DateTime, nullable=False, default=func.now()), Column("sidast_breytt", DateTime, nullable=False, default=func.current_timestamp(), onupdate=func.current_timestamp()), Column("skrad_af", Unicode(20), ForeignKey("notendur.notandi"), nullable=False), Column("deadline", Date), Column("titill", Unicode, nullable=False), Column("lysing", Unicode, nullable=False), Column("mikilvaegi", Integer, ForeignKey("mikilvaegi.mikilvaegi"), nullable=False, default=40), Column("forgangur", Integer, nullable=False, default=0), Column("framvinda", Integer, nullable=False, default=0), Column("fasi", Integer, ForeignKey("fasar.fasi"), nullable=False, default=5), Column("abyrgdarmadur", Unicode(20), ForeignKey("notendur.notandi"), nullable=False), Column("cc", Unicode), Column("verknumer", Unicode(20)), Column("tengilidur", Unicode(60)), Column("bidur_eftir", Unicode) ) class Verkefni(base_model): class DEFAULTS: def __init__(self): self.verkefni = 0 self.skrad = datetime.datetime.now() self.sidast_breytt = datetime.datetime.now() self.titill = "" self.lysing = "" self.verknumer = None self.tengilidur = None self.deadline = None self.bidur_eftir = None self.flokkar = [] self.mikilvaegi = Mikilvaegi.get(40) self.forgangur = 0 self.framvinda = 0 self.fasi = Fasi.get(5) self.abyrgdarmadur = None # Must be set by controller self.cc = None self.depends_on = () self.depend_on_me = () def get_fyrirtaeki(self): return [] def get_framkvaemd(self): return [] def combined_athugasemdir(self): return [] def get_dependency_parents(self): return [] def get_dependency_children(self): return [] @property def skyldverkefni(self): related = SkyltVerkefni.select_by( or_(SkyltVerkefni.c._verkefni_a==self.verkefni,SkyltVerkefni.c._verkefni_b==self.verkefni) ) relations = [] for sv in related: if sv.verkefni_a == self: relations.append((sv.verkefni_b, sv.athugasemd)) else: relations.append((sv.verkefni_a, sv.athugasemd)) return relations def combined_athugasemdir(self): l = list(self.textar) + [a for a in self.atburdir if a.skyring != u'Bætti við athugasemd.'] l.sort(key=lambda x: x.dags) return l def get_fyrirtaeki(self): return [a.fyrirtaeki for a in self.fyrirtaeki_tengsl] def add_fyrirtaeki(self, fyrirtaeki_to_add): va = VerkefniFyrirtaekiAssociation() va.fyrirtaeki = fyrirtaeki_to_add va.verkefni = self def remove_fyrirtaeki(self, fyrirtaeki_to_remove): for va in list(self.fyrirtaeki_tengsl): if va.fyrirtaeki == fyrirtaeki_to_remove: self.fyrirtaeki_tengsl.remove(va) def get_framkvaemd(self): return [a.notandi for a in self.framkvaemd] def add_framkvaemd(self, notandi_to_add): f = Framkvaemd() f.notandi = notandi_to_add f.verkefni = self def remove_framkvaemd(self, notandi_to_remove): for f in list(self.framkvaemd): if f.notandi == notandi_to_remove: self.framkvaemd.remove(f) def get_dependency_children(self): return [d.child for d in self.depend_on_me] def add_dependency_child(self, child): if child not in self.get_dependency_children(): d = Dependency() d.parent = self d.child = child self.depend_on_me.append(d) def remove_dependency_child(self, child): dd = [d for d in self.depend_on_me if d.child == child] for d in dd: self.depend_on_me.remove(d) def get_dependency_parents(self): return [d.parent for d in self.depends_on] def add_dependency_parent(self, parent): if parent not in self.get_dependency_parents(): d = Dependency() d.parent = parent d.child = self self.depends_on.append(d) def remove_dependency_parent(self, parent): dd = [d for d in self.depends_on if d.parent == parent] for d in dd: self.depends_on.remove(d) def add_comment(self, notandi, texti): t = Texti() t.dags = datetime.datetime.now() t.notandi = notandi t.body = texti self.textar.append(t) a = Atburdur(self, notandi, u"Bætti við athugasemd.") a.create_item("comment_add", "", texti) return t,a def changelog_entry(self, notandi, skyring): """Examines updated fields of this instance and generates a changelog entry, preserving oldv values in the table atburdur_items.""" entry = Atburdur(self, notandi, skyring) def changelog(fld, old, new): entry.create_item(fld, old, new) instance = self # Scalar fields for fld in ('deadline', 'titill', 'lysing', 'forgangur', 'framvinda', 'cc', 'verknumer', 'tengilidur', 'bidur_eftir'): history = getattr(Verkefni, fld).get_history(instance, passive=True) if history.is_modified(): old, new = None, None if len(history.deleted_items()): old = history.deleted_items()[0] if len(history.added_items()): new = history.added_items()[0] # Need to check this cause SA considers 10 (int) -> 10L (long) a change, we don't if old != new: changelog(fld, old, new) for fld,key in (('fasi', 'fasi'), ('abyrgdarmadur', 'notandi'), ('mikilvaegi', 'mikilvaegi')): history = getattr(Verkefni, fld).get_history(self, passive=True) if history.is_modified(): old, new = None, None if len(history.deleted_items()): old = getattr(history.deleted_items()[0], key) if len(history.added_items()): new = getattr(history.added_items()[0], key) if old != new: changelog(fld, old, new) # Fyrirtaeki history = Verkefni.fyrirtaeki_tengsl.get_history(instance, passive=True) for i in history.added_items(): changelog('fyrirtaeki_add', None, i.fyrirtaeki.fyrirtaeki) for i in history.deleted_items(): changelog('fyrirtaeki_remove', i.fyrirtaeki.fyrirtaeki, None) # Flokkar history = Verkefni.flokkar.get_history(instance, passive=True) for i in history.added_items(): changelog('flokkur_add', None, i.flokkur) for i in history.deleted_items(): changelog('flokkur_remove', i.flokkur, None) # Framkvaemd history = Verkefni.framkvaemd.get_history(instance, passive=True) for i in history.added_items(): changelog('framkvaemd_add', None, i.notandi.notandi) for i in history.deleted_items(): changelog('framkvaemd_remove', i.notandi.notandi, None) # Dependencies history = Verkefni.depends_on.get_history(instance, passive=True) for i in history.added_items(): changelog('dep_first_add', None, i.parent.verkefni) for i in history.deleted_items(): changelog('dep_first_remove', i.parent.verkefni, None) history = Verkefni.depend_on_me.get_history(instance, passive=True) for i in history.added_items(): changelog('dep_then_add', None, i.child.verkefni) for i in history.deleted_items(): changelog('dep_then_remove', i.child.verkefni, None) # Related history = Verkefni._relatives_a.get_history(instance, passive=False) for i in history.added_items(): changelog('rel_add', None, i.verkefni) for i in history.deleted_items(): changelog('rel_remove', i.verkefni, None) history = Verkefni._relatives_b.get_history(instance, passive=False) for i in history.added_items(): changelog('rel_add', None, i.verkefni) for i in history.deleted_items(): changelog('rel_remove', i.verkefni, None) return entry assign_mapper(ctx, Verkefni, verkefni, properties={ '_mikilvaegi': verkefni.c.mikilvaegi, 'mikilvaegi': relation(Mikilvaegi, lazy=False), '_fasi': verkefni.c.fasi, 'fasi': relation(Fasi, lazy=False), '_abyrgdarmadur': verkefni.c.abyrgdarmadur, 'abyrgdarmadur': relation(Notandi, primaryjoin=verkefni.c.abyrgdarmadur==Notandi.c.notandi, lazy=False), '_skrad_af': verkefni.c.skrad_af, 'skrad_af': relation(Notandi, primaryjoin=verkefni.c.skrad_af==Notandi.c.notandi, lazy=False), 'flokkar': relation(Flokkur, secondary=verkefni_flokkar, lazy=False) }) # Atburdaskra = event log atburdaskra = Table("atburdaskra", metadata, Column("atburdur", Integer, primary_key=True), Column("verkefni", Integer, ForeignKey("verkefni.verkefni"), nullable=False), Column("notandi", Unicode(20), ForeignKey("notendur.notandi"), nullable=False), Column("dags", DateTime, nullable=False, default=func.now()), Column("skyring", Unicode) ) # Atburdur = Event class Atburdur(base_model): def __init__(self, verkefni, notandi, skyring=None): self.verkefni = verkefni self.notandi = notandi self.skyring = skyring self.dags = datetime.datetime.now() def create_item(self, svid, gamalt=None, nytt=None, texti=None): nextid = max([0] + [item.item for item in self.items])+1 return AtburdurItem(self, nextid, svid, gamalt, nytt, texti) assign_mapper(ctx, Atburdur, atburdaskra, properties={ '_verkefni': atburdaskra.c.verkefni, 'verkefni': relation(Verkefni, backref=backref("atburdir", cascade="all, delete-orphan", order_by=atburdaskra.c.dags)), '_notandi': atburdaskra.c.notandi, 'notandi': relation(Notandi) }) # This table stores the fields of a changelog entry. # atburdur = event # svid = fieldname # gamalt = old value # nytt = new value atburdaskra_items = Table("atburdaskra_items", metadata, Column("atburdur", Integer, ForeignKey("atburdaskra.atburdur"), primary_key=True), Column("item", Integer, primary_key=True), Column("svid", Unicode(100)), Column("gamalt", Unicode), Column("nytt", Unicode), Column("texti", Unicode) ) def getattr_if_not_none(obj, attr, alt=""): if obj is None: return alt else: return getattr(obj, attr) class AtburdurItem(base_model): itemFormats = { 'flokkur_add': lambda i: u'Bætt í flokk: %s' % getattr_if_not_none(Flokkur.get(int(i.nytt)), "heiti", i.nytt), 'fyrirtaeki_add': lambda i: u'Skráð á fyriræki: %s' % getattr_if_not_none(Fyrirtaeki.get(int(i.nytt)), "heiti", i.nytt), 'framkvaemd_add': lambda i: u'Nýr framkvæmdaraðili: %s' % getattr_if_not_none(Notandi.get(i.nytt), "nafn", i.nytt), 'flokkur_remove': lambda i: u'Tekið úr flokk: %s' % getattr_if_not_none(Flokkur.get(int(i.gamalt)), "heiti", i.gamalt), 'fyrirtaeki_remove': lambda i: u'Skráð af fyriræki: %s' % getattr_if_not_none(Fyrirtaeki.get(int(i.gamalt)), "heiti", i.gamalt), 'framkvaemd_remove': lambda i: u'Framkvæmdaraðili fjarlægður: %s' % getattr_if_not_none(Notandi.get(i.gamalt), "nafn", i.gamalt), 'comment_add': lambda i: u'%s' % i.nytt, # TODO wiki 'forgangur': u'Forgangur', 'deadline': u'Afgr. fyrir', 'titill': u'Titill', 'lysing': u'Lýsing', 'cc': u'CC', 'verknumer': u'Verknúmer', 'tengilidur': u'Tengiliður', 'bidur_eftir': u'Bíður eftir', 'framvinda': u'Framvinda', 'fasi': lambda i: (u'Fasi: %s -> %s' % (getattr_if_not_none(i.gamalt and Fasi.get(i.gamalt), "heiti"), getattr_if_not_none(i.nytt and Fasi.get(i.nytt), "heiti"))), 'mikilvaegi': lambda i: (u'Mikilvægi: %s -> %s' % (getattr_if_not_none(i.gamalt and Mikilvaegi.get(i.gamalt), "heiti"), getattr_if_not_none(i.nytt and Mikilvaegi.get(i.nytt), "heiti"))), 'abyrgdarmadur': lambda i: (u'Ábyrgðarmaður: %s -> %s' % (getattr_if_not_none(i.gamalt and Notandi.get(i.gamalt), "nafn"), getattr_if_not_none(i.nytt and Notandi.get(i.nytt), "nafn"))), } def __init__(self, atburdur, item, svid, gamalt=None, nytt=None, texti=None): self.atburdur = atburdur self.item = item self.svid = svid self.gamalt = gamalt self.nytt = nytt self.texti = texti def __unicode__(self): if self.svid in AtburdurItem.itemFormats: if callable(AtburdurItem.itemFormats[self.svid]): return AtburdurItem.itemFormats[self.svid](self) else: prettyName = AtburdurItem.itemFormats[self.svid] else: prettyName = self.svid def trunc(text, maxlen=70, trailer="..."): if text is not None and type(text) in (str,unicode) and len(text) > maxlen: return text[:maxlen-len(trailer)] + trailer else: return text return (u'%s: %s -> %s' % (prettyName, trunc(self.gamalt), trunc(self.nytt))) assign_mapper(ctx, AtburdurItem, atburdaskra_items, properties={ '_atburdur': atburdaskra_items.c.atburdur, 'atburdur': relation(Atburdur, backref=backref("items", cascade="all, delete-orphan", lazy=False)) }) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---