Edgewall Software

Ticket #2259: attachment_0.13dev.patch

File attachment_0.13dev.patch, 15.2 KB (added by Emmanuel Blot, 14 years ago)

New patch proposal for current trunk (0.13dev)

  • trac/ticket/notification.py

    From e2d8ea996bbf6404148e25510de154e178ad7941 Mon Sep 17 00:00:00 2001
    From: Emmanuel Blot <[email protected]>
    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 b  
    1818
    1919from __future__ import with_statement
    2020
     21from datetime import datetime
    2122from hashlib import md5
    2223from unicodedata import east_asian_width
    2324
    2425from genshi.template.text import NewTextTemplate
    2526
     27from trac.attachment import IAttachmentChangeListener
    2628from trac.core import *
    2729from trac.config import *
    2830from trac.notification import NotifyEmail
    2931from trac.ticket.api import TicketSystem
    30 from trac.util.datefmt import to_utimestamp
     32from trac.ticket.model import Ticket
     33from trac.util.datefmt import to_utimestamp, utc
    3134from trac.util.text import CRLF, wrap, obfuscate_email_address, to_unicode, \
    3235                           text_width
    3336from trac.util.translation import deactivate, reactivate
    class TicketNotifyEmail(NotifyEmail):  
    9295        translated_fields = ticket.fields
    9396        try:
    9497            ticket.fields = TicketSystem(self.env).get_ticket_fields()
    95             self._notify(ticket, newticket, modtime)
     98            self.ticket = ticket
     99            self.modtime = modtime
     100            self.newticket = newticket
     101            self.reporter = ''
     102            self.owner = ''
     103            link = self.env.abs_href.ticket(ticket.id)
     104            summary = self.ticket['summary']
     105            author = None
     106
     107            if not newticket and modtime:  # Ticket change
     108                (link, summary) = \
     109                    self._build_notification_changes(ticket, modtime)
     110            else:
     111                link = self.env.abs_href.ticket(ticket.id)
     112                summary = self.ticket['summary']
     113            if newticket:
     114                author = ticket['reporter']
     115
     116            ticket_values = ticket.values.copy()
     117            ticket_values['id'] = ticket.id
     118            ticket_values['description'] = wrap(
     119                ticket_values.get('description', ''), self.COLS,
     120                initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
     121                ambiwidth=self.ambiwidth)
     122            ticket_values['new'] = self.newticket
     123            ticket_values['link'] = link
     124
     125            subject = self.format_subj(summary)
     126            if not self.newticket:
     127                subject = 'Re: ' + subject
     128            self.data.update({
     129                'ticket_props': self.format_props(),
     130                'ticket_body_hdr': self.format_hdr(),
     131                'subject': subject,
     132                'ticket': ticket_values,
     133                })
     134            NotifyEmail.notify(self, ticket.id, subject, author)
    96135        finally:
    97136            ticket.fields = translated_fields
    98137            reactivate(t)
    99138
    100     def _notify(self, ticket, newticket=True, modtime=None):
    101         self.ticket = ticket
    102         self.modtime = modtime
    103         self.newticket = newticket
     139    def notify_attachment(self, ticket, author, filename, modtime, add):
     140        """Send ticket attachment notification (untranslated)"""
     141        t = deactivate()
     142        translated_fields = ticket.fields
     143        try:
     144            ticket.fields = TicketSystem(self.env).get_ticket_fields()
     145            self.ticket = ticket
     146            self.modtime = modtime
     147            self.newticket = False
     148            self.reporter = ''
     149            self.owner = ''
     150            link = self.env.abs_href.ticket(ticket.id)
     151            summary = self.ticket['summary']
     152            ticket_values = ticket.values.copy()
     153            ticket_values['id'] = ticket.id
     154            ticket_values['description'] = wrap(
     155                ticket_values.get('description', ''), self.COLS,
     156                initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
     157                ambiwidth=self.ambiwidth)
     158            ticket_values['new'] = self.newticket
     159            ticket_values['link'] = link
     160            subject = 'Re: ' + self.format_subj(summary)
     161            body = '%s attachment "%s"' % (add and 'Add' or 'Delete', filename)
     162            change = { 'author': author }
     163            self.data.update({
     164                'ticket_props': self.format_props(),
     165                'ticket_body_hdr': self.format_hdr(),
     166                'subject': subject,
     167                'ticket': ticket_values,
     168                'change': change,
     169                'changes_body': body,
     170                })
     171            NotifyEmail.notify(self, ticket.id, subject, author)
     172        finally:
     173            ticket.fields = translated_fields
     174            reactivate(t)
    104175
    105         changes_body = ''
    106         self.reporter = ''
    107         self.owner = ''
    108         changes_descr = ''
     176    def _build_notification_changes(self, ticket, modtime):
     177        from trac.ticket.web_ui import TicketModule
    109178        change_data = {}
     179        changes_descr = ''
     180        changes_body = ''
    110181        link = self.env.abs_href.ticket(ticket.id)
    111182        summary = self.ticket['summary']
    112         author = None
    113        
    114         if not self.newticket and modtime:  # Ticket change
    115             from trac.ticket.web_ui import TicketModule
    116             for change in TicketModule(self.env).grouped_changelog_entries(
    117                                                 ticket, when=modtime):
    118                 if not change['permanent']: # attachment with same time...
    119                     continue
    120                 author = change['author']
    121                 change_data.update({
    122                     'author': obfuscate_email_address(author),
    123                     'comment': wrap(change['comment'], self.COLS, ' ', ' ',
    124                                     CRLF, self.ambiwidth)
    125                     })
    126                 link += '#comment:%s' % str(change.get('cnum', ''))
    127                 for field, values in change['fields'].iteritems():
    128                     old = values['old']
    129                     new = values['new']
    130                     newv = ''
    131                     if field == 'description':
    132                         new_descr = wrap(new, self.COLS, ' ', ' ', CRLF,
    133                                          self.ambiwidth)
    134                         old_descr = wrap(old, self.COLS, '> ', '> ', CRLF,
    135                                          self.ambiwidth)
    136                         old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
    137                                                       CRLF)
    138                         cdescr = CRLF
    139                         cdescr += 'Old description:' + 2 * CRLF + old_descr + \
    140                                   2 * CRLF
    141                         cdescr += 'New description:' + 2 * CRLF + new_descr + \
    142                                   CRLF
    143                         changes_descr = cdescr
    144                     elif field == 'summary':
    145                         summary = "%s (was: %s)" % (new, old)
    146                     elif field == 'cc':
    147                         (addcc, delcc) = self.diff_cc(old, new)
    148                         chgcc = ''
    149                         if delcc:
    150                             chgcc += wrap(" * cc: %s (removed)" %
    151                                           ', '.join(delcc),
    152                                           self.COLS, ' ', ' ', CRLF,
    153                                           self.ambiwidth) + CRLF
    154                         if addcc:
    155                             chgcc += wrap(" * cc: %s (added)" %
    156                                           ', '.join(addcc),
    157                                           self.COLS, ' ', ' ', CRLF,
    158                                           self.ambiwidth) + CRLF
    159                         if chgcc:
    160                             changes_body += chgcc
    161                         self.prev_cc += self.parse_cc(old) if old else []
    162                     else:
    163                         if field in ['owner', 'reporter']:
    164                             old = obfuscate_email_address(old)
    165                             new = obfuscate_email_address(new)
    166                         newv = new
    167                         length = 7 + len(field)
    168                         spacer_old, spacer_new = ' ', ' '
    169                         if len(old + new) + length > self.COLS:
    170                             length = 5
    171                             if len(old) + length > self.COLS:
    172                                 spacer_old = CRLF
    173                             if len(new) + length > self.COLS:
    174                                 spacer_new = CRLF
    175                         chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
    176                                                       spacer_old, spacer_new,
    177                                                       new)
    178                         chg = chg.replace(CRLF, CRLF + length * ' ')
    179                         chg = wrap(chg, self.COLS, '', length * ' ', CRLF,
    180                                    self.ambiwidth)
    181                         changes_body += ' %s%s' % (chg, CRLF)
    182                     if newv:
    183                         change_data[field] = {'oldvalue': old, 'newvalue': new}
    184        
    185         if newticket:
    186             author = ticket['reporter']
    187 
    188         ticket_values = ticket.values.copy()
    189         ticket_values['id'] = ticket.id
    190         ticket_values['description'] = wrap(
    191             ticket_values.get('description', ''), self.COLS,
    192             initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
    193             ambiwidth=self.ambiwidth)
    194         ticket_values['new'] = self.newticket
    195         ticket_values['link'] = link
    196        
    197         subject = self.format_subj(summary)
    198         if not self.newticket:
    199             subject = 'Re: ' + subject
     183        for change in TicketModule(self.env).grouped_changelog_entries(
     184                                            ticket, when=modtime):
     185            if not change['permanent']: # attachment with same time...
     186                continue
     187            author = change['author']
     188            change_data.update({
     189                'author': obfuscate_email_address(author),
     190                'comment': wrap(change['comment'], self.COLS, ' ', ' ',
     191                                CRLF, self.ambiwidth)
     192                })
     193            link += '#comment:%s' % str(change.get('cnum', ''))
     194            for field, values in change['fields'].iteritems():
     195                old = values['old']
     196                new = values['new']
     197                newv = ''
     198                if field == 'description':
     199                    new_descr = wrap(new, self.COLS, ' ', ' ', CRLF,
     200                                     self.ambiwidth)
     201                    old_descr = wrap(old, self.COLS, '> ', '> ', CRLF,
     202                                     self.ambiwidth)
     203                    old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
     204                                                  CRLF)
     205                    cdescr = CRLF
     206                    cdescr += 'Old description:' + 2 * CRLF + old_descr + \
     207                              2 * CRLF
     208                    cdescr += 'New description:' + 2 * CRLF + new_descr + \
     209                              CRLF
     210                    changes_descr = cdescr
     211                elif field == 'summary':
     212                    summary = "%s (was: %s)" % (new, old)
     213                elif field == 'cc':
     214                    (addcc, delcc) = self.diff_cc(old, new)
     215                    chgcc = ''
     216                    if delcc:
     217                        chgcc += wrap(" * cc: %s (removed)" %
     218                                      ', '.join(delcc),
     219                                      self.COLS, ' ', ' ', CRLF,
     220                                      self.ambiwidth) + CRLF
     221                    if addcc:
     222                        chgcc += wrap(" * cc: %s (added)" %
     223                                      ', '.join(addcc),
     224                                      self.COLS, ' ', ' ', CRLF,
     225                                      self.ambiwidth) + CRLF
     226                    if chgcc:
     227                        changes_body += chgcc
     228                    self.prev_cc += self.parse_cc(old) if old else []
     229                else:
     230                    if field in ['owner', 'reporter']:
     231                        old = obfuscate_email_address(old)
     232                        new = obfuscate_email_address(new)
     233                    newv = new
     234                    length = 7 + len(field)
     235                    spacer_old, spacer_new = ' ', ' '
     236                    if len(old + new) + length > self.COLS:
     237                        length = 5
     238                        if len(old) + length > self.COLS:
     239                            spacer_old = CRLF
     240                        if len(new) + length > self.COLS:
     241                            spacer_new = CRLF
     242                    chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
     243                                                  spacer_old, spacer_new,
     244                                                  new)
     245                    chg = chg.replace(CRLF, CRLF + length * ' ')
     246                    chg = wrap(chg, self.COLS, '', length * ' ', CRLF,
     247                               self.ambiwidth)
     248                    changes_body += ' %s%s' % (chg, CRLF)
     249                if newv:
     250                    change_data[field] = {'oldvalue': old, 'newvalue': new}
    200251        self.data.update({
    201             'ticket_props': self.format_props(),
    202             'ticket_body_hdr': self.format_hdr(),
    203             'subject': subject,
    204             'ticket': ticket_values,
    205252            'changes_body': changes_body,
    206253            'changes_descr': changes_descr,
    207254            'change': change_data
    208255            })
    209         NotifyEmail.notify(self, ticket.id, subject, author)
     256        return (link, summary)
    210257
    211258    def format_props(self):
    212259        tkt = self.ticket
    class TicketNotifyEmail(NotifyEmail):  
    404451    def get_text_width(self, text):
    405452        return text_width(text, ambiwidth=self.ambiwidth)
    406453
     454
     455class TicketAttachmentNotifier(Component):
     456    """Sends notification on attachment change"""
     457
     458    implements(IAttachmentChangeListener)
     459
     460    ticket_notify_attachment = BoolOption('notification',
     461                                          'ticket_notify_attachment',
     462                                          'true',
     463        """Always send notifications on ticket attachment events.""")
     464
     465    # IAttachmentChangeListener
     466
     467    def attachment_added(self, attachment):
     468        if self.ticket_notify_attachment:
     469            self._notify_attachment(attachment, True)
     470
     471    def attachment_deleted(self, attachment):
     472        if self.ticket_notify_attachment:
     473            self._notify_attachment(attachment, False)
     474
     475    def attachment_reparented(self, attachment, old_parent_realm,
     476                              old_parent_id):
     477        pass
     478
     479    # Implementation
     480
     481    def _notify_attachment(self, attachment, add):
     482        author = attachment.author
     483        resource = attachment.resource.parent
     484        if resource.realm != 'ticket':
     485            return
     486        ticket = Ticket(self.env, resource.id)
     487        tn = TicketNotifyEmail(self.env)
     488        filename = attachment.filename
     489        date = attachment.date or datetime.now(utc)
     490        tn.notify_attachment(ticket, author, filename, date, add)