##// END OF EJS Templates
premailer: replace logger instead of altering handlers....
super-admin -
r4857:916d130a default
parent child Browse files
Show More
@@ -1,453 +1,454 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Model for notifications
23 Model for notifications
24 """
24 """
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 import premailer
29 import premailer
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.sql.expression import false, true
31 from sqlalchemy.sql.expression import false, true
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import Notification, User, UserNotification
36 from rhodecode.model.db import Notification, User, UserNotification
37 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
38 from rhodecode.translation import TranslationString
38 from rhodecode.translation import TranslationString
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class NotificationModel(BaseModel):
43 class NotificationModel(BaseModel):
44
44
45 cls = Notification
45 cls = Notification
46
46
47 def __get_notification(self, notification):
47 def __get_notification(self, notification):
48 if isinstance(notification, Notification):
48 if isinstance(notification, Notification):
49 return notification
49 return notification
50 elif isinstance(notification, (int, long)):
50 elif isinstance(notification, (int, long)):
51 return Notification.get(notification)
51 return Notification.get(notification)
52 else:
52 else:
53 if notification:
53 if notification:
54 raise Exception('notification must be int, long or Instance'
54 raise Exception('notification must be int, long or Instance'
55 ' of Notification got %s' % type(notification))
55 ' of Notification got %s' % type(notification))
56
56
57 def create(
57 def create(
58 self, created_by, notification_subject='', notification_body='',
58 self, created_by, notification_subject='', notification_body='',
59 notification_type=Notification.TYPE_MESSAGE, recipients=None,
59 notification_type=Notification.TYPE_MESSAGE, recipients=None,
60 mention_recipients=None, with_email=True, email_kwargs=None):
60 mention_recipients=None, with_email=True, email_kwargs=None):
61 """
61 """
62
62
63 Creates notification of given type
63 Creates notification of given type
64
64
65 :param created_by: int, str or User instance. User who created this
65 :param created_by: int, str or User instance. User who created this
66 notification
66 notification
67 :param notification_subject: subject of notification itself,
67 :param notification_subject: subject of notification itself,
68 it will be generated automatically from notification_type if not specified
68 it will be generated automatically from notification_type if not specified
69 :param notification_body: body of notification text
69 :param notification_body: body of notification text
70 it will be generated automatically from notification_type if not specified
70 it will be generated automatically from notification_type if not specified
71 :param notification_type: type of notification, based on that we
71 :param notification_type: type of notification, based on that we
72 pick templates
72 pick templates
73 :param recipients: list of int, str or User objects, when None
73 :param recipients: list of int, str or User objects, when None
74 is given send to all admins
74 is given send to all admins
75 :param mention_recipients: list of int, str or User objects,
75 :param mention_recipients: list of int, str or User objects,
76 that were mentioned
76 that were mentioned
77 :param with_email: send email with this notification
77 :param with_email: send email with this notification
78 :param email_kwargs: dict with arguments to generate email
78 :param email_kwargs: dict with arguments to generate email
79 """
79 """
80
80
81 from rhodecode.lib.celerylib import tasks, run_task
81 from rhodecode.lib.celerylib import tasks, run_task
82
82
83 if recipients and not getattr(recipients, '__iter__', False):
83 if recipients and not getattr(recipients, '__iter__', False):
84 raise Exception('recipients must be an iterable object')
84 raise Exception('recipients must be an iterable object')
85
85
86 if not (notification_subject and notification_body) and not notification_type:
86 if not (notification_subject and notification_body) and not notification_type:
87 raise ValueError('notification_subject, and notification_body '
87 raise ValueError('notification_subject, and notification_body '
88 'cannot be empty when notification_type is not specified')
88 'cannot be empty when notification_type is not specified')
89
89
90 created_by_obj = self._get_user(created_by)
90 created_by_obj = self._get_user(created_by)
91
91
92 if not created_by_obj:
92 if not created_by_obj:
93 raise Exception('unknown user %s' % created_by)
93 raise Exception('unknown user %s' % created_by)
94
94
95 # default MAIN body if not given
95 # default MAIN body if not given
96 email_kwargs = email_kwargs or {'body': notification_body}
96 email_kwargs = email_kwargs or {'body': notification_body}
97 mention_recipients = mention_recipients or set()
97 mention_recipients = mention_recipients or set()
98
98
99 if recipients is None:
99 if recipients is None:
100 # recipients is None means to all admins
100 # recipients is None means to all admins
101 recipients_objs = User.query().filter(User.admin == true()).all()
101 recipients_objs = User.query().filter(User.admin == true()).all()
102 log.debug('sending notifications %s to admins: %s',
102 log.debug('sending notifications %s to admins: %s',
103 notification_type, recipients_objs)
103 notification_type, recipients_objs)
104 else:
104 else:
105 recipients_objs = set()
105 recipients_objs = set()
106 for u in recipients:
106 for u in recipients:
107 obj = self._get_user(u)
107 obj = self._get_user(u)
108 if obj:
108 if obj:
109 recipients_objs.add(obj)
109 recipients_objs.add(obj)
110 else: # we didn't find this user, log the error and carry on
110 else: # we didn't find this user, log the error and carry on
111 log.error('cannot notify unknown user %r', u)
111 log.error('cannot notify unknown user %r', u)
112
112
113 if not recipients_objs:
113 if not recipients_objs:
114 raise Exception('no valid recipients specified')
114 raise Exception('no valid recipients specified')
115
115
116 log.debug('sending notifications %s to %s',
116 log.debug('sending notifications %s to %s',
117 notification_type, recipients_objs)
117 notification_type, recipients_objs)
118
118
119 # add mentioned users into recipients
119 # add mentioned users into recipients
120 final_recipients = set(recipients_objs).union(mention_recipients)
120 final_recipients = set(recipients_objs).union(mention_recipients)
121
121
122 (subject, email_body, email_body_plaintext) = \
122 (subject, email_body, email_body_plaintext) = \
123 EmailNotificationModel().render_email(notification_type, **email_kwargs)
123 EmailNotificationModel().render_email(notification_type, **email_kwargs)
124
124
125 if not notification_subject:
125 if not notification_subject:
126 notification_subject = subject
126 notification_subject = subject
127
127
128 if not notification_body:
128 if not notification_body:
129 notification_body = email_body_plaintext
129 notification_body = email_body_plaintext
130
130
131 notification = Notification.create(
131 notification = Notification.create(
132 created_by=created_by_obj, subject=notification_subject,
132 created_by=created_by_obj, subject=notification_subject,
133 body=notification_body, recipients=final_recipients,
133 body=notification_body, recipients=final_recipients,
134 type_=notification_type
134 type_=notification_type
135 )
135 )
136
136
137 if not with_email: # skip sending email, and just create notification
137 if not with_email: # skip sending email, and just create notification
138 return notification
138 return notification
139
139
140 # don't send email to person who created this comment
140 # don't send email to person who created this comment
141 rec_objs = set(recipients_objs).difference({created_by_obj})
141 rec_objs = set(recipients_objs).difference({created_by_obj})
142
142
143 # now notify all recipients in question
143 # now notify all recipients in question
144
144
145 for recipient in rec_objs.union(mention_recipients):
145 for recipient in rec_objs.union(mention_recipients):
146 # inject current recipient
146 # inject current recipient
147 email_kwargs['recipient'] = recipient
147 email_kwargs['recipient'] = recipient
148 email_kwargs['mention'] = recipient in mention_recipients
148 email_kwargs['mention'] = recipient in mention_recipients
149 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
149 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
150 notification_type, **email_kwargs)
150 notification_type, **email_kwargs)
151
151
152 extra_headers = None
152 extra_headers = None
153 if 'thread_ids' in email_kwargs:
153 if 'thread_ids' in email_kwargs:
154 extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')}
154 extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')}
155
155
156 log.debug('Creating notification email task for user:`%s`', recipient)
156 log.debug('Creating notification email task for user:`%s`', recipient)
157 task = run_task(
157 task = run_task(
158 tasks.send_email, recipient.email, subject,
158 tasks.send_email, recipient.email, subject,
159 email_body_plaintext, email_body, extra_headers=extra_headers)
159 email_body_plaintext, email_body, extra_headers=extra_headers)
160 log.debug('Created email task: %s', task)
160 log.debug('Created email task: %s', task)
161
161
162 return notification
162 return notification
163
163
164 def delete(self, user, notification):
164 def delete(self, user, notification):
165 # we don't want to remove actual notification just the assignment
165 # we don't want to remove actual notification just the assignment
166 try:
166 try:
167 notification = self.__get_notification(notification)
167 notification = self.__get_notification(notification)
168 user = self._get_user(user)
168 user = self._get_user(user)
169 if notification and user:
169 if notification and user:
170 obj = UserNotification.query()\
170 obj = UserNotification.query()\
171 .filter(UserNotification.user == user)\
171 .filter(UserNotification.user == user)\
172 .filter(UserNotification.notification == notification)\
172 .filter(UserNotification.notification == notification)\
173 .one()
173 .one()
174 Session().delete(obj)
174 Session().delete(obj)
175 return True
175 return True
176 except Exception:
176 except Exception:
177 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
178 raise
178 raise
179
179
180 def get_for_user(self, user, filter_=None):
180 def get_for_user(self, user, filter_=None):
181 """
181 """
182 Get mentions for given user, filter them if filter dict is given
182 Get mentions for given user, filter them if filter dict is given
183 """
183 """
184 user = self._get_user(user)
184 user = self._get_user(user)
185
185
186 q = UserNotification.query()\
186 q = UserNotification.query()\
187 .filter(UserNotification.user == user)\
187 .filter(UserNotification.user == user)\
188 .join((
188 .join((
189 Notification, UserNotification.notification_id ==
189 Notification, UserNotification.notification_id ==
190 Notification.notification_id))
190 Notification.notification_id))
191 if filter_ == ['all']:
191 if filter_ == ['all']:
192 q = q # no filter
192 q = q # no filter
193 elif filter_ == ['unread']:
193 elif filter_ == ['unread']:
194 q = q.filter(UserNotification.read == false())
194 q = q.filter(UserNotification.read == false())
195 elif filter_:
195 elif filter_:
196 q = q.filter(Notification.type_.in_(filter_))
196 q = q.filter(Notification.type_.in_(filter_))
197
197
198 return q
198 return q
199
199
200 def mark_read(self, user, notification):
200 def mark_read(self, user, notification):
201 try:
201 try:
202 notification = self.__get_notification(notification)
202 notification = self.__get_notification(notification)
203 user = self._get_user(user)
203 user = self._get_user(user)
204 if notification and user:
204 if notification and user:
205 obj = UserNotification.query()\
205 obj = UserNotification.query()\
206 .filter(UserNotification.user == user)\
206 .filter(UserNotification.user == user)\
207 .filter(UserNotification.notification == notification)\
207 .filter(UserNotification.notification == notification)\
208 .one()
208 .one()
209 obj.read = True
209 obj.read = True
210 Session().add(obj)
210 Session().add(obj)
211 return True
211 return True
212 except Exception:
212 except Exception:
213 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
214 raise
214 raise
215
215
216 def mark_all_read_for_user(self, user, filter_=None):
216 def mark_all_read_for_user(self, user, filter_=None):
217 user = self._get_user(user)
217 user = self._get_user(user)
218 q = UserNotification.query()\
218 q = UserNotification.query()\
219 .filter(UserNotification.user == user)\
219 .filter(UserNotification.user == user)\
220 .filter(UserNotification.read == false())\
220 .filter(UserNotification.read == false())\
221 .join((
221 .join((
222 Notification, UserNotification.notification_id ==
222 Notification, UserNotification.notification_id ==
223 Notification.notification_id))
223 Notification.notification_id))
224 if filter_ == ['unread']:
224 if filter_ == ['unread']:
225 q = q.filter(UserNotification.read == false())
225 q = q.filter(UserNotification.read == false())
226 elif filter_:
226 elif filter_:
227 q = q.filter(Notification.type_.in_(filter_))
227 q = q.filter(Notification.type_.in_(filter_))
228
228
229 # this is a little inefficient but sqlalchemy doesn't support
229 # this is a little inefficient but sqlalchemy doesn't support
230 # update on joined tables :(
230 # update on joined tables :(
231 for obj in q.all():
231 for obj in q.all():
232 obj.read = True
232 obj.read = True
233 Session().add(obj)
233 Session().add(obj)
234
234
235 def get_unread_cnt_for_user(self, user):
235 def get_unread_cnt_for_user(self, user):
236 user = self._get_user(user)
236 user = self._get_user(user)
237 return UserNotification.query()\
237 return UserNotification.query()\
238 .filter(UserNotification.read == false())\
238 .filter(UserNotification.read == false())\
239 .filter(UserNotification.user == user).count()
239 .filter(UserNotification.user == user).count()
240
240
241 def get_unread_for_user(self, user):
241 def get_unread_for_user(self, user):
242 user = self._get_user(user)
242 user = self._get_user(user)
243 return [x.notification for x in UserNotification.query()
243 return [x.notification for x in UserNotification.query()
244 .filter(UserNotification.read == false())
244 .filter(UserNotification.read == false())
245 .filter(UserNotification.user == user).all()]
245 .filter(UserNotification.user == user).all()]
246
246
247 def get_user_notification(self, user, notification):
247 def get_user_notification(self, user, notification):
248 user = self._get_user(user)
248 user = self._get_user(user)
249 notification = self.__get_notification(notification)
249 notification = self.__get_notification(notification)
250
250
251 return UserNotification.query()\
251 return UserNotification.query()\
252 .filter(UserNotification.notification == notification)\
252 .filter(UserNotification.notification == notification)\
253 .filter(UserNotification.user == user).scalar()
253 .filter(UserNotification.user == user).scalar()
254
254
255 def make_description(self, notification, translate, show_age=True):
255 def make_description(self, notification, translate, show_age=True):
256 """
256 """
257 Creates a human readable description based on properties
257 Creates a human readable description based on properties
258 of notification object
258 of notification object
259 """
259 """
260 _ = translate
260 _ = translate
261 _map = {
261 _map = {
262 notification.TYPE_CHANGESET_COMMENT: [
262 notification.TYPE_CHANGESET_COMMENT: [
263 _('%(user)s commented on commit %(date_or_age)s'),
263 _('%(user)s commented on commit %(date_or_age)s'),
264 _('%(user)s commented on commit at %(date_or_age)s'),
264 _('%(user)s commented on commit at %(date_or_age)s'),
265 ],
265 ],
266 notification.TYPE_MESSAGE: [
266 notification.TYPE_MESSAGE: [
267 _('%(user)s sent message %(date_or_age)s'),
267 _('%(user)s sent message %(date_or_age)s'),
268 _('%(user)s sent message at %(date_or_age)s'),
268 _('%(user)s sent message at %(date_or_age)s'),
269 ],
269 ],
270 notification.TYPE_MENTION: [
270 notification.TYPE_MENTION: [
271 _('%(user)s mentioned you %(date_or_age)s'),
271 _('%(user)s mentioned you %(date_or_age)s'),
272 _('%(user)s mentioned you at %(date_or_age)s'),
272 _('%(user)s mentioned you at %(date_or_age)s'),
273 ],
273 ],
274 notification.TYPE_REGISTRATION: [
274 notification.TYPE_REGISTRATION: [
275 _('%(user)s registered in RhodeCode %(date_or_age)s'),
275 _('%(user)s registered in RhodeCode %(date_or_age)s'),
276 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
276 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
277 ],
277 ],
278 notification.TYPE_PULL_REQUEST: [
278 notification.TYPE_PULL_REQUEST: [
279 _('%(user)s opened new pull request %(date_or_age)s'),
279 _('%(user)s opened new pull request %(date_or_age)s'),
280 _('%(user)s opened new pull request at %(date_or_age)s'),
280 _('%(user)s opened new pull request at %(date_or_age)s'),
281 ],
281 ],
282 notification.TYPE_PULL_REQUEST_UPDATE: [
282 notification.TYPE_PULL_REQUEST_UPDATE: [
283 _('%(user)s updated pull request %(date_or_age)s'),
283 _('%(user)s updated pull request %(date_or_age)s'),
284 _('%(user)s updated pull request at %(date_or_age)s'),
284 _('%(user)s updated pull request at %(date_or_age)s'),
285 ],
285 ],
286 notification.TYPE_PULL_REQUEST_COMMENT: [
286 notification.TYPE_PULL_REQUEST_COMMENT: [
287 _('%(user)s commented on pull request %(date_or_age)s'),
287 _('%(user)s commented on pull request %(date_or_age)s'),
288 _('%(user)s commented on pull request at %(date_or_age)s'),
288 _('%(user)s commented on pull request at %(date_or_age)s'),
289 ],
289 ],
290 }
290 }
291
291
292 templates = _map[notification.type_]
292 templates = _map[notification.type_]
293
293
294 if show_age:
294 if show_age:
295 template = templates[0]
295 template = templates[0]
296 date_or_age = h.age(notification.created_on)
296 date_or_age = h.age(notification.created_on)
297 if translate:
297 if translate:
298 date_or_age = translate(date_or_age)
298 date_or_age = translate(date_or_age)
299
299
300 if isinstance(date_or_age, TranslationString):
300 if isinstance(date_or_age, TranslationString):
301 date_or_age = date_or_age.interpolate()
301 date_or_age = date_or_age.interpolate()
302
302
303 else:
303 else:
304 template = templates[1]
304 template = templates[1]
305 date_or_age = h.format_date(notification.created_on)
305 date_or_age = h.format_date(notification.created_on)
306
306
307 return template % {
307 return template % {
308 'user': notification.created_by_user.username,
308 'user': notification.created_by_user.username,
309 'date_or_age': date_or_age,
309 'date_or_age': date_or_age,
310 }
310 }
311
311
312
312
313 # Templates for Titles, that could be overwritten by rcextensions
313 # Templates for Titles, that could be overwritten by rcextensions
314 # Title of email for pull-request update
314 # Title of email for pull-request update
315 EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = ''
315 EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = ''
316 # Title of email for request for pull request review
316 # Title of email for request for pull request review
317 EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = ''
317 EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = ''
318
318
319 # Title of email for general comment on pull request
319 # Title of email for general comment on pull request
320 EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = ''
320 EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = ''
321 # Title of email for general comment which includes status change on pull request
321 # Title of email for general comment which includes status change on pull request
322 EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
322 EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
323 # Title of email for inline comment on a file in pull request
323 # Title of email for inline comment on a file in pull request
324 EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = ''
324 EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = ''
325
325
326 # Title of email for general comment on commit
326 # Title of email for general comment on commit
327 EMAIL_COMMENT_SUBJECT_TEMPLATE = ''
327 EMAIL_COMMENT_SUBJECT_TEMPLATE = ''
328 # Title of email for general comment which includes status change on commit
328 # Title of email for general comment which includes status change on commit
329 EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
329 EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
330 # Title of email for inline comment on a file in commit
330 # Title of email for inline comment on a file in commit
331 EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = ''
331 EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = ''
332
332
333 import cssutils
334 # hijack css utils logger and replace with ours
335 log = logging.getLogger('rhodecode.cssutils.premailer')
336 cssutils.log.setLog(log)
337
333
338
334 class EmailNotificationModel(BaseModel):
339 class EmailNotificationModel(BaseModel):
335 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
340 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
336 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
341 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
337 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
342 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
338 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
343 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
339 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
344 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
340 TYPE_MAIN = Notification.TYPE_MESSAGE
345 TYPE_MAIN = Notification.TYPE_MESSAGE
341
346
342 TYPE_PASSWORD_RESET = 'password_reset'
347 TYPE_PASSWORD_RESET = 'password_reset'
343 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
348 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
344 TYPE_EMAIL_TEST = 'email_test'
349 TYPE_EMAIL_TEST = 'email_test'
345 TYPE_EMAIL_EXCEPTION = 'exception'
350 TYPE_EMAIL_EXCEPTION = 'exception'
346 TYPE_UPDATE_AVAILABLE = 'update_available'
351 TYPE_UPDATE_AVAILABLE = 'update_available'
347 TYPE_TEST = 'test'
352 TYPE_TEST = 'test'
348
353
349 email_types = {
354 email_types = {
350 TYPE_MAIN:
355 TYPE_MAIN:
351 'rhodecode:templates/email_templates/main.mako',
356 'rhodecode:templates/email_templates/main.mako',
352 TYPE_TEST:
357 TYPE_TEST:
353 'rhodecode:templates/email_templates/test.mako',
358 'rhodecode:templates/email_templates/test.mako',
354 TYPE_EMAIL_EXCEPTION:
359 TYPE_EMAIL_EXCEPTION:
355 'rhodecode:templates/email_templates/exception_tracker.mako',
360 'rhodecode:templates/email_templates/exception_tracker.mako',
356 TYPE_UPDATE_AVAILABLE:
361 TYPE_UPDATE_AVAILABLE:
357 'rhodecode:templates/email_templates/update_available.mako',
362 'rhodecode:templates/email_templates/update_available.mako',
358 TYPE_EMAIL_TEST:
363 TYPE_EMAIL_TEST:
359 'rhodecode:templates/email_templates/email_test.mako',
364 'rhodecode:templates/email_templates/email_test.mako',
360 TYPE_REGISTRATION:
365 TYPE_REGISTRATION:
361 'rhodecode:templates/email_templates/user_registration.mako',
366 'rhodecode:templates/email_templates/user_registration.mako',
362 TYPE_PASSWORD_RESET:
367 TYPE_PASSWORD_RESET:
363 'rhodecode:templates/email_templates/password_reset.mako',
368 'rhodecode:templates/email_templates/password_reset.mako',
364 TYPE_PASSWORD_RESET_CONFIRMATION:
369 TYPE_PASSWORD_RESET_CONFIRMATION:
365 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
370 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
366 TYPE_COMMIT_COMMENT:
371 TYPE_COMMIT_COMMENT:
367 'rhodecode:templates/email_templates/commit_comment.mako',
372 'rhodecode:templates/email_templates/commit_comment.mako',
368 TYPE_PULL_REQUEST:
373 TYPE_PULL_REQUEST:
369 'rhodecode:templates/email_templates/pull_request_review.mako',
374 'rhodecode:templates/email_templates/pull_request_review.mako',
370 TYPE_PULL_REQUEST_COMMENT:
375 TYPE_PULL_REQUEST_COMMENT:
371 'rhodecode:templates/email_templates/pull_request_comment.mako',
376 'rhodecode:templates/email_templates/pull_request_comment.mako',
372 TYPE_PULL_REQUEST_UPDATE:
377 TYPE_PULL_REQUEST_UPDATE:
373 'rhodecode:templates/email_templates/pull_request_update.mako',
378 'rhodecode:templates/email_templates/pull_request_update.mako',
374 }
379 }
375
380
376 premailer_instance = premailer.Premailer(
381 premailer_instance = premailer.Premailer()
377 cssutils_logging_level=logging.ERROR,
378 cssutils_logging_handler=logging.getLogger().handlers[0]
379 if logging.getLogger().handlers else None,
380 )
381
382
382 def __init__(self):
383 def __init__(self):
383 """
384 """
384 Example usage::
385 Example usage::
385
386
386 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
387 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
387 EmailNotificationModel.TYPE_TEST, **email_kwargs)
388 EmailNotificationModel.TYPE_TEST, **email_kwargs)
388
389
389 """
390 """
390 super(EmailNotificationModel, self).__init__()
391 super(EmailNotificationModel, self).__init__()
391 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
392 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
392
393
393 def _update_kwargs_for_render(self, kwargs):
394 def _update_kwargs_for_render(self, kwargs):
394 """
395 """
395 Inject params required for Mako rendering
396 Inject params required for Mako rendering
396
397
397 :param kwargs:
398 :param kwargs:
398 """
399 """
399
400
400 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
401 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
401 kwargs['rhodecode_version'] = rhodecode.__version__
402 kwargs['rhodecode_version'] = rhodecode.__version__
402 instance_url = h.route_url('home')
403 instance_url = h.route_url('home')
403 _kwargs = {
404 _kwargs = {
404 'instance_url': instance_url,
405 'instance_url': instance_url,
405 'whitespace_filter': self.whitespace_filter,
406 'whitespace_filter': self.whitespace_filter,
406 'email_pr_update_subject_template': EMAIL_PR_UPDATE_SUBJECT_TEMPLATE,
407 'email_pr_update_subject_template': EMAIL_PR_UPDATE_SUBJECT_TEMPLATE,
407 'email_pr_review_subject_template': EMAIL_PR_REVIEW_SUBJECT_TEMPLATE,
408 'email_pr_review_subject_template': EMAIL_PR_REVIEW_SUBJECT_TEMPLATE,
408 'email_pr_comment_subject_template': EMAIL_PR_COMMENT_SUBJECT_TEMPLATE,
409 'email_pr_comment_subject_template': EMAIL_PR_COMMENT_SUBJECT_TEMPLATE,
409 'email_pr_comment_status_change_subject_template': EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
410 'email_pr_comment_status_change_subject_template': EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
410 'email_pr_comment_file_subject_template': EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE,
411 'email_pr_comment_file_subject_template': EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE,
411 'email_comment_subject_template': EMAIL_COMMENT_SUBJECT_TEMPLATE,
412 'email_comment_subject_template': EMAIL_COMMENT_SUBJECT_TEMPLATE,
412 'email_comment_status_change_subject_template': EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
413 'email_comment_status_change_subject_template': EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
413 'email_comment_file_subject_template': EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE,
414 'email_comment_file_subject_template': EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE,
414 }
415 }
415 _kwargs.update(kwargs)
416 _kwargs.update(kwargs)
416 return _kwargs
417 return _kwargs
417
418
418 def whitespace_filter(self, text):
419 def whitespace_filter(self, text):
419 return text.replace('\n', '').replace('\t', '')
420 return text.replace('\n', '').replace('\t', '')
420
421
421 def get_renderer(self, type_, request):
422 def get_renderer(self, type_, request):
422 template_name = self.email_types[type_]
423 template_name = self.email_types[type_]
423 return request.get_partial_renderer(template_name)
424 return request.get_partial_renderer(template_name)
424
425
425 def render_email(self, type_, **kwargs):
426 def render_email(self, type_, **kwargs):
426 """
427 """
427 renders template for email, and returns a tuple of
428 renders template for email, and returns a tuple of
428 (subject, email_headers, email_html_body, email_plaintext_body)
429 (subject, email_headers, email_html_body, email_plaintext_body)
429 """
430 """
430 request = get_current_request()
431 request = get_current_request()
431
432
432 # translator and helpers inject
433 # translator and helpers inject
433 _kwargs = self._update_kwargs_for_render(kwargs)
434 _kwargs = self._update_kwargs_for_render(kwargs)
434 email_template = self.get_renderer(type_, request=request)
435 email_template = self.get_renderer(type_, request=request)
435 subject = email_template.render('subject', **_kwargs)
436 subject = email_template.render('subject', **_kwargs)
436
437
437 try:
438 try:
438 body_plaintext = email_template.render('body_plaintext', **_kwargs)
439 body_plaintext = email_template.render('body_plaintext', **_kwargs)
439 except AttributeError:
440 except AttributeError:
440 # it's not defined in template, ok we can skip it
441 # it's not defined in template, ok we can skip it
441 body_plaintext = ''
442 body_plaintext = ''
442
443
443 # render WHOLE template
444 # render WHOLE template
444 body = email_template.render(None, **_kwargs)
445 body = email_template.render(None, **_kwargs)
445
446
446 try:
447 try:
447 # Inline CSS styles and conversion
448 # Inline CSS styles and conversion
448 body = self.premailer_instance.transform(body)
449 body = self.premailer_instance.transform(body)
449 except Exception:
450 except Exception:
450 log.exception('Failed to parse body with premailer')
451 log.exception('Failed to parse body with premailer')
451 pass
452 pass
452
453
453 return subject, body, body_plaintext
454 return subject, body, body_plaintext
General Comments 0
You need to be logged in to leave comments. Login now