From e2d8ea996bbf6404148e25510de154e178ad7941 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Fri, 13 May 2011 19:38:32 +0200 Subject: [PATCH] Add attachment notification --- trac/ticket/notification.py | 290 ++++++++++++++++++++++++++++--------------- 1 files changed, 187 insertions(+), 103 deletions(-) diff --git a/trac/ticket/notification.py b/trac/ticket/notification.py index ed25467..931b234 100644 --- a/trac/ticket/notification.py +++ b/trac/ticket/notification.py @@ -18,16 +18,19 @@ from __future__ import with_statement +from datetime import datetime from hashlib import md5 from unicodedata import east_asian_width from genshi.template.text import NewTextTemplate +from trac.attachment import IAttachmentChangeListener from trac.core import * from trac.config import * from trac.notification import NotifyEmail from trac.ticket.api import TicketSystem -from trac.util.datefmt import to_utimestamp +from trac.ticket.model import Ticket +from trac.util.datefmt import to_utimestamp, utc from trac.util.text import CRLF, wrap, obfuscate_email_address, to_unicode, \ text_width from trac.util.translation import deactivate, reactivate @@ -92,121 +95,165 @@ class TicketNotifyEmail(NotifyEmail): translated_fields = ticket.fields try: ticket.fields = TicketSystem(self.env).get_ticket_fields() - self._notify(ticket, newticket, modtime) + self.ticket = ticket + self.modtime = modtime + self.newticket = newticket + self.reporter = '' + self.owner = '' + link = self.env.abs_href.ticket(ticket.id) + summary = self.ticket['summary'] + author = None + + if not newticket and modtime: # Ticket change + (link, summary) = \ + self._build_notification_changes(ticket, modtime) + else: + link = self.env.abs_href.ticket(ticket.id) + summary = self.ticket['summary'] + if newticket: + author = ticket['reporter'] + + ticket_values = ticket.values.copy() + ticket_values['id'] = ticket.id + ticket_values['description'] = wrap( + ticket_values.get('description', ''), self.COLS, + initial_indent=' ', subsequent_indent=' ', linesep=CRLF, + ambiwidth=self.ambiwidth) + ticket_values['new'] = self.newticket + ticket_values['link'] = link + + subject = self.format_subj(summary) + if not self.newticket: + subject = 'Re: ' + subject + self.data.update({ + 'ticket_props': self.format_props(), + 'ticket_body_hdr': self.format_hdr(), + 'subject': subject, + 'ticket': ticket_values, + }) + NotifyEmail.notify(self, ticket.id, subject, author) finally: ticket.fields = translated_fields reactivate(t) - def _notify(self, ticket, newticket=True, modtime=None): - self.ticket = ticket - self.modtime = modtime - self.newticket = newticket + def notify_attachment(self, ticket, author, filename, modtime, add): + """Send ticket attachment notification (untranslated)""" + t = deactivate() + translated_fields = ticket.fields + try: + ticket.fields = TicketSystem(self.env).get_ticket_fields() + self.ticket = ticket + self.modtime = modtime + self.newticket = False + self.reporter = '' + self.owner = '' + link = self.env.abs_href.ticket(ticket.id) + summary = self.ticket['summary'] + ticket_values = ticket.values.copy() + ticket_values['id'] = ticket.id + ticket_values['description'] = wrap( + ticket_values.get('description', ''), self.COLS, + initial_indent=' ', subsequent_indent=' ', linesep=CRLF, + ambiwidth=self.ambiwidth) + ticket_values['new'] = self.newticket + ticket_values['link'] = link + subject = 'Re: ' + self.format_subj(summary) + body = '%s attachment "%s"' % (add and 'Add' or 'Delete', filename) + change = { 'author': author } + self.data.update({ + 'ticket_props': self.format_props(), + 'ticket_body_hdr': self.format_hdr(), + 'subject': subject, + 'ticket': ticket_values, + 'change': change, + 'changes_body': body, + }) + NotifyEmail.notify(self, ticket.id, subject, author) + finally: + ticket.fields = translated_fields + reactivate(t) - changes_body = '' - self.reporter = '' - self.owner = '' - changes_descr = '' + def _build_notification_changes(self, ticket, modtime): + from trac.ticket.web_ui import TicketModule change_data = {} + changes_descr = '' + changes_body = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] - author = None - - 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, when=modtime): - if not change['permanent']: # attachment with same time... - continue - author = change['author'] - change_data.update({ - 'author': obfuscate_email_address(author), - 'comment': wrap(change['comment'], self.COLS, ' ', ' ', - CRLF, self.ambiwidth) - }) - link += '#comment:%s' % str(change.get('cnum', '')) - for field, values in change['fields'].iteritems(): - old = values['old'] - new = values['new'] - newv = '' - if field == 'description': - new_descr = wrap(new, self.COLS, ' ', ' ', CRLF, - self.ambiwidth) - old_descr = wrap(old, self.COLS, '> ', '> ', CRLF, - self.ambiwidth) - 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 - changes_descr = cdescr - elif field == 'summary': - summary = "%s (was: %s)" % (new, old) - elif field == 'cc': - (addcc, delcc) = self.diff_cc(old, new) - chgcc = '' - if delcc: - chgcc += wrap(" * cc: %s (removed)" % - ', '.join(delcc), - self.COLS, ' ', ' ', CRLF, - self.ambiwidth) + CRLF - if addcc: - chgcc += wrap(" * cc: %s (added)" % - ', '.join(addcc), - self.COLS, ' ', ' ', CRLF, - self.ambiwidth) + CRLF - if chgcc: - changes_body += chgcc - self.prev_cc += self.parse_cc(old) if old else [] - else: - if field in ['owner', 'reporter']: - old = obfuscate_email_address(old) - new = obfuscate_email_address(new) - newv = new - length = 7 + len(field) - spacer_old, spacer_new = ' ', ' ' - if len(old + new) + length > self.COLS: - length = 5 - if len(old) + length > self.COLS: - spacer_old = CRLF - if len(new) + length > self.COLS: - spacer_new = CRLF - chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, - spacer_old, spacer_new, - new) - chg = chg.replace(CRLF, CRLF + length * ' ') - chg = wrap(chg, self.COLS, '', length * ' ', CRLF, - self.ambiwidth) - changes_body += ' %s%s' % (chg, CRLF) - if newv: - change_data[field] = {'oldvalue': old, 'newvalue': new} - - if newticket: - author = ticket['reporter'] - - ticket_values = ticket.values.copy() - ticket_values['id'] = ticket.id - ticket_values['description'] = wrap( - ticket_values.get('description', ''), self.COLS, - initial_indent=' ', subsequent_indent=' ', linesep=CRLF, - ambiwidth=self.ambiwidth) - ticket_values['new'] = self.newticket - ticket_values['link'] = link - - subject = self.format_subj(summary) - if not self.newticket: - subject = 'Re: ' + subject + for change in TicketModule(self.env).grouped_changelog_entries( + ticket, when=modtime): + if not change['permanent']: # attachment with same time... + continue + author = change['author'] + change_data.update({ + 'author': obfuscate_email_address(author), + 'comment': wrap(change['comment'], self.COLS, ' ', ' ', + CRLF, self.ambiwidth) + }) + link += '#comment:%s' % str(change.get('cnum', '')) + for field, values in change['fields'].iteritems(): + old = values['old'] + new = values['new'] + newv = '' + if field == 'description': + new_descr = wrap(new, self.COLS, ' ', ' ', CRLF, + self.ambiwidth) + old_descr = wrap(old, self.COLS, '> ', '> ', CRLF, + self.ambiwidth) + 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 + changes_descr = cdescr + elif field == 'summary': + summary = "%s (was: %s)" % (new, old) + elif field == 'cc': + (addcc, delcc) = self.diff_cc(old, new) + chgcc = '' + if delcc: + chgcc += wrap(" * cc: %s (removed)" % + ', '.join(delcc), + self.COLS, ' ', ' ', CRLF, + self.ambiwidth) + CRLF + if addcc: + chgcc += wrap(" * cc: %s (added)" % + ', '.join(addcc), + self.COLS, ' ', ' ', CRLF, + self.ambiwidth) + CRLF + if chgcc: + changes_body += chgcc + self.prev_cc += self.parse_cc(old) if old else [] + else: + if field in ['owner', 'reporter']: + old = obfuscate_email_address(old) + new = obfuscate_email_address(new) + newv = new + length = 7 + len(field) + spacer_old, spacer_new = ' ', ' ' + if len(old + new) + length > self.COLS: + length = 5 + if len(old) + length > self.COLS: + spacer_old = CRLF + if len(new) + length > self.COLS: + spacer_new = CRLF + chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, + spacer_old, spacer_new, + new) + chg = chg.replace(CRLF, CRLF + length * ' ') + chg = wrap(chg, self.COLS, '', length * ' ', CRLF, + self.ambiwidth) + changes_body += ' %s%s' % (chg, CRLF) + if newv: + change_data[field] = {'oldvalue': old, 'newvalue': new} self.data.update({ - 'ticket_props': self.format_props(), - 'ticket_body_hdr': self.format_hdr(), - 'subject': subject, - 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) - NotifyEmail.notify(self, ticket.id, subject, author) + return (link, summary) def format_props(self): tkt = self.ticket @@ -404,3 +451,40 @@ class TicketNotifyEmail(NotifyEmail): def get_text_width(self, text): return text_width(text, ambiwidth=self.ambiwidth) + +class TicketAttachmentNotifier(Component): + """Sends notification on attachment change""" + + implements(IAttachmentChangeListener) + + ticket_notify_attachment = BoolOption('notification', + 'ticket_notify_attachment', + 'true', + """Always send notifications on ticket attachment events.""") + + # IAttachmentChangeListener + + def attachment_added(self, attachment): + if self.ticket_notify_attachment: + self._notify_attachment(attachment, True) + + def attachment_deleted(self, attachment): + if self.ticket_notify_attachment: + self._notify_attachment(attachment, False) + + def attachment_reparented(self, attachment, old_parent_realm, + old_parent_id): + pass + + # Implementation + + def _notify_attachment(self, attachment, add): + author = attachment.author + resource = attachment.resource.parent + if resource.realm != 'ticket': + return + ticket = Ticket(self.env, resource.id) + tn = TicketNotifyEmail(self.env) + filename = attachment.filename + date = attachment.date or datetime.now(utc) + tn.notify_attachment(ticket, author, filename, date, add) -- 1.7.4.1+GitX