diff examples/trac/trac/ticket/notification.py @ 39:93b4dcbafd7b trunk

Copy Trac to main branch.
author cmlenz
date Mon, 03 Jul 2006 18:53:27 +0000
parents
children
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/examples/trac/trac/ticket/notification.py
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2003-2006 Edgewall Software
+# Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com>
+# Copyright (C) 2005-2006 Emmanuel Blot <emmanuel.blot@free.fr>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Daniel Lundin <daniel@edgewall.com>
+#
+
+import md5
+
+from trac import __version__
+from trac.core import *
+from trac.config import *
+from trac.util.text import CRLF, wrap
+from trac.notification import NotifyEmail
+
+
+class TicketNotificationSystem(Component):
+
+    always_notify_owner = BoolOption('notification', 'always_notify_owner',
+                                     'false',
+        """Always send notifications to the ticket owner (''since 0.9'').""")
+
+    always_notify_reporter = BoolOption('notification', 'always_notify_reporter',
+                                        'false',
+        """Always send notifications to any address in the ''reporter''
+        field.""")
+
+    always_notify_updater = BoolOption('notification', 'always_notify_updater',
+                                       'true',
+        """Always send notifications to the person who causes the ticket 
+        property change.""")
+
+
+class TicketNotifyEmail(NotifyEmail):
+    """Notification of ticket changes."""
+
+    template_name = "ticket_notify_email.cs"
+    ticket = None
+    newticket = None
+    modtime = 0
+    from_email = 'trac+ticket@localhost'
+    COLS = 75
+
+    def __init__(self, env):
+        NotifyEmail.__init__(self, env)
+        self.prev_cc = []
+
+    def notify(self, ticket, req, newticket=True, modtime=0):
+        self.ticket = ticket
+        self.modtime = modtime
+        self.newticket = newticket
+        self.ticket['description'] = wrap(self.ticket.values.get('description', ''),
+                                          self.COLS, initial_indent=' ',
+                                          subsequent_indent=' ', linesep=CRLF)
+        self.hdf.set_unescaped('email.ticket_props', self.format_props())
+        self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr())
+        self.hdf['ticket.new'] = self.newticket
+        subject = self.format_subj()
+        link = req.abs_href.ticket(ticket.id)
+        if not self.newticket:
+            subject = 'Re: ' + subject
+        self.hdf.set_unescaped('email.subject', subject)
+        changes = ''
+        if not self.newticket and modtime:  # Ticket change
+            from trac.ticket.web_ui import TicketModule
+            for change in TicketModule(self.env).grouped_changelog_entries(
+                ticket, self.db, when=modtime):
+                if not change['permanent']: # attachment with same time...
+                    continue
+                self.hdf.set_unescaped('ticket.change.author', 
+                                       change['author'])
+                self.hdf.set_unescaped('ticket.change.comment',
+                                       wrap(change['comment'], self.COLS,
+                                            ' ', ' ', CRLF))
+                link += '#comment:%d' % change['cnum']
+                for field, values in change['fields'].iteritems():
+                    old = values['old']
+                    new = values['new']
+                    pfx = 'ticket.change.%s' % field
+                    newv = ''
+                    if field == 'description':
+                        new_descr = wrap(new, self.COLS, ' ', ' ', CRLF)
+                        old_descr = wrap(old, self.COLS, '> ', '> ', CRLF)
+                        old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF)
+                        cdescr = CRLF
+                        cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF
+                        cdescr += 'New description:' + 2*CRLF + new_descr + CRLF
+                        self.hdf.set_unescaped('email.changes_descr', cdescr)
+                    elif field == 'cc':
+                        (addcc, delcc) = self.diff_cc(old, new)
+                        chgcc = ''
+                        if delcc:
+                            chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), 
+                                          self.COLS, ' ', ' ', CRLF)
+                            chgcc += CRLF
+                        if addcc:
+                            chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), 
+                                          self.COLS, ' ', ' ', CRLF)
+                            chgcc += CRLF
+                        if chgcc:
+                            changes += chgcc
+                        self.prev_cc += old and self.parse_cc(old) or []
+                    else:
+                        newv = new
+                        l = 7 + len(field)
+                        chg = wrap('%s => %s' % (old, new), self.COLS - l, '',
+                                   l * ' ', CRLF)
+                        changes += '  * %s:  %s%s' % (field, chg, CRLF)
+                    if newv:
+                        self.hdf.set_unescaped('%s.oldvalue' % pfx, old)
+                        self.hdf.set_unescaped('%s.newvalue' % pfx, newv)
+            if changes:
+                self.hdf.set_unescaped('email.changes_body', changes)
+        self.ticket['link'] = link
+        self.hdf.set_unescaped('ticket', self.ticket.values)
+        NotifyEmail.notify(self, ticket.id, subject)
+
+    def format_props(self):
+        tkt = self.ticket
+        fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
+        width = [0, 0, 0, 0]
+        i = 0
+        for f in [f['name'] for f in fields if f['type'] != 'textarea']:
+            if not tkt.values.has_key(f):
+                continue
+            fval = tkt[f]
+            if fval.find('\n') != -1:
+                continue
+            idx = 2 * (i % 2)
+            if len(f) > width[idx]:
+                width[idx] = len(f)
+            if len(fval) > width[idx + 1]:
+                width[idx + 1] = len(fval)
+            i += 1
+        format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
+                  ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
+        l = (width[0] + width[1] + 5)
+        sep = l * '-' + '+' + (self.COLS - l) * '-'
+        txt = sep + CRLF
+        big = []
+        i = 0
+        for f in [f for f in fields if f['name'] != 'description']:
+            fname = f['name']
+            if not tkt.values.has_key(fname):
+                continue
+            fval = tkt[fname]
+            if f['type'] == 'textarea' or '\n' in unicode(fval):
+                big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
+            else:
+                txt += format[i % 2] % (fname.capitalize(), fval)
+                i += 1
+        if i % 2:
+            txt += CRLF
+        if big:
+            txt += sep
+            for name, value in big:
+                txt += CRLF.join(['', name + ':', value, '', ''])
+        txt += sep
+        return txt
+
+    def parse_cc(self, txt):
+        return filter(lambda x: '@' in x, txt.replace(',', ' ').split())
+
+    def diff_cc(self, old, new):
+        oldcc = NotifyEmail.addrsep_re.split(old)
+        newcc = NotifyEmail.addrsep_re.split(new)
+        added = [x for x in newcc if x and x not in oldcc]
+        removed = [x for x in oldcc if x and x not in newcc]
+        return (added, removed)
+
+    def format_hdr(self):
+        return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
+                                                 self.COLS, linesep=CRLF))
+
+    def format_subj(self):
+        projname = self.config.get('project', 'name')
+        return '[%s] #%s: %s' % (projname, self.ticket.id,
+                                 self.ticket['summary'])
+
+    def get_recipients(self, tktid):
+        notify_reporter = self.config.getbool('notification',
+                                              'always_notify_reporter')
+        notify_owner = self.config.getbool('notification',
+                                           'always_notify_owner')
+        notify_updater = self.config.getbool('notification', 
+                                             'always_notify_updater')
+
+        ccrecipients = self.prev_cc
+        torecipients = []
+        cursor = self.db.cursor()
+
+        # Harvest email addresses from the cc, reporter, and owner fields
+        cursor.execute("SELECT cc,reporter,owner FROM ticket WHERE id=%s",
+                       (tktid,))
+        row = cursor.fetchone()
+        if row:
+            ccrecipients += row[0] and row[0].replace(',', ' ').split() or []
+            if notify_reporter:
+                torecipients.append(row[1])
+            if notify_owner:
+                torecipients.append(row[2])
+
+        # Harvest email addresses from the author field of ticket_change(s)
+        if notify_reporter:
+            cursor.execute("SELECT DISTINCT author,ticket FROM ticket_change "
+                           "WHERE ticket=%s", (tktid,))
+            for author,ticket in cursor:
+                torecipients.append(author)
+
+        # Suppress the updater from the recipients
+        if not notify_updater:
+            cursor.execute("SELECT author FROM ticket_change WHERE ticket=%s "
+                           "ORDER BY time DESC LIMIT 1", (tktid,))
+            (updater, ) = cursor.fetchone() 
+            torecipients = [r for r in torecipients if r and r != updater]
+
+        return (torecipients, ccrecipients)
+
+    def get_message_id(self, rcpt, modtime=0):
+        """Generate a predictable, but sufficiently unique message ID."""
+        s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'),
+                               int(self.ticket.id), modtime, rcpt)
+        dig = md5.new(s).hexdigest()
+        host = self.from_email[self.from_email.find('@') + 1:]
+        msgid = '<%03d.%s@%s>' % (len(s), dig, host)
+        return msgid
+
+    def send(self, torcpts, ccrcpts):
+        hdrs = {}
+        always_cc = self.config['notification'].get('smtp_always_cc')
+        always_bcc = self.config['notification'].get('smtp_always_bcc')
+        dest = filter(None, torcpts) or filter(None, ccrcpts) or \
+               filter(None, [always_cc]) or filter(None, [always_bcc])
+        if not dest:
+            self.env.log.info('no recipient for a ticket notification')
+            return
+        hdrs['Message-ID'] = self.get_message_id(dest[0], self.modtime)
+        hdrs['X-Trac-Ticket-ID'] = str(self.ticket.id)
+        hdrs['X-Trac-Ticket-URL'] = self.ticket['link']
+        if not self.newticket:
+            hdrs['In-Reply-To'] = self.get_message_id(dest[0])
+            hdrs['References'] = self.get_message_id(dest[0])
+        NotifyEmail.send(self, torcpts, ccrcpts, hdrs)
+
Copyright (C) 2012-2017 Edgewall Software