Edgewall Software

Ticket #2259: attachment_0.13dev.patch

File attachment_0.13dev.patch, 15.2 KB (added by eblot, 13 months 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 <eblot@neotion.com>
    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)