Mercurial > genshi > mirror
diff examples/trac/trac/ticket/notification.py @ 39:93b4dcbafd7b trunk
Copy Trac to main branch.
author | cmlenz |
---|---|
date | Mon, 03 Jul 2006 18:53:27 +0000 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/examples/trac/trac/ticket/notification.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2003-2006 Edgewall Software +# Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com> +# Copyright (C) 2005-2006 Emmanuel Blot <emmanuel.blot@free.fr> +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://projects.edgewall.com/trac/. +# +# Author: Daniel Lundin <daniel@edgewall.com> +# + +import md5 + +from trac import __version__ +from trac.core import * +from trac.config import * +from trac.util.text import CRLF, wrap +from trac.notification import NotifyEmail + + +class TicketNotificationSystem(Component): + + always_notify_owner = BoolOption('notification', 'always_notify_owner', + 'false', + """Always send notifications to the ticket owner (''since 0.9'').""") + + always_notify_reporter = BoolOption('notification', 'always_notify_reporter', + 'false', + """Always send notifications to any address in the ''reporter'' + field.""") + + always_notify_updater = BoolOption('notification', 'always_notify_updater', + 'true', + """Always send notifications to the person who causes the ticket + property change.""") + + +class TicketNotifyEmail(NotifyEmail): + """Notification of ticket changes.""" + + template_name = "ticket_notify_email.cs" + ticket = None + newticket = None + modtime = 0 + from_email = 'trac+ticket@localhost' + COLS = 75 + + def __init__(self, env): + NotifyEmail.__init__(self, env) + self.prev_cc = [] + + def notify(self, ticket, req, newticket=True, modtime=0): + self.ticket = ticket + self.modtime = modtime + self.newticket = newticket + self.ticket['description'] = wrap(self.ticket.values.get('description', ''), + self.COLS, initial_indent=' ', + subsequent_indent=' ', linesep=CRLF) + self.hdf.set_unescaped('email.ticket_props', self.format_props()) + self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr()) + self.hdf['ticket.new'] = self.newticket + subject = self.format_subj() + link = req.abs_href.ticket(ticket.id) + if not self.newticket: + subject = 'Re: ' + subject + self.hdf.set_unescaped('email.subject', subject) + changes = '' + if not self.newticket and modtime: # Ticket change + from trac.ticket.web_ui import TicketModule + for change in TicketModule(self.env).grouped_changelog_entries( + ticket, self.db, when=modtime): + if not change['permanent']: # attachment with same time... + continue + self.hdf.set_unescaped('ticket.change.author', + change['author']) + self.hdf.set_unescaped('ticket.change.comment', + wrap(change['comment'], self.COLS, + ' ', ' ', CRLF)) + link += '#comment:%d' % change['cnum'] + for field, values in change['fields'].iteritems(): + old = values['old'] + new = values['new'] + pfx = 'ticket.change.%s' % field + newv = '' + if field == 'description': + new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) + old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) + old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF) + cdescr = CRLF + cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF + cdescr += 'New description:' + 2*CRLF + new_descr + CRLF + self.hdf.set_unescaped('email.changes_descr', cdescr) + elif field == 'cc': + (addcc, delcc) = self.diff_cc(old, new) + chgcc = '' + if delcc: + chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), + self.COLS, ' ', ' ', CRLF) + chgcc += CRLF + if addcc: + chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), + self.COLS, ' ', ' ', CRLF) + chgcc += CRLF + if chgcc: + changes += chgcc + self.prev_cc += old and self.parse_cc(old) or [] + else: + newv = new + l = 7 + len(field) + chg = wrap('%s => %s' % (old, new), self.COLS - l, '', + l * ' ', CRLF) + changes += ' * %s: %s%s' % (field, chg, CRLF) + if newv: + self.hdf.set_unescaped('%s.oldvalue' % pfx, old) + self.hdf.set_unescaped('%s.newvalue' % pfx, newv) + if changes: + self.hdf.set_unescaped('email.changes_body', changes) + self.ticket['link'] = link + self.hdf.set_unescaped('ticket', self.ticket.values) + NotifyEmail.notify(self, ticket.id, subject) + + def format_props(self): + tkt = self.ticket + fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')] + width = [0, 0, 0, 0] + i = 0 + for f in [f['name'] for f in fields if f['type'] != 'textarea']: + if not tkt.values.has_key(f): + continue + fval = tkt[f] + if fval.find('\n') != -1: + continue + idx = 2 * (i % 2) + if len(f) > width[idx]: + width[idx] = len(f) + if len(fval) > width[idx + 1]: + width[idx + 1] = len(fval) + i += 1 + format = ('%%%is: %%-%is | ' % (width[0], width[1]), + ' %%%is: %%-%is%s' % (width[2], width[3], CRLF)) + l = (width[0] + width[1] + 5) + sep = l * '-' + '+' + (self.COLS - l) * '-' + txt = sep + CRLF + big = [] + i = 0 + for f in [f for f in fields if f['name'] != 'description']: + fname = f['name'] + if not tkt.values.has_key(fname): + continue + fval = tkt[fname] + if f['type'] == 'textarea' or '\n' in unicode(fval): + big.append((fname.capitalize(), CRLF.join(fval.splitlines()))) + else: + txt += format[i % 2] % (fname.capitalize(), fval) + i += 1 + if i % 2: + txt += CRLF + if big: + txt += sep + for name, value in big: + txt += CRLF.join(['', name + ':', value, '', '']) + txt += sep + return txt + + def parse_cc(self, txt): + return filter(lambda x: '@' in x, txt.replace(',', ' ').split()) + + def diff_cc(self, old, new): + oldcc = NotifyEmail.addrsep_re.split(old) + newcc = NotifyEmail.addrsep_re.split(new) + added = [x for x in newcc if x and x not in oldcc] + removed = [x for x in oldcc if x and x not in newcc] + return (added, removed) + + def format_hdr(self): + return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'], + self.COLS, linesep=CRLF)) + + def format_subj(self): + projname = self.config.get('project', 'name') + return '[%s] #%s: %s' % (projname, self.ticket.id, + self.ticket['summary']) + + def get_recipients(self, tktid): + notify_reporter = self.config.getbool('notification', + 'always_notify_reporter') + notify_owner = self.config.getbool('notification', + 'always_notify_owner') + notify_updater = self.config.getbool('notification', + 'always_notify_updater') + + ccrecipients = self.prev_cc + torecipients = [] + cursor = self.db.cursor() + + # Harvest email addresses from the cc, reporter, and owner fields + cursor.execute("SELECT cc,reporter,owner FROM ticket WHERE id=%s", + (tktid,)) + row = cursor.fetchone() + if row: + ccrecipients += row[0] and row[0].replace(',', ' ').split() or [] + if notify_reporter: + torecipients.append(row[1]) + if notify_owner: + torecipients.append(row[2]) + + # Harvest email addresses from the author field of ticket_change(s) + if notify_reporter: + cursor.execute("SELECT DISTINCT author,ticket FROM ticket_change " + "WHERE ticket=%s", (tktid,)) + for author,ticket in cursor: + torecipients.append(author) + + # Suppress the updater from the recipients + if not notify_updater: + cursor.execute("SELECT author FROM ticket_change WHERE ticket=%s " + "ORDER BY time DESC LIMIT 1", (tktid,)) + (updater, ) = cursor.fetchone() + torecipients = [r for r in torecipients if r and r != updater] + + return (torecipients, ccrecipients) + + def get_message_id(self, rcpt, modtime=0): + """Generate a predictable, but sufficiently unique message ID.""" + s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'), + int(self.ticket.id), modtime, rcpt) + dig = md5.new(s).hexdigest() + host = self.from_email[self.from_email.find('@') + 1:] + msgid = '<%03d.%s@%s>' % (len(s), dig, host) + return msgid + + def send(self, torcpts, ccrcpts): + hdrs = {} + always_cc = self.config['notification'].get('smtp_always_cc') + always_bcc = self.config['notification'].get('smtp_always_bcc') + dest = filter(None, torcpts) or filter(None, ccrcpts) or \ + filter(None, [always_cc]) or filter(None, [always_bcc]) + if not dest: + self.env.log.info('no recipient for a ticket notification') + return + hdrs['Message-ID'] = self.get_message_id(dest[0], self.modtime) + hdrs['X-Trac-Ticket-ID'] = str(self.ticket.id) + hdrs['X-Trac-Ticket-URL'] = self.ticket['link'] + if not self.newticket: + hdrs['In-Reply-To'] = self.get_message_id(dest[0]) + hdrs['References'] = self.get_message_id(dest[0]) + NotifyEmail.send(self, torcpts, ccrcpts, hdrs) +