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