Edgewall Software

Ticket #2259: attachment_1.0.1dev.patch

File attachment_1.0.1dev.patch, 15.9 KB (added by Emmanuel Blot, 12 years ago)

New patch for latest development version (1.0.1dev), including cboos fixes.

  • trac/ticket/notification.py

    From 96eea30c464c0aa22182ea6018c1fdbba63947fb Mon Sep 17 00:00:00 2001
    From: Emmanuel Blot <[email protected]>
    Date: Thu, 22 Nov 2012 14:31:31 +0100
    Subject: [PATCH] Restore TicketAttachmentNotifier feature
    
    ---
     trac/ticket/notification.py | 299 ++++++++++++++++++++++++++++----------------
     1 file changed, 193 insertions(+), 106 deletions(-)
    
    diff --git a/trac/ticket/notification.py b/trac/ticket/notification.py
    index f4dc3ab..b20f0df 100644
    a b  
    1818
    1919from __future__ import with_statement
    2020
     21from datetime import datetime
    2122from hashlib import md5
    2223
    2324from genshi.template.text import NewTextTemplate
    2425
     26from trac.attachment import IAttachmentChangeListener
    2527from trac.core import *
    2628from trac.config import *
    2729from trac.notification import NotifyEmail
    2830from trac.ticket.api import TicketSystem
     31from trac.ticket.model import Ticket
    2932from trac.util.datefmt import format_date_or_datetime, get_timezone, \
    30                               to_utimestamp
     33                              to_utimestamp, utc
    3134from trac.util.text import obfuscate_email_address, text_width, wrap
    3235from trac.util.translation import deactivate, reactivate
    3336
    class TicketNotifyEmail(NotifyEmail):  
    154157        translated_fields = ticket.fields
    155158        try:
    156159            ticket.fields = TicketSystem(self.env).get_ticket_fields()
    157             self._notify(ticket, newticket, modtime)
     160            self.ticket = ticket
     161            self.modtime = modtime
     162            self.newticket = newticket
     163            self.reporter = ''
     164            self.owner = ''
     165            if not newticket and modtime:  # Ticket change
     166                (link, summary, author) = \
     167                    self._build_notification_changes(ticket, modtime)
     168            else:
     169                link = self.env.abs_href.ticket(ticket.id)
     170                summary = self.ticket['summary']
     171                author = None
     172            if newticket:
     173                author = ticket['reporter']
     174            ticket_values = ticket.values.copy()
     175            ticket_values['id'] = ticket.id
     176            ticket_values['description'] = wrap(
     177                ticket_values.get('description', ''), self.COLS,
     178                initial_indent=' ', subsequent_indent=' ', linesep='\n',
     179                ambiwidth=self.ambiwidth)
     180            ticket_values['new'] = self.newticket
     181            ticket_values['link'] = link
     182            subject = self.format_subj(summary)
     183            if not self.newticket:
     184                subject = 'Re: ' + subject
     185            self.data.update({
     186                'ticket_props': self.format_props(),
     187                'ticket_body_hdr': self.format_hdr(),
     188                'subject': subject,
     189                'ticket': ticket_values,
     190                })
     191            NotifyEmail.notify(self, ticket.id, subject, author)
    158192        finally:
    159193            ticket.fields = translated_fields
    160194            reactivate(t)
    161195
    162     def _notify(self, ticket, newticket=True, modtime=None):
    163         self.ticket = ticket
    164         self.modtime = modtime
    165         self.newticket = newticket
     196    def notify_attachment(self, ticket, author, filename, modtime, body):
     197        """Send ticket attachment notification (untranslated)"""
     198        t = deactivate()
     199        translated_fields = ticket.fields
     200        try:
     201            ticket.fields = TicketSystem(self.env).get_ticket_fields()
     202            self.ticket = ticket
     203            self.modtime = modtime
     204            self.newticket = False
     205            self.reporter = ''
     206            self.owner = ''
     207            link = self.env.abs_href.ticket(ticket.id)
     208            summary = self.ticket['summary']
     209            ticket_values = ticket.values.copy()
     210            ticket_values['id'] = ticket.id
     211            ticket_values['description'] = wrap(
     212                ticket_values.get('description', ''), self.COLS,
     213                initial_indent=' ', subsequent_indent=' ', linesep='\n',
     214                ambiwidth=self.ambiwidth)
     215            ticket_values['new'] = self.newticket
     216            ticket_values['link'] = link
     217            subject = 'Re: ' + self.format_subj(summary)
     218            changes_descr = ''
     219            change_data = {'author': author}
     220            self.data.update({
     221                'ticket_props': self.format_props(),
     222                'ticket_body_hdr': self.format_hdr(),
     223                'subject': subject,
     224                'ticket': ticket_values,
     225                'changes_body': body,
     226                'changes_descr': changes_descr,
     227                'change': change_data,
     228                })
     229            NotifyEmail.notify(self, ticket.id, subject, author)
     230        finally:
     231            ticket.fields = translated_fields
     232            reactivate(t)
    166233
     234    def _build_notification_changes(self, ticket, modtime):
     235        from trac.ticket.web_ui import TicketModule
    167236        changes_body = ''
    168         self.reporter = ''
    169         self.owner = ''
    170237        changes_descr = ''
    171238        change_data = {}
    172239        link = self.env.abs_href.ticket(ticket.id)
    173240        summary = self.ticket['summary']
    174         author = None
    175 
    176         if not self.newticket and modtime:  # Ticket change
    177             from trac.ticket.web_ui import TicketModule
    178             for change in TicketModule(self.env).grouped_changelog_entries(
    179                                                 ticket, when=modtime):
    180                 if not change['permanent']: # attachment with same time...
    181                     continue
    182                 author = change['author']
    183                 change_data.update({
    184                     'author': self.obfuscate_email(author),
    185                     'comment': wrap(change['comment'], self.COLS, ' ', ' ',
    186                                     '\n', self.ambiwidth)
    187                     })
    188                 link += '#comment:%s' % str(change.get('cnum', ''))
    189                 for field, values in change['fields'].iteritems():
    190                     old = values['old']
    191                     new = values['new']
    192                     newv = ''
    193                     if field == 'description':
    194                         new_descr = wrap(new, self.COLS, ' ', ' ', '\n',
    195                                          self.ambiwidth)
    196                         old_descr = wrap(old, self.COLS, '> ', '> ', '\n',
    197                                          self.ambiwidth)
    198                         old_descr = old_descr.replace(2 * '\n', '\n' + '>' + \
    199                                                       '\n')
    200                         cdescr = '\n'
    201                         cdescr += 'Old description:' + 2 * '\n' + old_descr + \
    202                                   2 * '\n'
    203                         cdescr += 'New description:' + 2 * '\n' + new_descr + \
    204                                   '\n'
    205                         changes_descr = cdescr
    206                     elif field == 'summary':
    207                         summary = "%s (was: %s)" % (new, old)
    208                     elif field == 'cc':
    209                         (addcc, delcc) = self.diff_cc(old, new)
    210                         chgcc = ''
    211                         if delcc:
    212                             chgcc += wrap(" * cc: %s (removed)" %
    213                                           ', '.join(delcc),
    214                                           self.COLS, ' ', ' ', '\n',
    215                                           self.ambiwidth) + '\n'
    216                         if addcc:
    217                             chgcc += wrap(" * cc: %s (added)" %
    218                                           ', '.join(addcc),
    219                                           self.COLS, ' ', ' ', '\n',
    220                                           self.ambiwidth) + '\n'
    221                         if chgcc:
    222                             changes_body += chgcc
    223                         self.prev_cc += self.parse_cc(old) if old else []
    224                     else:
    225                         if field in ['owner', 'reporter']:
    226                             old = self.obfuscate_email(old)
    227                             new = self.obfuscate_email(new)
    228                         elif field in ticket.time_fields:
    229                             format = ticket.fields.by_name(field).get('format')
    230                             old = self.format_time_field(old, format)
    231                             new = self.format_time_field(new, format)
    232                         newv = new
    233                         length = 7 + len(field)
    234                         spacer_old, spacer_new = ' ', ' '
    235                         if len(old + new) + length > self.COLS:
    236                             length = 5
    237                             if len(old) + length > self.COLS:
    238                                 spacer_old = '\n'
    239                             if len(new) + length > self.COLS:
    240                                 spacer_new = '\n'
    241                         chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
    242                                                       spacer_old, spacer_new,
    243                                                       new)
    244                         chg = chg.replace('\n', '\n' + length * ' ')
    245                         chg = wrap(chg, self.COLS, '', length * ' ', '\n',
    246                                    self.ambiwidth)
    247                         changes_body += ' %s%s' % (chg, '\n')
    248                     if newv:
    249                         change_data[field] = {'oldvalue': old, 'newvalue': new}
    250 
    251         if newticket:
    252             author = ticket['reporter']
    253 
    254         ticket_values = ticket.values.copy()
    255         ticket_values['id'] = ticket.id
    256         ticket_values['description'] = wrap(
    257             ticket_values.get('description', ''), self.COLS,
    258             initial_indent=' ', subsequent_indent=' ', linesep='\n',
    259             ambiwidth=self.ambiwidth)
    260         ticket_values['new'] = self.newticket
    261         ticket_values['link'] = link
    262 
    263         subject = self.format_subj(summary)
    264         if not self.newticket:
    265             subject = 'Re: ' + subject
     241        for change in TicketModule(self.env).grouped_changelog_entries(
     242                                            ticket, when=modtime):
     243            if not change['permanent']: # attachment with same time...
     244                continue
     245            author = change['author']
     246            change_data.update({
     247                'author': self.obfuscate_email(author),
     248                'comment': wrap(change['comment'], self.COLS, ' ', ' ',
     249                                '\n', self.ambiwidth)
     250                })
     251            link += '#comment:%s' % str(change.get('cnum', ''))
     252            for field, values in change['fields'].iteritems():
     253                old = values['old']
     254                new = values['new']
     255                newv = ''
     256                if field == 'description':
     257                    new_descr = wrap(new, self.COLS, ' ', ' ', '\n',
     258                                     self.ambiwidth)
     259                    old_descr = wrap(old, self.COLS, '> ', '> ', '\n',
     260                                     self.ambiwidth)
     261                    old_descr = old_descr.replace(2 * '\n', '\n' + '>' + \
     262                                                  '\n')
     263                    cdescr = '\n'
     264                    cdescr += 'Old description:' + 2 * '\n' + old_descr + \
     265                              2 * '\n'
     266                    cdescr += 'New description:' + 2 * '\n' + new_descr + \
     267                              '\n'
     268                    changes_descr = cdescr
     269                elif field == 'summary':
     270                    summary = "%s (was: %s)" % (new, old)
     271                elif field == 'cc':
     272                    (addcc, delcc) = self.diff_cc(old, new)
     273                    chgcc = ''
     274                    if delcc:
     275                        chgcc += wrap(" * cc: %s (removed)" %
     276                                      ', '.join(delcc),
     277                                      self.COLS, ' ', ' ', '\n',
     278                                      self.ambiwidth) + '\n'
     279                    if addcc:
     280                        chgcc += wrap(" * cc: %s (added)" %
     281                                      ', '.join(addcc),
     282                                      self.COLS, ' ', ' ', '\n',
     283                                      self.ambiwidth) + '\n'
     284                    if chgcc:
     285                        changes_body += chgcc
     286                    self.prev_cc += self.parse_cc(old) if old else []
     287                else:
     288                    if field in ['owner', 'reporter']:
     289                        old = self.obfuscate_email(old)
     290                        new = self.obfuscate_email(new)
     291                    elif field in ticket.time_fields:
     292                        format = ticket.fields.by_name(field).get('format')
     293                        old = self.format_time_field(old, format)
     294                        new = self.format_time_field(new, format)
     295                    newv = new
     296                    length = 7 + len(field)
     297                    spacer_old, spacer_new = ' ', ' '
     298                    if len(old + new) + length > self.COLS:
     299                        length = 5
     300                        if len(old) + length > self.COLS:
     301                            spacer_old = '\n'
     302                        if len(new) + length > self.COLS:
     303                            spacer_new = '\n'
     304                    chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
     305                                                  spacer_old, spacer_new,
     306                                                  new)
     307                    chg = chg.replace('\n', '\n' + length * ' ')
     308                    chg = wrap(chg, self.COLS, '', length * ' ', '\n',
     309                               self.ambiwidth)
     310                    changes_body += ' %s%s' % (chg, '\n')
     311                if newv:
     312                    change_data[field] = {'oldvalue': old, 'newvalue': new}
    266313        self.data.update({
    267             'ticket_props': self.format_props(),
    268             'ticket_body_hdr': self.format_hdr(),
    269             'subject': subject,
    270             'ticket': ticket_values,
    271314            'changes_body': changes_body,
    272315            'changes_descr': changes_descr,
    273             'change': change_data
     316            'change': change_data,
    274317            })
    275         NotifyEmail.notify(self, ticket.id, subject, author)
     318        return (link, summary, author)
    276319
    277320    def format_props(self):
    278321        tkt = self.ticket
    class TicketNotifyEmail(NotifyEmail):  
    441484        else:
    442485            return obfuscate_email_address(text)
    443486
     487class TicketAttachmentNotifier(Component):
     488    """Sends notification on attachment change"""
     489
     490    implements(IAttachmentChangeListener)
     491
     492    ticket_notify_attachment = BoolOption('notification',
     493                                          'ticket_notify_attachment',
     494                                          'true',
     495        """Always send notifications on ticket attachment events.""")
     496
     497    # IAttachmentChangeListener
     498
     499    def attachment_added(self, attachment):
     500        if self.ticket_notify_attachment:
     501            self._notify_attachment(attachment, True)
     502
     503    def attachment_deleted(self, attachment):
     504        if self.ticket_notify_attachment:
     505            self._notify_attachment(attachment, False)
     506
     507    def attachment_reparented(self, attachment, old_parent_realm,
     508                              old_parent_id):
     509        pass
     510
     511    # Implementation
     512
     513    def _notify_attachment(self, attachment, add):
     514        author = attachment.author
     515        resource = attachment.resource.parent
     516        if resource.realm != 'ticket':
     517            return
     518        ticket = Ticket(self.env, resource.id)
     519        tn = TicketNotifyEmail(self.env)
     520        filename = attachment.filename
     521        date = attachment.date or datetime.now(utc)
     522        # Note: no translation yet
     523        if add:
     524            body = "Attachment \"%s\" added." % filename
     525        else:
     526            body = "Attachment \"%s\" removed." % filename
     527        if attachment.description:
     528            body += "\n\n" + attachment.description
     529        tn.notify_attachment(ticket, author, filename, date, body)
     530
    444531class BatchTicketNotifyEmail(NotifyEmail):
    445532    """Notification of ticket batch modifications."""
    446533