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 [email protected]
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
-~----------~----~----~----~------~----~------~--~---