Edgewall Software

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

File attachment_0.13dev.2.patch, 15.0 KB (added by Emmanuel Blot, 14 years 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)