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