Edgewall Software

Ticket #2259: attachment_0.13dev.2.patch

File attachment_0.13dev.2.patch, 15.0 KB (added by eblot, 12 months ago)

Fix up some issues with the previous patch, all unit/functional tests now pass Ok.

  • trac/ticket/notification.py

    diff --git a/trac/ticket/notification.py b/trac/ticket/notification.py
    index ed25467..38e575b 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 
     31from trac.resource import ResourceNotFound 
    2932from trac.ticket.api import TicketSystem 
    30 from trac.util.datefmt import to_utimestamp 
     33from trac.ticket.model import Ticket 
     34from trac.util.datefmt import to_utimestamp, utc 
    3135from trac.util.text import CRLF, wrap, obfuscate_email_address, to_unicode, \ 
    3236                           text_width 
    3337from trac.util.translation import deactivate, reactivate 
    class TicketNotifyEmail(NotifyEmail): 
    9296        translated_fields = ticket.fields 
    9397        try: 
    9498            ticket.fields = TicketSystem(self.env).get_ticket_fields() 
    95             self._notify(ticket, newticket, modtime) 
     99            self.ticket = ticket 
     100            self.modtime = modtime 
     101            self.newticket = newticket 
     102            self.reporter = '' 
     103            self.owner = '' 
     104            link = self.env.abs_href.ticket(ticket.id) 
     105            summary = self.ticket['summary'] 
     106            author = None 
     107 
     108            if not newticket and modtime:  # Ticket change 
     109                (link, summary, author) = \ 
     110                    self._build_notification_changes(ticket, modtime) 
     111            else: 
     112                link = self.env.abs_href.ticket(ticket.id) 
     113                summary = self.ticket['summary'] 
     114            if newticket: 
     115                author = ticket['reporter'] 
     116 
     117            ticket_values = ticket.values.copy() 
     118            ticket_values['id'] = ticket.id 
     119            ticket_values['description'] = wrap( 
     120                ticket_values.get('description', ''), self.COLS, 
     121                initial_indent=' ', subsequent_indent=' ', linesep=CRLF, 
     122                ambiwidth=self.ambiwidth) 
     123            ticket_values['new'] = self.newticket 
     124            ticket_values['link'] = link 
     125 
     126            subject = self.format_subj(summary) 
     127            if not self.newticket: 
     128                subject = 'Re: ' + subject 
     129            self.data.update({ 
     130                'ticket_props': self.format_props(), 
     131                'ticket_body_hdr': self.format_hdr(), 
     132                'subject': subject, 
     133                'ticket': ticket_values, 
     134                }) 
     135            NotifyEmail.notify(self, ticket.id, subject, author) 
    96136        finally: 
    97137            ticket.fields = translated_fields 
    98138            reactivate(t) 
    99139 
    100     def _notify(self, ticket, newticket=True, modtime=None): 
    101         self.ticket = ticket 
    102         self.modtime = modtime 
    103         self.newticket = newticket 
     140    def notify_attachment(self, ticket, author, filename, modtime, add): 
     141        """Send ticket attachment notification (untranslated)""" 
     142        t = deactivate() 
     143        translated_fields = ticket.fields 
     144        try: 
     145            ticket.fields = TicketSystem(self.env).get_ticket_fields() 
     146            self.ticket = ticket 
     147            self.modtime = modtime 
     148            self.newticket = False 
     149            self.reporter = '' 
     150            self.owner = '' 
     151            link = self.env.abs_href.ticket(ticket.id) 
     152            summary = self.ticket['summary'] 
     153            ticket_values = ticket.values.copy() 
     154            ticket_values['id'] = ticket.id 
     155            ticket_values['description'] = wrap( 
     156                ticket_values.get('description', ''), self.COLS, 
     157                initial_indent=' ', subsequent_indent=' ', linesep=CRLF, 
     158                ambiwidth=self.ambiwidth) 
     159            ticket_values['new'] = self.newticket 
     160            ticket_values['link'] = link 
     161            subject = 'Re: ' + self.format_subj(summary) 
     162            body = '%s attachment "%s"' % (add and 'Add' or 'Delete', filename) 
     163            author = obfuscate_email_address(author) 
     164            change = { 'author': author } 
     165            self.data.update({ 
     166                'ticket_props': self.format_props(), 
     167                'ticket_body_hdr': self.format_hdr(), 
     168                'subject': subject, 
     169                'ticket': ticket_values, 
     170                'change': change, 
     171                'changes_body': body, 
     172                }) 
     173            NotifyEmail.notify(self, ticket.id, subject, author) 
     174        finally: 
     175            ticket.fields = translated_fields 
     176            reactivate(t) 
    104177 
    105         changes_body = '' 
    106         self.reporter = '' 
    107         self.owner = '' 
    108         changes_descr = '' 
     178    def _build_notification_changes(self, ticket, modtime): 
     179        from trac.ticket.web_ui import TicketModule 
    109180        change_data = {} 
     181        changes_descr = '' 
     182        changes_body = '' 
    110183        link = self.env.abs_href.ticket(ticket.id) 
    111184        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 
     185        for change in TicketModule(self.env).grouped_changelog_entries( 
     186                                            ticket, when=modtime): 
     187            if not change['permanent']: # attachment with same time... 
     188                continue 
     189            author = change['author'] 
     190            change_data.update({ 
     191                'author': obfuscate_email_address(author), 
     192                'comment': wrap(change['comment'], self.COLS, ' ', ' ', 
     193                                CRLF, self.ambiwidth) 
     194                }) 
     195            link += '#comment:%s' % str(change.get('cnum', '')) 
     196            for field, values in change['fields'].iteritems(): 
     197                old = values['old'] 
     198                new = values['new'] 
     199                newv = '' 
     200                if field == 'description': 
     201                    new_descr = wrap(new, self.COLS, ' ', ' ', CRLF, 
     202                                     self.ambiwidth) 
     203                    old_descr = wrap(old, self.COLS, '> ', '> ', CRLF, 
     204                                     self.ambiwidth) 
     205                    old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \ 
     206                                                  CRLF) 
     207                    cdescr = CRLF 
     208                    cdescr += 'Old description:' + 2 * CRLF + old_descr + \ 
     209                              2 * CRLF 
     210                    cdescr += 'New description:' + 2 * CRLF + new_descr + \ 
     211                              CRLF 
     212                    changes_descr = cdescr 
     213                elif field == 'summary': 
     214                    summary = "%s (was: %s)" % (new, old) 
     215                elif field == 'cc': 
     216                    (addcc, delcc) = self.diff_cc(old, new) 
     217                    chgcc = '' 
     218                    if delcc: 
     219                        chgcc += wrap(" * cc: %s (removed)" % 
     220                                      ', '.join(delcc),  
     221                                      self.COLS, ' ', ' ', CRLF, 
     222                                      self.ambiwidth) + CRLF 
     223                    if addcc: 
     224                        chgcc += wrap(" * cc: %s (added)" % 
     225                                      ', '.join(addcc),  
     226                                      self.COLS, ' ', ' ', CRLF, 
     227                                      self.ambiwidth) + CRLF 
     228                    if chgcc: 
     229                        changes_body += chgcc 
     230                    self.prev_cc += self.parse_cc(old) if old else [] 
     231                else: 
     232                    if field in ['owner', 'reporter']: 
     233                        old = obfuscate_email_address(old) 
     234                        new = obfuscate_email_address(new) 
     235                    newv = new 
     236                    length = 7 + len(field) 
     237                    spacer_old, spacer_new = ' ', ' ' 
     238                    if len(old + new) + length > self.COLS: 
     239                        length = 5 
     240                        if len(old) + length > self.COLS: 
     241                            spacer_old = CRLF 
     242                        if len(new) + length > self.COLS: 
     243                            spacer_new = CRLF 
     244                    chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, 
     245                                                  spacer_old, spacer_new, 
     246                                                  new) 
     247                    chg = chg.replace(CRLF, CRLF + length * ' ') 
     248                    chg = wrap(chg, self.COLS, '', length * ' ', CRLF, 
     249                               self.ambiwidth) 
     250                    changes_body += ' %s%s' % (chg, CRLF) 
     251                if newv: 
     252                    change_data[field] = {'oldvalue': old, 'newvalue': new} 
    200253        self.data.update({ 
    201             'ticket_props': self.format_props(), 
    202             'ticket_body_hdr': self.format_hdr(), 
    203             'subject': subject, 
    204             'ticket': ticket_values, 
    205254            'changes_body': changes_body, 
    206255            'changes_descr': changes_descr, 
    207256            'change': change_data 
    208257            }) 
    209         NotifyEmail.notify(self, ticket.id, subject, author) 
     258        return (link, summary, author) 
    210259 
    211260    def format_props(self): 
    212261        tkt = self.ticket 
    class TicketNotifyEmail(NotifyEmail): 
    404453    def get_text_width(self, text): 
    405454        return text_width(text, ambiwidth=self.ambiwidth) 
    406455 
     456 
     457class TicketAttachmentNotifier(Component): 
     458    """Sends notification on attachment change""" 
     459 
     460    implements(IAttachmentChangeListener) 
     461 
     462    ticket_notify_attachment = BoolOption('notification', 
     463                                          'ticket_notify_attachment', 
     464                                          'true', 
     465        """Always send notifications on ticket attachment events.""") 
     466 
     467    # IAttachmentChangeListener 
     468 
     469    def attachment_added(self, attachment): 
     470        if self.ticket_notify_attachment: 
     471            self._notify_attachment(attachment, True) 
     472 
     473    def attachment_deleted(self, attachment): 
     474        if self.ticket_notify_attachment: 
     475            self._notify_attachment(attachment, False) 
     476 
     477    def attachment_reparented(self, attachment, old_parent_realm, 
     478                              old_parent_id): 
     479        pass 
     480 
     481    # Implementation 
     482 
     483    def _notify_attachment(self, attachment, add): 
     484        author = attachment.author 
     485        resource = attachment.resource.parent 
     486        if resource.realm != 'ticket': 
     487            return 
     488        try: 
     489            ticket = Ticket(self.env, resource.id) 
     490        except ResourceNotFound: 
     491            return 
     492        tn = TicketNotifyEmail(self.env) 
     493        filename = attachment.filename 
     494        date = attachment.date or datetime.now(utc) 
     495        tn.notify_attachment(ticket, author, filename, date, add)