Mercurial > genshi > mirror
comparison 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 |
comparison
equal
deleted
inserted
replaced
38:ee669cb9cccc | 39:93b4dcbafd7b |
---|---|
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 | |
19 import md5 | |
20 | |
21 from trac import __version__ | |
22 from trac.core import * | |
23 from trac.config import * | |
24 from trac.util.text import CRLF, wrap | |
25 from trac.notification import NotifyEmail | |
26 | |
27 | |
28 class 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 always_notify_updater = BoolOption('notification', 'always_notify_updater', | |
40 'true', | |
41 """Always send notifications to the person who causes the ticket | |
42 property change.""") | |
43 | |
44 | |
45 class TicketNotifyEmail(NotifyEmail): | |
46 """Notification of ticket changes.""" | |
47 | |
48 template_name = "ticket_notify_email.cs" | |
49 ticket = None | |
50 newticket = None | |
51 modtime = 0 | |
52 from_email = 'trac+ticket@localhost' | |
53 COLS = 75 | |
54 | |
55 def __init__(self, env): | |
56 NotifyEmail.__init__(self, env) | |
57 self.prev_cc = [] | |
58 | |
59 def notify(self, ticket, req, newticket=True, modtime=0): | |
60 self.ticket = ticket | |
61 self.modtime = modtime | |
62 self.newticket = newticket | |
63 self.ticket['description'] = wrap(self.ticket.values.get('description', ''), | |
64 self.COLS, initial_indent=' ', | |
65 subsequent_indent=' ', linesep=CRLF) | |
66 self.hdf.set_unescaped('email.ticket_props', self.format_props()) | |
67 self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr()) | |
68 self.hdf['ticket.new'] = self.newticket | |
69 subject = self.format_subj() | |
70 link = req.abs_href.ticket(ticket.id) | |
71 if not self.newticket: | |
72 subject = 'Re: ' + subject | |
73 self.hdf.set_unescaped('email.subject', subject) | |
74 changes = '' | |
75 if not self.newticket and modtime: # Ticket change | |
76 from trac.ticket.web_ui import TicketModule | |
77 for change in TicketModule(self.env).grouped_changelog_entries( | |
78 ticket, self.db, when=modtime): | |
79 if not change['permanent']: # attachment with same time... | |
80 continue | |
81 self.hdf.set_unescaped('ticket.change.author', | |
82 change['author']) | |
83 self.hdf.set_unescaped('ticket.change.comment', | |
84 wrap(change['comment'], self.COLS, | |
85 ' ', ' ', CRLF)) | |
86 link += '#comment:%d' % change['cnum'] | |
87 for field, values in change['fields'].iteritems(): | |
88 old = values['old'] | |
89 new = values['new'] | |
90 pfx = 'ticket.change.%s' % field | |
91 newv = '' | |
92 if field == 'description': | |
93 new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) | |
94 old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) | |
95 old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF) | |
96 cdescr = CRLF | |
97 cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF | |
98 cdescr += 'New description:' + 2*CRLF + new_descr + CRLF | |
99 self.hdf.set_unescaped('email.changes_descr', cdescr) | |
100 elif field == 'cc': | |
101 (addcc, delcc) = self.diff_cc(old, new) | |
102 chgcc = '' | |
103 if delcc: | |
104 chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), | |
105 self.COLS, ' ', ' ', CRLF) | |
106 chgcc += CRLF | |
107 if addcc: | |
108 chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), | |
109 self.COLS, ' ', ' ', CRLF) | |
110 chgcc += CRLF | |
111 if chgcc: | |
112 changes += chgcc | |
113 self.prev_cc += old and self.parse_cc(old) or [] | |
114 else: | |
115 newv = new | |
116 l = 7 + len(field) | |
117 chg = wrap('%s => %s' % (old, new), self.COLS - l, '', | |
118 l * ' ', CRLF) | |
119 changes += ' * %s: %s%s' % (field, chg, CRLF) | |
120 if newv: | |
121 self.hdf.set_unescaped('%s.oldvalue' % pfx, old) | |
122 self.hdf.set_unescaped('%s.newvalue' % pfx, newv) | |
123 if changes: | |
124 self.hdf.set_unescaped('email.changes_body', changes) | |
125 self.ticket['link'] = link | |
126 self.hdf.set_unescaped('ticket', self.ticket.values) | |
127 NotifyEmail.notify(self, ticket.id, subject) | |
128 | |
129 def format_props(self): | |
130 tkt = self.ticket | |
131 fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')] | |
132 width = [0, 0, 0, 0] | |
133 i = 0 | |
134 for f in [f['name'] for f in fields if f['type'] != 'textarea']: | |
135 if not tkt.values.has_key(f): | |
136 continue | |
137 fval = tkt[f] | |
138 if fval.find('\n') != -1: | |
139 continue | |
140 idx = 2 * (i % 2) | |
141 if len(f) > width[idx]: | |
142 width[idx] = len(f) | |
143 if len(fval) > width[idx + 1]: | |
144 width[idx + 1] = len(fval) | |
145 i += 1 | |
146 format = ('%%%is: %%-%is | ' % (width[0], width[1]), | |
147 ' %%%is: %%-%is%s' % (width[2], width[3], CRLF)) | |
148 l = (width[0] + width[1] + 5) | |
149 sep = l * '-' + '+' + (self.COLS - l) * '-' | |
150 txt = sep + CRLF | |
151 big = [] | |
152 i = 0 | |
153 for f in [f for f in fields if f['name'] != 'description']: | |
154 fname = f['name'] | |
155 if not tkt.values.has_key(fname): | |
156 continue | |
157 fval = tkt[fname] | |
158 if f['type'] == 'textarea' or '\n' in unicode(fval): | |
159 big.append((fname.capitalize(), CRLF.join(fval.splitlines()))) | |
160 else: | |
161 txt += format[i % 2] % (fname.capitalize(), fval) | |
162 i += 1 | |
163 if i % 2: | |
164 txt += CRLF | |
165 if big: | |
166 txt += sep | |
167 for name, value in big: | |
168 txt += CRLF.join(['', name + ':', value, '', '']) | |
169 txt += sep | |
170 return txt | |
171 | |
172 def parse_cc(self, txt): | |
173 return filter(lambda x: '@' in x, txt.replace(',', ' ').split()) | |
174 | |
175 def diff_cc(self, old, new): | |
176 oldcc = NotifyEmail.addrsep_re.split(old) | |
177 newcc = NotifyEmail.addrsep_re.split(new) | |
178 added = [x for x in newcc if x and x not in oldcc] | |
179 removed = [x for x in oldcc if x and x not in newcc] | |
180 return (added, removed) | |
181 | |
182 def format_hdr(self): | |
183 return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'], | |
184 self.COLS, linesep=CRLF)) | |
185 | |
186 def format_subj(self): | |
187 projname = self.config.get('project', 'name') | |
188 return '[%s] #%s: %s' % (projname, self.ticket.id, | |
189 self.ticket['summary']) | |
190 | |
191 def get_recipients(self, tktid): | |
192 notify_reporter = self.config.getbool('notification', | |
193 'always_notify_reporter') | |
194 notify_owner = self.config.getbool('notification', | |
195 'always_notify_owner') | |
196 notify_updater = self.config.getbool('notification', | |
197 'always_notify_updater') | |
198 | |
199 ccrecipients = self.prev_cc | |
200 torecipients = [] | |
201 cursor = self.db.cursor() | |
202 | |
203 # Harvest email addresses from the cc, reporter, and owner fields | |
204 cursor.execute("SELECT cc,reporter,owner FROM ticket WHERE id=%s", | |
205 (tktid,)) | |
206 row = cursor.fetchone() | |
207 if row: | |
208 ccrecipients += row[0] and row[0].replace(',', ' ').split() or [] | |
209 if notify_reporter: | |
210 torecipients.append(row[1]) | |
211 if notify_owner: | |
212 torecipients.append(row[2]) | |
213 | |
214 # Harvest email addresses from the author field of ticket_change(s) | |
215 if notify_reporter: | |
216 cursor.execute("SELECT DISTINCT author,ticket FROM ticket_change " | |
217 "WHERE ticket=%s", (tktid,)) | |
218 for author,ticket in cursor: | |
219 torecipients.append(author) | |
220 | |
221 # Suppress the updater from the recipients | |
222 if not notify_updater: | |
223 cursor.execute("SELECT author FROM ticket_change WHERE ticket=%s " | |
224 "ORDER BY time DESC LIMIT 1", (tktid,)) | |
225 (updater, ) = cursor.fetchone() | |
226 torecipients = [r for r in torecipients if r and r != updater] | |
227 | |
228 return (torecipients, ccrecipients) | |
229 | |
230 def get_message_id(self, rcpt, modtime=0): | |
231 """Generate a predictable, but sufficiently unique message ID.""" | |
232 s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'), | |
233 int(self.ticket.id), modtime, rcpt) | |
234 dig = md5.new(s).hexdigest() | |
235 host = self.from_email[self.from_email.find('@') + 1:] | |
236 msgid = '<%03d.%s@%s>' % (len(s), dig, host) | |
237 return msgid | |
238 | |
239 def send(self, torcpts, ccrcpts): | |
240 hdrs = {} | |
241 always_cc = self.config['notification'].get('smtp_always_cc') | |
242 always_bcc = self.config['notification'].get('smtp_always_bcc') | |
243 dest = filter(None, torcpts) or filter(None, ccrcpts) or \ | |
244 filter(None, [always_cc]) or filter(None, [always_bcc]) | |
245 if not dest: | |
246 self.env.log.info('no recipient for a ticket notification') | |
247 return | |
248 hdrs['Message-ID'] = self.get_message_id(dest[0], self.modtime) | |
249 hdrs['X-Trac-Ticket-ID'] = str(self.ticket.id) | |
250 hdrs['X-Trac-Ticket-URL'] = self.ticket['link'] | |
251 if not self.newticket: | |
252 hdrs['In-Reply-To'] = self.get_message_id(dest[0]) | |
253 hdrs['References'] = self.get_message_id(dest[0]) | |
254 NotifyEmail.send(self, torcpts, ccrcpts, hdrs) | |
255 |