Mercurial > p > roundup > code
diff roundup/roundupdb.py @ 25:4cf1daf2f2eb
More Grande Splite
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sun, 22 Jul 2001 12:01:27 +0000 |
| parents | |
| children | c7c14960f413 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roundup/roundupdb.py Sun Jul 22 12:01:27 2001 +0000 @@ -0,0 +1,249 @@ +# $Id: roundupdb.py,v 1.1 2001-07-22 11:58:35 richard Exp $ + +import re, os, smtplib, socket + +import hyperdb, date + +def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')): + ''' Take a foo123 and return ('foo', 123) + ''' + m = dre.match(designator) + return m.group(1), m.group(2) + +class Database: + def getuid(self): + """Return the id of the "user" node associated with the user + that owns this connection to the hyperdatabase.""" + return self.user.lookup(self.journaltag) + + def uidFromAddress(self, address): + ''' address is from the rfc822 module, and therefore is (name, addr) + + user is created if they don't exist in the db already + ''' + (realname, address) = address + users = self.user.stringFind(address=address) + if users: return users[0] + return self.user.create(username=address, address=address, + realname=realname) + +class Class(hyperdb.Class): + # Overridden methods: + def __init__(self, db, classname, **properties): + hyperdb.Class.__init__(self, db, classname, **properties) + self.auditors = {'create': [], 'set': [], 'retire': []} + self.reactors = {'create': [], 'set': [], 'retire': []} + + def create(self, **propvalues): + """These operations trigger detectors and can be vetoed. Attempts + to modify the "creation" or "activity" properties cause a KeyError. + """ + if propvalues.has_key('creation') or propvalues.has_key('activity'): + raise KeyError, '"creation" and "activity" are reserved' + for audit in self.auditors['create']: + audit(self.db, self, None, propvalues) + nodeid = hyperdb.Class.create(self, **propvalues) + for react in self.reactors['create']: + react(self.db, self, nodeid, None) + return nodeid + + def set(self, nodeid, **propvalues): + """These operations trigger detectors and can be vetoed. Attempts + to modify the "creation" or "activity" properties cause a KeyError. + """ + if propvalues.has_key('creation') or propvalues.has_key('activity'): + raise KeyError, '"creation" and "activity" are reserved' + for audit in self.auditors['set']: + audit(self.db, self, nodeid, propvalues) + oldvalues = self.db.getnode(self.classname, nodeid) + hyperdb.Class.set(self, nodeid, **propvalues) + for react in self.reactors['set']: + react(self.db, self, nodeid, oldvalues) + + def retire(self, nodeid): + """These operations trigger detectors and can be vetoed. Attempts + to modify the "creation" or "activity" properties cause a KeyError. + """ + for audit in self.auditors['retire']: + audit(self.db, self, nodeid, None) + hyperdb.Class.retire(self, nodeid) + for react in self.reactors['retire']: + react(self.db, self, nodeid, None) + + # New methods: + + def audit(self, event, detector): + """Register a detector + """ + self.auditors[event].append(detector) + + def react(self, event, detector): + """Register a detector + """ + self.reactors[event].append(detector) + +class FileClass(Class): + def create(self, **propvalues): + ''' snaffle the file propvalue and store in a file + ''' + content = propvalues['content'] + del propvalues['content'] + newid = Class.create(self, **propvalues) + self.setcontent(self.classname, newid, content) + return newid + + def filename(self, classname, nodeid): + # TODO: split into multiple files directories + return os.path.join(self.db.dir, 'files', '%s%s'%(classname, nodeid)) + + def setcontent(self, classname, nodeid, content): + ''' set the content file for this file + ''' + open(self.filename(classname, nodeid), 'wb').write(content) + + def getcontent(self, classname, nodeid): + ''' get the content file for this file + ''' + return open(self.filename(classname, nodeid), 'rb').read() + + def get(self, nodeid, propname): + ''' trap the content propname and get it from the file + ''' + if propname == 'content': + return self.getcontent(self.classname, nodeid) + return Class.get(self, nodeid, propname) + + def getprops(self): + ''' In addition to the actual properties on the node, these methods + provide the "content" property. + ''' + d = Class.getprops(self).copy() + d['content'] = hyperdb.String() + return d + +# XXX deviation from spec - was called ItemClass +class IssueClass(Class): + # Overridden methods: + + def __init__(self, db, classname, **properties): + """The newly-created class automatically includes the "messages", + "files", "nosy", and "superseder" properties. If the 'properties' + dictionary attempts to specify any of these properties or a + "creation" or "activity" property, a ValueError is raised.""" + if not properties.has_key('title'): + properties['title'] = hyperdb.String() + if not properties.has_key('messages'): + properties['messages'] = hyperdb.Multilink("msg") + if not properties.has_key('files'): + properties['files'] = hyperdb.Multilink("file") + if not properties.has_key('nosy'): + properties['nosy'] = hyperdb.Multilink("user") + if not properties.has_key('superseder'): + properties['superseder'] = hyperdb.Multilink("issue") + if (properties.has_key('creation') or properties.has_key('activity') + or properties.has_key('creator')): + raise ValueError, '"creation", "activity" and "creator" are reserved' + Class.__init__(self, db, classname, **properties) + + def get(self, nodeid, propname): + if propname == 'creation': + return self.db.getjournal(self.classname, nodeid)[0][1] + if propname == 'activity': + return self.db.getjournal(self.classname, nodeid)[-1][1] + if propname == 'creator': + name = self.db.getjournal(self.classname, nodeid)[0][2] + return self.db.user.lookup(name) + return Class.get(self, nodeid, propname) + + def getprops(self): + """In addition to the actual properties on the node, these + methods provide the "creation" and "activity" properties.""" + d = Class.getprops(self).copy() + d['creation'] = hyperdb.Date() + d['activity'] = hyperdb.Date() + d['creator'] = hyperdb.Link("user") + return d + + # New methods: + + def addmessage(self, nodeid, summary, text): + """Add a message to an issue's mail spool. + + A new "msg" node is constructed using the current date, the user that + owns the database connection as the author, and the specified summary + text. + + The "files" and "recipients" fields are left empty. + + The given text is saved as the body of the message and the node is + appended to the "messages" field of the specified issue. + """ + + def sendmessage(self, nodeid, msgid): + """Send a message to the members of an issue's nosy list. + + The message is sent only to users on the nosy list who are not + already on the "recipients" list for the message. + + These users are then added to the message's "recipients" list. + """ + # figure the recipient ids + recipients = self.db.msg.get(msgid, 'recipients') + r = {} + for recipid in recipients: + r[recipid] = 1 + authid = self.db.msg.get(msgid, 'author') + r[authid] = 1 + + # now figure the nosy people who weren't recipients + sendto = [] + nosy = self.get(nodeid, 'nosy') + for nosyid in nosy: + if not r.has_key(nosyid): + sendto.append(nosyid) + recipients.append(nosyid) + + if sendto: + # update the message's recipients list + self.db.msg.set(msgid, recipients=recipients) + + # send an email to the people who missed out + sendto = [self.db.user.get(i, 'address') for i in recipients] + cn = self.classname + title = self.get(nodeid, 'title') or '%s message copy'%cn + m = ['Subject: [%s%s] %s'%(cn, nodeid, title)] + m.append('To: %s'%', '.join(sendto)) + m.append('Reply-To: %s'%self.ISSUE_TRACKER_EMAIL) + m.append('') + m.append(self.db.msg.get(msgid, 'content')) + # TODO attachments + try: + smtp = smtplib.SMTP(self.MAILHOST) + smtp.sendmail(self.ISSUE_TRACKER_EMAIL, sendto, '\n'.join(m)) + except socket.error, value: + return "Couldn't send confirmation email: mailhost %s"%value + except smtplib.SMTPException, value: + return "Couldn't send confirmation email: %s"%value + +# +# $Log: not supported by cvs2svn $ +# Revision 1.6 2001/07/20 07:35:55 richard +# largish changes as a start of splitting off bits and pieces to allow more +# flexible installation / database back-ends +# +# Revision 1.5 2001/07/20 00:22:50 richard +# Priority list changes - removed the redundant TODO and added support. See +# roundup-devel for details. +# +# Revision 1.4 2001/07/19 06:27:07 anthonybaxter +# fixing (manually) the (dollarsign)Log(dollarsign) entries caused by +# my using the magic (dollarsign)Id(dollarsign) and (dollarsign)Log(dollarsign) +# strings in a commit message. I'm a twonk. +# +# Also broke the help string in two. +# +# Revision 1.3 2001/07/19 05:52:22 anthonybaxter +# Added CVS keywords Id and Log to all python files. +# +# +
