The following will download the messages spool for an issue as an mbox file. It's intended to be dropped into the extensions directory of a 0.8 or later tracker. It'll work with 0.7, but the registration interface has changed (the init bit at the end is 0.8-or-later).
The code:
from roundup.cgi.actions import Action class MboxAction(Action): def handle(self): if self.classname != 'issue': raise ValueError, 'Can only download messages mbox for issues' if self.nodeid is None: raise ValueError, 'Can only download messages mbox for single issues' r = [] a = r.append msg = self.db.msg user = self.db.user for msgid in self.db.issue.get(self.nodeid, 'messages'): author = msg.get(msgid, 'author') date = msg.get(msgid, 'date') sdate = date.pretty('%a, %d %b %Y %H:%M:%S +0000') a('From %s %s'%(user.get(author, 'address'), sdate)) a('From: %s'%user.get(author, 'address')) a('Message-Id: %s'%msg.get(msgid, 'messageid')) inreplyto = msg.get(msgid, 'inreplyto') if inreplyto: a('In-Reply-To: %s'%inreplyto) body = msg.get(msgid, 'content').splitlines() for line in range(len(body)): if body[line].startswith('From '): body[line] = '>'+body['line'] a(body[line]) a('\n') h = self.client.additional_headers h['Content-Type'] = 'application/mbox' self.client.header() if self.client.env['REQUEST_METHOD'] == 'HEAD': # all done, return a dummy string return 'dummy' return '\n'.join(r) def init(tracker): tracker.registerAction('mbox', MboxAction)
-- RichardJones
From wiki Sat Jul 29 11:12:31 +1000 2006 From: wiki Date: Sat, 29 Jul 2006 11:12:31 +1000 Subject: The usage bit Message-ID: <20060729111231+1000@www.mechanicalcat.net>
To use it, I created a link:
<a href="#" tal:attributes="href string:issue${context/id}?@action=mbox" i18n:translate="">Download</a>
(should work fine in the issue.item.html template)
From twb Fri Jan 23 14:37:20 +1100 2009 From: Trent W. Buck <trentbuck@gmail.com> Date: Tue, 20 Jan 2009 12:04:44 +1100 Subject: New version Message-ID: <301vuyzt1v.fsf@Clio.twb.ath.cx>
I have worked on improving this extension, the code of which can be seen at
and the results of which can be seen at e.g.
I hope to improve mbox support further in future, but I don't intend to work on it in the near future. In particular:
- including a pseudo-message that shows the final properties;
- including the property changes made by each email; and
- including attached files as MIME attachments.
I think the latter two are hard because AFAICT roundup "forgets" which messages are associated with which property changes and attachments.
Rouilj found the code referenced by Trent at: http://darcs.net/darcs-bugtracker in the extensions subdirectory. It is deployed to a 1.x roundup instance, but I am not sure which version. I am including the code below and have added an attachment at (aka [[attachment:darcs-messages_as_mbox.py]]).
The code is:
from email.message import Message from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.encoders import encode_base64 class MboxAction(Action): ## These get set in handle() by a dirty hack (see "setattr"). activity = None # issue / patch actor = None # issue / patch assignedto = None # issue / patch (used) creation = None # issue / patch creator = None # issue / patch darcswatchurl = None # patch messages = None # issue / patch (used) nosy = None # issue / patch (used) priority = None # issue (used) status = None # issue / patch (used) title = None # issue / patch (used) topic = None # issue (used) def handle(self): if self.classname != "issue" and self.classname != "patch": raise ValueError, "Only an issue or patch can become an mbox." if self.nodeid is None: raise ValueError, "Cannot iterate over multiple issues at once." ## This saves typing later. It's naughty, because AFAICT ## self.db.issue.get() does some extra magic that isn't ## reproduced here. for (key, val) in self.db.getnode (self.classname, self.nodeid).items (): setattr (self, key, val) if not self.messages: raise ValueError, "Can't make an mbox without messages." self.client.additional_headers ["Content-Type"] = "application/mbox" self.client.header () if self.client.env["REQUEST_METHOD"] == "HEAD": return None # Stop now, as the mbox wasn't requested. mbox = [] # The accumulator. for message_id in self.messages: message = MIMEText (self.db.msg.get (message_id, "content"), "plain", "utf-8") # hard-code as UTF-8 for now. files = self.db.msg.get (message_id, "files") if files: message_body = message message_body ["Content-Disposition"] = "inline" message = MIMEMultipart () message.attach (message_body) for it in files: attachment = Message () attachment.add_header ("Content-Disposition", "attachment", filename=self.db.file.get (it, "name")) attachment ["Content-Type"] = self.db.file.get (it, "type") attachment.set_payload (self.db.file.get (it, "content")) encode_base64 (attachment) # bloated, but reliable. message.attach (attachment) message ["Date"] = self.db.msg.get (message_id, "date").pretty ("%a, %d %b %Y %H:%M:%S +0000") message ["From"] = self.address (self.db.msg.get (message_id, "author")) it = self.db.msg.get (message_id, "recipients") if it: message ["CC"] = ", ".join ([self.address (recipient) for recipient in it]) message ["Message-ID"] = self.db.msg.get(message_id, "messageid") or None message ["In-Reply-To"] = self.db.msg.get(message_id, "inreplyto") or None ## The remaining header fields are identical for all ## messages in the mbox, at least until such time as I ## work out how to reconcile the "journal" with the list ## of messages. message ["Reply-To"] = self.db.config.TRACKER_EMAIL message ["Subject"] = "[" + self.classname + self.nodeid + "] " + self.title if self.priority: message ["X-Roundup-Priority"] = self.db.priority.get (self.priority, "name") if self.status: message ["X-Roundup-Status"] = self.db.status.get (self.status, "name") if self.assignedto: message ["X-Roundup-Assigned-To"] = self.db.user.get (self.assignedto, "username") if self.nosy: message ["X-Roundup-Nosy-List"] = ", ".join([self.db.user.get (nose, "username") for nose in self.nosy]) if self.topic: message ["X-Roundup-Topics"] = ", ".join([self.db.keyword.get (topic, "name") for topic in self.topic]) mbox.append (message) return "\n".join ([str (message) for message in mbox]) def address(self, x): get = self.db.user.get return (get(x, "realname") or get(x, "username")) + ":;" def init(tracker): tracker.registerAction("mbox", MboxAction)
Use the attachment at [[attachment:darcs-messages_as_mbox.py]] if you are going to deploy this. Cut/paste from here will screw up the whitespace so critical for proper syntax in python. ---- CategoryActions