Edgewall Software

Ticket #3100: notification.py

File notification.py, 9.4 KB (added by guillaume.tardif @…, 6 years ago)

correction for new field class

Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2003-2006 Edgewall Software
4# Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com>
5# Copyright (C) 2005-2006 Emmanuel Blot <emmanuel.blot@free.fr>
6# All rights reserved.
7#
8# This software is licensed as described in the file COPYING, which
9# you should have received as part of this distribution. The terms
10# are also available at http://trac.edgewall.com/license.html.
11#
12# This software consists of voluntary contributions made by many
13# individuals. For the exact contribution history, see the revision
14# history and logs, available at http://projects.edgewall.com/trac/.
15#
16# Author: Daniel Lundin <daniel@edgewall.com>
17#
18
19import md5
20
21from trac import __version__
22from trac.core import *
23from trac.config import *
24from trac.util import CRLF, wrap
25from trac.notification import NotifyEmail
26
27
28class TicketNotificationSystem(Component):
29
30    always_notify_owner = BoolOption('notification', 'always_notify_owner',
31                                     'false',
32        """Always send notifications to the ticket owner (''since 0.9'').""")
33
34    always_notify_reporter = BoolOption('notification', 'always_notify_reporter',
35                                        'false',
36        """Always send notifications to any address in the ''reporter''
37        field.""")
38
39
40class TicketNotifyEmail(NotifyEmail):
41    """Notification of ticket changes."""
42
43    template_name = "ticket_notify_email.cs"
44    ticket = None
45    newticket = None
46    modtime = 0
47    from_email = 'trac+ticket@localhost'
48    COLS = 75
49
50    def __init__(self, env):
51        NotifyEmail.__init__(self, env)
52        self.prev_cc = []
53
54    def notify(self, ticket, newticket=True, modtime=0):
55        self.ticket = ticket
56        self.modtime = modtime
57        self.newticket = newticket
58        self.ticket['description'] = wrap(self.ticket.values.get('description', ''),
59                                          self.COLS, initial_indent=' ',
60                                          subsequent_indent=' ', linesep=CRLF)
61        self.ticket['link'] = self.env.abs_href.ticket(ticket.id)
62        self.hdf.set_unescaped('email.ticket_props', self.format_props())
63        self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr())
64        self.hdf.set_unescaped('ticket', self.ticket.values)
65        self.hdf['ticket.new'] = self.newticket
66        subject = self.format_subj()
67        if not self.newticket:
68            subject = 'Re: ' + subject
69        self.hdf.set_unescaped('email.subject', subject)
70        changes = ''
71        if not self.newticket and modtime:  # Ticket change
72            changelog = ticket.get_changelog(modtime)
73            for date, author, field, old, new in changelog:
74                self.hdf.set_unescaped('ticket.change.author', author)
75                pfx = 'ticket.change.%s' % field
76                newv = ''
77                if field == 'comment':
78                    newv = wrap(new, self.COLS, ' ', ' ', CRLF)
79                elif field == 'description':
80                    new_descr = wrap(new, self.COLS, ' ', ' ', CRLF)
81                    old_descr = wrap(old, self.COLS, '> ', '> ', CRLF)
82                    old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF)
83                    cdescr = CRLF
84                    cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF
85                    cdescr += 'New description:' + 2*CRLF + new_descr + CRLF
86                    self.hdf.set_unescaped('email.changes_descr', cdescr)
87                elif field == 'cc':
88                    (addcc, delcc) = self.diff_cc(old, new)
89                    chgcc = ''
90                    if delcc:
91                        chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), 
92                                      self.COLS, ' ', ' ', CRLF)
93                        chgcc += CRLF
94                    if addcc:
95                        chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), 
96                                      self.COLS, ' ', ' ', CRLF)
97                        chgcc += CRLF
98                    if chgcc:
99                        changes += chgcc
100                    self.prev_cc += old and self.parse_cc(old) or []
101                else:
102                    newv = new
103                    l = 7 + len(field)
104                    chg = wrap('%s => %s' % (old, new), self.COLS - l, '',
105                               l * ' ', CRLF)
106                    changes += '  * %s%s%s' % (field, chg, CRLF)
107                if newv:
108                    self.hdf.set_unescaped('%s.oldvalue' % pfx, old)
109                    self.hdf.set_unescaped('%s.newvalue' % pfx, newv)
110                self.hdf.set_unescaped('%s.author' % pfx, author)
111            if changes:
112                self.hdf.set_unescaped('email.changes_body', changes)
113        NotifyEmail.notify(self, ticket.id, subject)
114
115    def format_props(self):
116        tkt = self.ticket
117        fields = [f for f in tkt.fields if f.name not in ('summary', 'cc')]
118        width = [0, 0, 0, 0]
119        i = 0
120        for f in [f.name for f in fields if f.type != 'textarea']:
121            if not tkt.values.has_key(f):
122                continue
123            fval = tkt[f]
124            if fval.find('\n') != -1:
125                continue
126            idx = 2 * (i % 2)
127            if len(f) > width[idx]:
128                width[idx] = len(f)
129            if len(fval) > width[idx + 1]:
130                width[idx + 1] = len(fval)
131            i += 1
132        format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
133                  ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
134        l = (width[0] + width[1] + 5)
135        sep = l * '-' + '+' + (self.COLS - l) * '-'
136        txt = sep + CRLF
137        big = []
138        i = 0
139        for f in [f for f in fields if f.name != 'description']:
140            fname = f.name
141            if not tkt.values.has_key(fname):
142                continue
143            fval = tkt[fname]
144            if f.type == 'textarea' or '\n' in unicode(fval):
145                big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
146            else:
147                txt += format[i % 2] % (fname.capitalize(), fval)
148                i += 1
149        if i % 2:
150            txt += CRLF
151        if big:
152            txt += sep
153            for name, value in big:
154                txt += CRLF.join(['', name + ':', value, '', ''])
155        txt += sep
156        return txt
157
158    def parse_cc(self, txt):
159        return filter(lambda x: '@' in x, txt.replace(',', ' ').split())
160
161    def diff_cc(self, old, new):
162        oldcc = NotifyEmail.addrsep_re.split(old)
163        newcc = NotifyEmail.addrsep_re.split(new)
164        added = [x for x in newcc if x not in oldcc]
165        removed = [x for x in oldcc if x not in newcc]
166        return (added, removed)
167
168    def format_hdr(self):
169        return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
170                                                 self.COLS, linesep=CRLF))
171
172    def format_subj(self):
173        projname = self.config.get('project', 'name')
174        return '[%s] #%s: %s' % (projname, self.ticket.id,
175                                 self.ticket['summary'])
176
177    def get_recipients(self, tktid):
178        notify_reporter = self.config.getbool('notification',
179                                              'always_notify_reporter')
180        notify_owner = self.config.getbool('notification',
181                                           'always_notify_owner')
182
183        ccrecipients = self.prev_cc
184        torecipients = []
185        cursor = self.db.cursor()
186
187        # Harvest email addresses from the cc, reporter, and owner fields
188        cursor.execute("SELECT cc,reporter,owner FROM ticket WHERE id=%s",
189                       (tktid,))
190        row = cursor.fetchone()
191        if row:
192            ccrecipients += row[0] and row[0].replace(',', ' ').split() or []
193            if notify_reporter:
194                torecipients.append(row[1])
195            if notify_owner:
196                torecipients.append(row[2])
197
198        # Harvest email addresses from the author field of ticket_change(s)
199        if notify_reporter:
200            cursor.execute("SELECT DISTINCT author,ticket FROM ticket_change "
201                           "WHERE ticket=%s", (tktid,))
202            for author,ticket in cursor:
203                torecipients.append(author)
204
205        return (torecipients, ccrecipients)
206
207    def get_message_id(self, rcpt, modtime=0):
208        """Generate a predictable, but sufficiently unique message ID."""
209        s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'),
210                               int(self.ticket.id), modtime, rcpt)
211        dig = md5.new(s).hexdigest()
212        host = self.from_email[self.from_email.find('@') + 1:]
213        msgid = '<%03d.%s@%s>' % (len(s), dig, host)
214        return msgid
215
216    def send(self, torcpts, ccrcpts):
217        hdrs = {}
218        dest = torcpts or ccrcpts
219        if not dest:
220            self.env.log.info('no recipient for a ticket notification')
221            return 
222        hdrs['Message-ID'] = self.get_message_id(dest[0], self.modtime)
223        hdrs['X-Trac-Ticket-ID'] = str(self.ticket.id)
224        hdrs['X-Trac-Ticket-URL'] = self.ticket['link']
225        if not self.newticket:
226            hdrs['In-Reply-To'] = self.get_message_id(dest[0])
227            hdrs['References'] = self.get_message_id(dest[0])
228        NotifyEmail.send(self, torcpts, ccrcpts, hdrs)
229