##// END OF EJS Templates
emails: fixed newlines in email templates that can break email sending code.
marcink -
r1728:49fb0cec default
parent child Browse files
Show More
@@ -1,374 +1,379 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons.i18n.translation import _, ungettext
30 from pylons.i18n.translation import _, ungettext
31 from sqlalchemy.sql.expression import false, true
31 from sqlalchemy.sql.expression import false, true
32 from mako import exceptions
32 from mako import exceptions
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import PartialRenderer
36 from rhodecode.lib.utils import PartialRenderer
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.model.db import Notification, User, UserNotification
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.translation import TranslationString
41 from rhodecode.translation import TranslationString
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class NotificationModel(BaseModel):
46 class NotificationModel(BaseModel):
47
47
48 cls = Notification
48 cls = Notification
49
49
50 def __get_notification(self, notification):
50 def __get_notification(self, notification):
51 if isinstance(notification, Notification):
51 if isinstance(notification, Notification):
52 return notification
52 return notification
53 elif isinstance(notification, (int, long)):
53 elif isinstance(notification, (int, long)):
54 return Notification.get(notification)
54 return Notification.get(notification)
55 else:
55 else:
56 if notification:
56 if notification:
57 raise Exception('notification must be int, long or Instance'
57 raise Exception('notification must be int, long or Instance'
58 ' of Notification got %s' % type(notification))
58 ' of Notification got %s' % type(notification))
59
59
60 def create(
60 def create(
61 self, created_by, notification_subject, notification_body,
61 self, created_by, notification_subject, notification_body,
62 notification_type=Notification.TYPE_MESSAGE, recipients=None,
62 notification_type=Notification.TYPE_MESSAGE, recipients=None,
63 mention_recipients=None, with_email=True, email_kwargs=None):
63 mention_recipients=None, with_email=True, email_kwargs=None):
64 """
64 """
65
65
66 Creates notification of given type
66 Creates notification of given type
67
67
68 :param created_by: int, str or User instance. User who created this
68 :param created_by: int, str or User instance. User who created this
69 notification
69 notification
70 :param notification_subject: subject of notification itself
70 :param notification_subject: subject of notification itself
71 :param notification_body: body of notification text
71 :param notification_body: body of notification text
72 :param notification_type: type of notification, based on that we
72 :param notification_type: type of notification, based on that we
73 pick templates
73 pick templates
74
74
75 :param recipients: list of int, str or User objects, when None
75 :param recipients: list of int, str or User objects, when None
76 is given send to all admins
76 is given send to all admins
77 :param mention_recipients: list of int, str or User objects,
77 :param mention_recipients: list of int, str or User objects,
78 that were mentioned
78 that were mentioned
79 :param with_email: send email with this notification
79 :param with_email: send email with this notification
80 :param email_kwargs: dict with arguments to generate email
80 :param email_kwargs: dict with arguments to generate email
81 """
81 """
82
82
83 from rhodecode.lib.celerylib import tasks, run_task
83 from rhodecode.lib.celerylib import tasks, run_task
84
84
85 if recipients and not getattr(recipients, '__iter__', False):
85 if recipients and not getattr(recipients, '__iter__', False):
86 raise Exception('recipients must be an iterable object')
86 raise Exception('recipients must be an iterable object')
87
87
88 created_by_obj = self._get_user(created_by)
88 created_by_obj = self._get_user(created_by)
89 # default MAIN body if not given
89 # default MAIN body if not given
90 email_kwargs = email_kwargs or {'body': notification_body}
90 email_kwargs = email_kwargs or {'body': notification_body}
91 mention_recipients = mention_recipients or set()
91 mention_recipients = mention_recipients or set()
92
92
93 if not created_by_obj:
93 if not created_by_obj:
94 raise Exception('unknown user %s' % created_by)
94 raise Exception('unknown user %s' % created_by)
95
95
96 if recipients is None:
96 if recipients is None:
97 # recipients is None means to all admins
97 # recipients is None means to all admins
98 recipients_objs = User.query().filter(User.admin == true()).all()
98 recipients_objs = User.query().filter(User.admin == true()).all()
99 log.debug('sending notifications %s to admins: %s',
99 log.debug('sending notifications %s to admins: %s',
100 notification_type, recipients_objs)
100 notification_type, recipients_objs)
101 else:
101 else:
102 recipients_objs = []
102 recipients_objs = []
103 for u in recipients:
103 for u in recipients:
104 obj = self._get_user(u)
104 obj = self._get_user(u)
105 if obj:
105 if obj:
106 recipients_objs.append(obj)
106 recipients_objs.append(obj)
107 else: # we didn't find this user, log the error and carry on
107 else: # we didn't find this user, log the error and carry on
108 log.error('cannot notify unknown user %r', u)
108 log.error('cannot notify unknown user %r', u)
109
109
110 recipients_objs = set(recipients_objs)
110 recipients_objs = set(recipients_objs)
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 notification = Notification.create(
119 notification = Notification.create(
120 created_by=created_by_obj, subject=notification_subject,
120 created_by=created_by_obj, subject=notification_subject,
121 body=notification_body, recipients=final_recipients,
121 body=notification_body, recipients=final_recipients,
122 type_=notification_type
122 type_=notification_type
123 )
123 )
124
124
125 if not with_email: # skip sending email, and just create notification
125 if not with_email: # skip sending email, and just create notification
126 return notification
126 return notification
127
127
128 # don't send email to person who created this comment
128 # don't send email to person who created this comment
129 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
129 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
130
130
131 # now notify all recipients in question
131 # now notify all recipients in question
132
132
133 for recipient in rec_objs.union(mention_recipients):
133 for recipient in rec_objs.union(mention_recipients):
134 # inject current recipient
134 # inject current recipient
135 email_kwargs['recipient'] = recipient
135 email_kwargs['recipient'] = recipient
136 email_kwargs['mention'] = recipient in mention_recipients
136 email_kwargs['mention'] = recipient in mention_recipients
137 (subject, headers, email_body,
137 (subject, headers, email_body,
138 email_body_plaintext) = EmailNotificationModel().render_email(
138 email_body_plaintext) = EmailNotificationModel().render_email(
139 notification_type, **email_kwargs)
139 notification_type, **email_kwargs)
140
140
141 log.debug(
141 log.debug(
142 'Creating notification email task for user:`%s`', recipient)
142 'Creating notification email task for user:`%s`', recipient)
143 task = run_task(
143 task = run_task(
144 tasks.send_email, recipient.email, subject,
144 tasks.send_email, recipient.email, subject,
145 email_body_plaintext, email_body)
145 email_body_plaintext, email_body)
146 log.debug('Created email task: %s', task)
146 log.debug('Created email task: %s', task)
147
147
148 return notification
148 return notification
149
149
150 def delete(self, user, notification):
150 def delete(self, user, notification):
151 # we don't want to remove actual notification just the assignment
151 # we don't want to remove actual notification just the assignment
152 try:
152 try:
153 notification = self.__get_notification(notification)
153 notification = self.__get_notification(notification)
154 user = self._get_user(user)
154 user = self._get_user(user)
155 if notification and user:
155 if notification and user:
156 obj = UserNotification.query()\
156 obj = UserNotification.query()\
157 .filter(UserNotification.user == user)\
157 .filter(UserNotification.user == user)\
158 .filter(UserNotification.notification == notification)\
158 .filter(UserNotification.notification == notification)\
159 .one()
159 .one()
160 Session().delete(obj)
160 Session().delete(obj)
161 return True
161 return True
162 except Exception:
162 except Exception:
163 log.error(traceback.format_exc())
163 log.error(traceback.format_exc())
164 raise
164 raise
165
165
166 def get_for_user(self, user, filter_=None):
166 def get_for_user(self, user, filter_=None):
167 """
167 """
168 Get mentions for given user, filter them if filter dict is given
168 Get mentions for given user, filter them if filter dict is given
169
169
170 :param user:
170 :param user:
171 :param filter:
171 :param filter:
172 """
172 """
173 user = self._get_user(user)
173 user = self._get_user(user)
174
174
175 q = UserNotification.query()\
175 q = UserNotification.query()\
176 .filter(UserNotification.user == user)\
176 .filter(UserNotification.user == user)\
177 .join((
177 .join((
178 Notification, UserNotification.notification_id ==
178 Notification, UserNotification.notification_id ==
179 Notification.notification_id))
179 Notification.notification_id))
180
180
181 if filter_:
181 if filter_:
182 q = q.filter(Notification.type_.in_(filter_))
182 q = q.filter(Notification.type_.in_(filter_))
183
183
184 return q.all()
184 return q.all()
185
185
186 def mark_read(self, user, notification):
186 def mark_read(self, user, notification):
187 try:
187 try:
188 notification = self.__get_notification(notification)
188 notification = self.__get_notification(notification)
189 user = self._get_user(user)
189 user = self._get_user(user)
190 if notification and user:
190 if notification and user:
191 obj = UserNotification.query()\
191 obj = UserNotification.query()\
192 .filter(UserNotification.user == user)\
192 .filter(UserNotification.user == user)\
193 .filter(UserNotification.notification == notification)\
193 .filter(UserNotification.notification == notification)\
194 .one()
194 .one()
195 obj.read = True
195 obj.read = True
196 Session().add(obj)
196 Session().add(obj)
197 return True
197 return True
198 except Exception:
198 except Exception:
199 log.error(traceback.format_exc())
199 log.error(traceback.format_exc())
200 raise
200 raise
201
201
202 def mark_all_read_for_user(self, user, filter_=None):
202 def mark_all_read_for_user(self, user, filter_=None):
203 user = self._get_user(user)
203 user = self._get_user(user)
204 q = UserNotification.query()\
204 q = UserNotification.query()\
205 .filter(UserNotification.user == user)\
205 .filter(UserNotification.user == user)\
206 .filter(UserNotification.read == false())\
206 .filter(UserNotification.read == false())\
207 .join((
207 .join((
208 Notification, UserNotification.notification_id ==
208 Notification, UserNotification.notification_id ==
209 Notification.notification_id))
209 Notification.notification_id))
210 if filter_:
210 if filter_:
211 q = q.filter(Notification.type_.in_(filter_))
211 q = q.filter(Notification.type_.in_(filter_))
212
212
213 # this is a little inefficient but sqlalchemy doesn't support
213 # this is a little inefficient but sqlalchemy doesn't support
214 # update on joined tables :(
214 # update on joined tables :(
215 for obj in q.all():
215 for obj in q.all():
216 obj.read = True
216 obj.read = True
217 Session().add(obj)
217 Session().add(obj)
218
218
219 def get_unread_cnt_for_user(self, user):
219 def get_unread_cnt_for_user(self, user):
220 user = self._get_user(user)
220 user = self._get_user(user)
221 return UserNotification.query()\
221 return UserNotification.query()\
222 .filter(UserNotification.read == false())\
222 .filter(UserNotification.read == false())\
223 .filter(UserNotification.user == user).count()
223 .filter(UserNotification.user == user).count()
224
224
225 def get_unread_for_user(self, user):
225 def get_unread_for_user(self, user):
226 user = self._get_user(user)
226 user = self._get_user(user)
227 return [x.notification for x in UserNotification.query()
227 return [x.notification for x in UserNotification.query()
228 .filter(UserNotification.read == false())
228 .filter(UserNotification.read == false())
229 .filter(UserNotification.user == user).all()]
229 .filter(UserNotification.user == user).all()]
230
230
231 def get_user_notification(self, user, notification):
231 def get_user_notification(self, user, notification):
232 user = self._get_user(user)
232 user = self._get_user(user)
233 notification = self.__get_notification(notification)
233 notification = self.__get_notification(notification)
234
234
235 return UserNotification.query()\
235 return UserNotification.query()\
236 .filter(UserNotification.notification == notification)\
236 .filter(UserNotification.notification == notification)\
237 .filter(UserNotification.user == user).scalar()
237 .filter(UserNotification.user == user).scalar()
238
238
239 def make_description(self, notification, show_age=True, translate=None):
239 def make_description(self, notification, show_age=True, translate=None):
240 """
240 """
241 Creates a human readable description based on properties
241 Creates a human readable description based on properties
242 of notification object
242 of notification object
243 """
243 """
244
244
245 _map = {
245 _map = {
246 notification.TYPE_CHANGESET_COMMENT: [
246 notification.TYPE_CHANGESET_COMMENT: [
247 _('%(user)s commented on commit %(date_or_age)s'),
247 _('%(user)s commented on commit %(date_or_age)s'),
248 _('%(user)s commented on commit at %(date_or_age)s'),
248 _('%(user)s commented on commit at %(date_or_age)s'),
249 ],
249 ],
250 notification.TYPE_MESSAGE: [
250 notification.TYPE_MESSAGE: [
251 _('%(user)s sent message %(date_or_age)s'),
251 _('%(user)s sent message %(date_or_age)s'),
252 _('%(user)s sent message at %(date_or_age)s'),
252 _('%(user)s sent message at %(date_or_age)s'),
253 ],
253 ],
254 notification.TYPE_MENTION: [
254 notification.TYPE_MENTION: [
255 _('%(user)s mentioned you %(date_or_age)s'),
255 _('%(user)s mentioned you %(date_or_age)s'),
256 _('%(user)s mentioned you at %(date_or_age)s'),
256 _('%(user)s mentioned you at %(date_or_age)s'),
257 ],
257 ],
258 notification.TYPE_REGISTRATION: [
258 notification.TYPE_REGISTRATION: [
259 _('%(user)s registered in RhodeCode %(date_or_age)s'),
259 _('%(user)s registered in RhodeCode %(date_or_age)s'),
260 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
260 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
261 ],
261 ],
262 notification.TYPE_PULL_REQUEST: [
262 notification.TYPE_PULL_REQUEST: [
263 _('%(user)s opened new pull request %(date_or_age)s'),
263 _('%(user)s opened new pull request %(date_or_age)s'),
264 _('%(user)s opened new pull request at %(date_or_age)s'),
264 _('%(user)s opened new pull request at %(date_or_age)s'),
265 ],
265 ],
266 notification.TYPE_PULL_REQUEST_COMMENT: [
266 notification.TYPE_PULL_REQUEST_COMMENT: [
267 _('%(user)s commented on pull request %(date_or_age)s'),
267 _('%(user)s commented on pull request %(date_or_age)s'),
268 _('%(user)s commented on pull request at %(date_or_age)s'),
268 _('%(user)s commented on pull request at %(date_or_age)s'),
269 ],
269 ],
270 }
270 }
271
271
272 templates = _map[notification.type_]
272 templates = _map[notification.type_]
273
273
274 if show_age:
274 if show_age:
275 template = templates[0]
275 template = templates[0]
276 date_or_age = h.age(notification.created_on)
276 date_or_age = h.age(notification.created_on)
277 if translate:
277 if translate:
278 date_or_age = translate(date_or_age)
278 date_or_age = translate(date_or_age)
279
279
280 if isinstance(date_or_age, TranslationString):
280 if isinstance(date_or_age, TranslationString):
281 date_or_age = date_or_age.interpolate()
281 date_or_age = date_or_age.interpolate()
282
282
283 else:
283 else:
284 template = templates[1]
284 template = templates[1]
285 date_or_age = h.format_date(notification.created_on)
285 date_or_age = h.format_date(notification.created_on)
286
286
287 return template % {
287 return template % {
288 'user': notification.created_by_user.username,
288 'user': notification.created_by_user.username,
289 'date_or_age': date_or_age,
289 'date_or_age': date_or_age,
290 }
290 }
291
291
292
292
293 class EmailNotificationModel(BaseModel):
293 class EmailNotificationModel(BaseModel):
294 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
294 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
295 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
295 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
296 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
296 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
297 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
297 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
298 TYPE_MAIN = Notification.TYPE_MESSAGE
298 TYPE_MAIN = Notification.TYPE_MESSAGE
299
299
300 TYPE_PASSWORD_RESET = 'password_reset'
300 TYPE_PASSWORD_RESET = 'password_reset'
301 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
301 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
302 TYPE_EMAIL_TEST = 'email_test'
302 TYPE_EMAIL_TEST = 'email_test'
303 TYPE_TEST = 'test'
303 TYPE_TEST = 'test'
304
304
305 email_types = {
305 email_types = {
306 TYPE_MAIN: 'email_templates/main.mako',
306 TYPE_MAIN: 'email_templates/main.mako',
307 TYPE_TEST: 'email_templates/test.mako',
307 TYPE_TEST: 'email_templates/test.mako',
308 TYPE_EMAIL_TEST: 'email_templates/email_test.mako',
308 TYPE_EMAIL_TEST: 'email_templates/email_test.mako',
309 TYPE_REGISTRATION: 'email_templates/user_registration.mako',
309 TYPE_REGISTRATION: 'email_templates/user_registration.mako',
310 TYPE_PASSWORD_RESET: 'email_templates/password_reset.mako',
310 TYPE_PASSWORD_RESET: 'email_templates/password_reset.mako',
311 TYPE_PASSWORD_RESET_CONFIRMATION: 'email_templates/password_reset_confirmation.mako',
311 TYPE_PASSWORD_RESET_CONFIRMATION: 'email_templates/password_reset_confirmation.mako',
312 TYPE_COMMIT_COMMENT: 'email_templates/commit_comment.mako',
312 TYPE_COMMIT_COMMENT: 'email_templates/commit_comment.mako',
313 TYPE_PULL_REQUEST: 'email_templates/pull_request_review.mako',
313 TYPE_PULL_REQUEST: 'email_templates/pull_request_review.mako',
314 TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.mako',
314 TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.mako',
315 }
315 }
316
316
317 def __init__(self):
317 def __init__(self):
318 """
318 """
319 Example usage::
319 Example usage::
320
320
321 (subject, headers, email_body,
321 (subject, headers, email_body,
322 email_body_plaintext) = EmailNotificationModel().render_email(
322 email_body_plaintext) = EmailNotificationModel().render_email(
323 EmailNotificationModel.TYPE_TEST, **email_kwargs)
323 EmailNotificationModel.TYPE_TEST, **email_kwargs)
324
324
325 """
325 """
326 super(EmailNotificationModel, self).__init__()
326 super(EmailNotificationModel, self).__init__()
327 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
327 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
328
328
329 def _update_kwargs_for_render(self, kwargs):
329 def _update_kwargs_for_render(self, kwargs):
330 """
330 """
331 Inject params required for Mako rendering
331 Inject params required for Mako rendering
332
332
333 :param kwargs:
333 :param kwargs:
334 """
334 """
335
335 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
336 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
336
337
337 _kwargs = {
338 _kwargs = {
338 'instance_url': h.url('home', qualified=True),
339 'instance_url': h.url('home', qualified=True),
340 'whitespace_filter': self.whitespace_filter
339 }
341 }
340 _kwargs.update(kwargs)
342 _kwargs.update(kwargs)
341 return _kwargs
343 return _kwargs
342
344
345 def whitespace_filter(self, text):
346 return text.replace('\n', '').replace('\t', '')
347
343 def get_renderer(self, type_):
348 def get_renderer(self, type_):
344 template_name = self.email_types[type_]
349 template_name = self.email_types[type_]
345 return PartialRenderer(template_name)
350 return PartialRenderer(template_name)
346
351
347 def render_email(self, type_, **kwargs):
352 def render_email(self, type_, **kwargs):
348 """
353 """
349 renders template for email, and returns a tuple of
354 renders template for email, and returns a tuple of
350 (subject, email_headers, email_html_body, email_plaintext_body)
355 (subject, email_headers, email_html_body, email_plaintext_body)
351 """
356 """
352 # translator and helpers inject
357 # translator and helpers inject
353 _kwargs = self._update_kwargs_for_render(kwargs)
358 _kwargs = self._update_kwargs_for_render(kwargs)
354
359
355 email_template = self.get_renderer(type_)
360 email_template = self.get_renderer(type_)
356
361
357 subject = email_template.render('subject', **_kwargs)
362 subject = email_template.render('subject', **_kwargs)
358
363
359 try:
364 try:
360 headers = email_template.render('headers', **_kwargs)
365 headers = email_template.render('headers', **_kwargs)
361 except AttributeError:
366 except AttributeError:
362 # it's not defined in template, ok we can skip it
367 # it's not defined in template, ok we can skip it
363 headers = ''
368 headers = ''
364
369
365 try:
370 try:
366 body_plaintext = email_template.render('body_plaintext', **_kwargs)
371 body_plaintext = email_template.render('body_plaintext', **_kwargs)
367 except AttributeError:
372 except AttributeError:
368 # it's not defined in template, ok we can skip it
373 # it's not defined in template, ok we can skip it
369 body_plaintext = ''
374 body_plaintext = ''
370
375
371 # render WHOLE template
376 # render WHOLE template
372 body = email_template.render(None, **_kwargs)
377 body = email_template.render(None, **_kwargs)
373
378
374 return subject, headers, body, body_plaintext
379 return subject, headers, body, body_plaintext
@@ -1,105 +1,105 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': h.person(user),
9 'user': h.person(user),
10 'repo_name': repo_name,
10 'repo_name': repo_name,
11 'commit_id': h.show_id(commit),
11 'commit_id': h.show_id(commit),
12 'status': status_change,
12 'status': status_change,
13 'comment_file': comment_file,
13 'comment_file': comment_file,
14 'comment_line': comment_line,
14 'comment_line': comment_line,
15 'comment_type': comment_type,
15 'comment_type': comment_type,
16 }
16 }
17 %>
17 %>
18 ${_('[mention]') if mention else ''} \
18 ${_('[mention]') if mention else ''} \
19
19
20 % if comment_file:
20 % if comment_file:
21 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s` (file: `%(comment_file)s`)') % data} ${_('in the %(repo_name)s repository') % data |n}
21 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s` (file: `%(comment_file)s`)') % data} ${_('in the %(repo_name)s repository') % data |n}
22 % else:
22 % else:
23 % if status_change:
23 % if status_change:
24 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s` (status: %(status)s)') % data |n} ${_('in the %(repo_name)s repository') % data |n}
24 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s` (status: %(status)s)') % data |n} ${_('in the %(repo_name)s repository') % data |n}
25 % else:
25 % else:
26 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s`') % data |n} ${_('in the %(repo_name)s repository') % data |n}
26 ${_('%(user)s left %(comment_type)s on commit `%(commit_id)s`') % data |n} ${_('in the %(repo_name)s repository') % data |n}
27 % endif
27 % endif
28 % endif
28 % endif
29
29
30 </%def>
30 </%def>
31
31
32 ## PLAINTEXT VERSION OF BODY
32 ## PLAINTEXT VERSION OF BODY
33 <%def name="body_plaintext()" filter="n,trim">
33 <%def name="body_plaintext()" filter="n,trim">
34 <%
34 <%
35 data = {
35 data = {
36 'user': h.person(user),
36 'user': h.person(user),
37 'repo_name': repo_name,
37 'repo_name': repo_name,
38 'commit_id': h.show_id(commit),
38 'commit_id': h.show_id(commit),
39 'status': status_change,
39 'status': status_change,
40 'comment_file': comment_file,
40 'comment_file': comment_file,
41 'comment_line': comment_line,
41 'comment_line': comment_line,
42 'comment_type': comment_type,
42 'comment_type': comment_type,
43 }
43 }
44 %>
44 %>
45 ${self.subject()}
45 ${self.subject()}
46
46
47 * ${_('Comment link')}: ${commit_comment_url}
47 * ${_('Comment link')}: ${commit_comment_url}
48
48
49 * ${_('Commit')}: ${h.show_id(commit)}
49 * ${_('Commit')}: ${h.show_id(commit)}
50
50
51 %if comment_file:
51 %if comment_file:
52 * ${_('File: %(comment_file)s on line %(comment_line)s') % data}
52 * ${_('File: %(comment_file)s on line %(comment_line)s') % data}
53 %endif
53 %endif
54
54
55 ---
55 ---
56
56
57 %if status_change:
57 %if status_change:
58 ${_('Commit status was changed to')}: *${status_change}*
58 ${_('Commit status was changed to')}: *${status_change}*
59 %endif
59 %endif
60
60
61 ${comment_body|n}
61 ${comment_body|n}
62
62
63 ${self.plaintext_footer()}
63 ${self.plaintext_footer()}
64 </%def>
64 </%def>
65
65
66
66
67 <%
67 <%
68 data = {
68 data = {
69 'user': h.person(user),
69 'user': h.person(user),
70 'repo': commit_target_repo,
70 'repo': commit_target_repo,
71 'repo_name': repo_name,
71 'repo_name': repo_name,
72 'commit_id': h.show_id(commit),
72 'commit_id': h.show_id(commit),
73 'comment_file': comment_file,
73 'comment_file': comment_file,
74 'comment_line': comment_line,
74 'comment_line': comment_line,
75 'comment_type': comment_type,
75 'comment_type': comment_type,
76 }
76 }
77 %>
77 %>
78 <table style="text-align:left;vertical-align:middle;">
78 <table style="text-align:left;vertical-align:middle;">
79 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
79 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
80
80
81 % if comment_file:
81 % if comment_file:
82 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s` (file:`%(comment_file)s`)') % data}</a> ${_('in the %(repo)s repository') % data |n}</h4>
82 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s` (file:`%(comment_file)s`)') % data}</a> ${_('in the %(repo)s repository') % data |n}</h4>
83 % else:
83 % else:
84 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s`') % data |n}</a> ${_('in the %(repo)s repository') % data |n}</h4>
84 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s`') % data |n}</a> ${_('in the %(repo)s repository') % data |n}</h4>
85 % endif
85 % endif
86 </td></tr>
86 </td></tr>
87
87
88 <tr><td style="padding-right:20px;padding-top:15px;">${_('Commit')}</td><td style="padding-top:15px;"><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${h.show_id(commit)}</a></td></tr>
88 <tr><td style="padding-right:20px;padding-top:15px;">${_('Commit')}</td><td style="padding-top:15px;"><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${h.show_id(commit)}</a></td></tr>
89 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td></tr>
89 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td></tr>
90
90
91 % if status_change:
91 % if status_change:
92 <tr><td style="padding-right:20px;">${_('Status')}</td>
92 <tr><td style="padding-right:20px;">${_('Status')}</td>
93 <td>${_('The commit status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}</td>
93 <td>${_('The commit status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}</td>
94 </tr>
94 </tr>
95 % endif
95 % endif
96 <tr>
96 <tr>
97 <td style="padding-right:20px;">
97 <td style="padding-right:20px;">
98 % if comment_type == 'todo':
98 % if comment_type == 'todo':
99 ${(_('TODO comment on line: %(comment_line)s') if comment_file else _('TODO comment')) % data}
99 ${(_('TODO comment on line: %(comment_line)s') if comment_file else _('TODO comment')) % data}
100 % else:
100 % else:
101 ${(_('Note comment on line: %(comment_line)s') if comment_file else _('Note comment')) % data}
101 ${(_('Note comment on line: %(comment_line)s') if comment_file else _('Note comment')) % data}
102 % endif
102 % endif
103 </td>
103 </td>
104 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td></tr>
104 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td></tr>
105 </table>
105 </table>
@@ -1,13 +1,13 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode test email: ${h.format_date(date)}
5 RhodeCode test email: ${h.format_date(date)}
6 </%def>
6 </%def>
7
7
8 ## plain text version of the email. Empty by default
8 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 Test Email from RhodeCode version: ${rhodecode_version}, sent by: ${user}
10 Test Email from RhodeCode version: ${rhodecode_version}, sent by: ${user}
11 </%def>
11 </%def>
12
12
13 ${body_plaintext()} No newline at end of file
13 ${body_plaintext()}
@@ -1,21 +1,21 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 </%def>
5 </%def>
6
6
7
7
8 ## plain text version of the email. Empty by default
8 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 ${body}
10 ${body}
11
11
12 ${self.plaintext_footer()}
12 ${self.plaintext_footer()}
13 </%def>
13 </%def>
14
14
15 ## BODY GOES BELOW
15 ## BODY GOES BELOW
16 <table style="text-align:left;vertical-align:top;">
16 <table style="text-align:left;vertical-align:top;">
17 <tr><td style="padding-right:20px;padding-top:15px;white-space:pre-wrap">${body}</td></tr>
17 <tr><td style="padding-right:20px;padding-top:15px;white-space:pre-wrap">${body}</td></tr>
18 </table>
18 </table>
19 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;display:block;color:#666666;text-decoration:none;" href="${instance_url}">
19 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;display:block;color:#666666;text-decoration:none;" href="${instance_url}">
20 ${self.plaintext_footer()}
20 ${self.plaintext_footer()}
21 </a></p> No newline at end of file
21 </a></p>
@@ -1,33 +1,33 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode Password reset
5 RhodeCode Password reset
6 </%def>
6 </%def>
7
7
8 ## plain text version of the email. Empty by default
8 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 Hi ${user.username},
10 Hi ${user.username},
11
11
12 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
12 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
13
13
14 *If you didn't do this, please contact your RhodeCode administrator.*
14 *If you didn't do this, please contact your RhodeCode administrator.*
15
15
16 You can continue, and generate new password by clicking following URL:
16 You can continue, and generate new password by clicking following URL:
17 ${password_reset_url}
17 ${password_reset_url}
18
18
19 This link will be active for 10 minutes.
19 This link will be active for 10 minutes.
20 ${self.plaintext_footer()}
20 ${self.plaintext_footer()}
21 </%def>
21 </%def>
22
22
23 ## BODY GOES BELOW
23 ## BODY GOES BELOW
24 <p>
24 <p>
25 Hello ${user.username},
25 Hello ${user.username},
26 </p><p>
26 </p><p>
27 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
27 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
28 <br/>
28 <br/>
29 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
29 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
30 </p><p>
30 </p><p>
31 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
31 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
32 This link will be active for 10 minutes.
32 This link will be active for 10 minutes.
33 </p>
33 </p>
@@ -1,29 +1,29 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 Your new RhodeCode password
5 Your new RhodeCode password
6 </%def>
6 </%def>
7
7
8 ## plain text version of the email. Empty by default
8 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 Hi ${user.username},
10 Hi ${user.username},
11
11
12 Below is your new access password for RhodeCode.
12 Below is your new access password for RhodeCode.
13
13
14 *If you didn't do this, please contact your RhodeCode administrator.*
14 *If you didn't do this, please contact your RhodeCode administrator.*
15
15
16 password: ${new_password}
16 password: ${new_password}
17
17
18 ${self.plaintext_footer()}
18 ${self.plaintext_footer()}
19 </%def>
19 </%def>
20
20
21 ## BODY GOES BELOW
21 ## BODY GOES BELOW
22 <p>
22 <p>
23 Hello ${user.username},
23 Hello ${user.username},
24 </p><p>
24 </p><p>
25 Below is your new access password for RhodeCode.
25 Below is your new access password for RhodeCode.
26 <br/>
26 <br/>
27 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
27 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
28 </p>
28 </p>
29 <p>password: <pre>${new_password}</pre>
29 <p>password: <pre>${new_password}</pre>
@@ -1,114 +1,114 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': h.person(user),
9 'user': h.person(user),
10 'pr_title': pull_request.title,
10 'pr_title': pull_request.title,
11 'pr_id': pull_request.pull_request_id,
11 'pr_id': pull_request.pull_request_id,
12 'status': status_change,
12 'status': status_change,
13 'comment_file': comment_file,
13 'comment_file': comment_file,
14 'comment_line': comment_line,
14 'comment_line': comment_line,
15 'comment_type': comment_type,
15 'comment_type': comment_type,
16 }
16 }
17 %>
17 %>
18
18
19 ${_('[mention]') if mention else ''} \
19 ${_('[mention]') if mention else ''} \
20
20
21 % if comment_file:
21 % if comment_file:
22 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s" (file: `%(comment_file)s`)') % data |n}
22 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s" (file: `%(comment_file)s`)') % data |n}
23 % else:
23 % else:
24 % if status_change:
24 % if status_change:
25 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s" (status: %(status)s)') % data |n}
25 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s" (status: %(status)s)') % data |n}
26 % else:
26 % else:
27 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s"') % data |n}
27 ${_('%(user)s left %(comment_type)s on pull request #%(pr_id)s "%(pr_title)s"') % data |n}
28 % endif
28 % endif
29 % endif
29 % endif
30 </%def>
30 </%def>
31
31
32 ## PLAINTEXT VERSION OF BODY
32 ## PLAINTEXT VERSION OF BODY
33 <%def name="body_plaintext()" filter="n,trim">
33 <%def name="body_plaintext()" filter="n,trim">
34 <%
34 <%
35 data = {
35 data = {
36 'user': h.person(user),
36 'user': h.person(user),
37 'pr_title': pull_request.title,
37 'pr_title': pull_request.title,
38 'pr_id': pull_request.pull_request_id,
38 'pr_id': pull_request.pull_request_id,
39 'status': status_change,
39 'status': status_change,
40 'comment_file': comment_file,
40 'comment_file': comment_file,
41 'comment_line': comment_line,
41 'comment_line': comment_line,
42 'comment_type': comment_type,
42 'comment_type': comment_type,
43 }
43 }
44 %>
44 %>
45 ${self.subject()}
45 ${self.subject()}
46
46
47 * ${_('Comment link')}: ${pr_comment_url}
47 * ${_('Comment link')}: ${pr_comment_url}
48
48
49 * ${_('Source repository')}: ${pr_source_repo_url}
49 * ${_('Source repository')}: ${pr_source_repo_url}
50
50
51 %if comment_file:
51 %if comment_file:
52 * ${_('File: %(comment_file)s on line %(comment_line)s') % {'comment_file': comment_file, 'comment_line': comment_line}}
52 * ${_('File: %(comment_file)s on line %(comment_line)s') % {'comment_file': comment_file, 'comment_line': comment_line}}
53 %endif
53 %endif
54
54
55 ---
55 ---
56
56
57 %if status_change and not closing_pr:
57 %if status_change and not closing_pr:
58 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s*') % data}
58 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s*') % data}
59 %elif status_change and closing_pr:
59 %elif status_change and closing_pr:
60 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s and closed*') % data}
60 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s and closed*') % data}
61 %endif
61 %endif
62
62
63 ${comment_body|n}
63 ${comment_body|n}
64
64
65 ${self.plaintext_footer()}
65 ${self.plaintext_footer()}
66 </%def>
66 </%def>
67
67
68
68
69 <%
69 <%
70 data = {
70 data = {
71 'user': h.person(user),
71 'user': h.person(user),
72 'pr_title': pull_request.title,
72 'pr_title': pull_request.title,
73 'pr_id': pull_request.pull_request_id,
73 'pr_id': pull_request.pull_request_id,
74 'status': status_change,
74 'status': status_change,
75 'comment_file': comment_file,
75 'comment_file': comment_file,
76 'comment_line': comment_line,
76 'comment_line': comment_line,
77 'comment_type': comment_type,
77 'comment_type': comment_type,
78 }
78 }
79 %>
79 %>
80 <table style="text-align:left;vertical-align:middle;">
80 <table style="text-align:left;vertical-align:middle;">
81 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
81 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
82
82
83 % if comment_file:
83 % if comment_file:
84 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s" (file:`%(comment_file)s`)') % data |n}</a></h4>
84 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s" (file:`%(comment_file)s`)') % data |n}</a></h4>
85 % else:
85 % else:
86 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s"') % data |n}</a></h4>
86 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s"') % data |n}</a></h4>
87 % endif
87 % endif
88
88
89 </td></tr>
89 </td></tr>
90 <tr><td style="padding-right:20px;padding-top:15px;">${_('Source')}</td><td style="padding-top:15px;"><a style="color:#427cc9;text-decoration:none;cursor:pointer" href="${pr_source_repo_url}">${pr_source_repo.repo_name}</a></td></tr>
90 <tr><td style="padding-right:20px;padding-top:15px;">${_('Source')}</td><td style="padding-top:15px;"><a style="color:#427cc9;text-decoration:none;cursor:pointer" href="${pr_source_repo_url}">${pr_source_repo.repo_name}</a></td></tr>
91
91
92 % if status_change:
92 % if status_change:
93 <tr>
93 <tr>
94 <td style="padding-right:20px;">${_('Status')}</td>
94 <td style="padding-right:20px;">${_('Status')}</td>
95 <td>
95 <td>
96 % if closing_pr:
96 % if closing_pr:
97 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
97 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
98 % else:
98 % else:
99 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
99 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
100 % endif
100 % endif
101 </td>
101 </td>
102 </tr>
102 </tr>
103 % endif
103 % endif
104 <tr>
104 <tr>
105 <td style="padding-right:20px;">
105 <td style="padding-right:20px;">
106 % if comment_type == 'todo':
106 % if comment_type == 'todo':
107 ${(_('TODO comment on line: %(comment_line)s') if comment_file else _('TODO comment')) % data}
107 ${(_('TODO comment on line: %(comment_line)s') if comment_file else _('TODO comment')) % data}
108 % else:
108 % else:
109 ${(_('Note comment on line: %(comment_line)s') if comment_file else _('Note comment')) % data}
109 ${(_('Note comment on line: %(comment_line)s') if comment_file else _('Note comment')) % data}
110 % endif
110 % endif
111 </td>
111 </td>
112 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td>
112 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td>
113 </tr>
113 </tr>
114 </table>
114 </table>
@@ -1,85 +1,85 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 <%def name="subject()" filter="n,trim">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%
6 <%
7 data = {
7 data = {
8 'user': h.person(user),
8 'user': h.person(user),
9 'pr_id': pull_request.pull_request_id,
9 'pr_id': pull_request.pull_request_id,
10 'pr_title': pull_request.title,
10 'pr_title': pull_request.title,
11 }
11 }
12 %>
12 %>
13
13
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
15 </%def>
15 </%def>
16
16
17
17
18 <%def name="body_plaintext()" filter="n,trim">
18 <%def name="body_plaintext()" filter="n,trim">
19 <%
19 <%
20 data = {
20 data = {
21 'user': h.person(user),
21 'user': h.person(user),
22 'pr_id': pull_request.pull_request_id,
22 'pr_id': pull_request.pull_request_id,
23 'pr_title': pull_request.title,
23 'pr_title': pull_request.title,
24 'source_ref_type': pull_request.source_ref_parts.type,
24 'source_ref_type': pull_request.source_ref_parts.type,
25 'source_ref_name': pull_request.source_ref_parts.name,
25 'source_ref_name': pull_request.source_ref_parts.name,
26 'target_ref_type': pull_request.target_ref_parts.type,
26 'target_ref_type': pull_request.target_ref_parts.type,
27 'target_ref_name': pull_request.target_ref_parts.name,
27 'target_ref_name': pull_request.target_ref_parts.name,
28 'repo_url': pull_request_source_repo_url
28 'repo_url': pull_request_source_repo_url
29 }
29 }
30 %>
30 %>
31 ${self.subject()}
31 ${self.subject()}
32
32
33
33
34 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % data)}
34 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % data)}
35
35
36
36
37 * ${_('Link')}: ${pull_request_url}
37 * ${_('Link')}: ${pull_request_url}
38
38
39 * ${_('Title')}: ${pull_request.title}
39 * ${_('Title')}: ${pull_request.title}
40
40
41 * ${_('Description')}:
41 * ${_('Description')}:
42
42
43 ${pull_request.description}
43 ${pull_request.description}
44
44
45
45
46 * ${ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
46 * ${ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
47
47
48 % for commit_id, message in pull_request_commits:
48 % for commit_id, message in pull_request_commits:
49 - ${h.short_id(commit_id)}
49 - ${h.short_id(commit_id)}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
51
51
52 % endfor
52 % endfor
53
53
54 ${self.plaintext_footer()}
54 ${self.plaintext_footer()}
55 </%def>
55 </%def>
56 <%
56 <%
57 data = {
57 data = {
58 'user': h.person(user),
58 'user': h.person(user),
59 'pr_id': pull_request.pull_request_id,
59 'pr_id': pull_request.pull_request_id,
60 'pr_title': pull_request.title,
60 'pr_title': pull_request.title,
61 'source_ref_type': pull_request.source_ref_parts.type,
61 'source_ref_type': pull_request.source_ref_parts.type,
62 'source_ref_name': pull_request.source_ref_parts.name,
62 'source_ref_name': pull_request.source_ref_parts.name,
63 'target_ref_type': pull_request.target_ref_parts.type,
63 'target_ref_type': pull_request.target_ref_parts.type,
64 'target_ref_name': pull_request.target_ref_parts.name,
64 'target_ref_name': pull_request.target_ref_parts.name,
65 'repo_url': pull_request_source_repo_url,
65 'repo_url': pull_request_source_repo_url,
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
67 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url)
67 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url)
68 }
68 }
69 %>
69 %>
70 <table style="text-align:left;vertical-align:middle;">
70 <table style="text-align:left;vertical-align:middle;">
71 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${pull_request_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % data }</a></h4></td></tr>
71 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${pull_request_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % data }</a></h4></td></tr>
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
73 <tr><td style="padding-right:20px;">${_('Source')}</td><td>${base.tag_button(pull_request.source_ref_parts.name)} ${h.literal(_('%(source_ref_type)s of %(source_repo_url)s') % data)}</td></tr>
73 <tr><td style="padding-right:20px;">${_('Source')}</td><td>${base.tag_button(pull_request.source_ref_parts.name)} ${h.literal(_('%(source_ref_type)s of %(source_repo_url)s') % data)}</td></tr>
74 <tr><td style="padding-right:20px;">${_('Target')}</td><td>${base.tag_button(pull_request.target_ref_parts.name)} ${h.literal(_('%(target_ref_type)s of %(target_repo_url)s') % data)}</td></tr>
74 <tr><td style="padding-right:20px;">${_('Target')}</td><td>${base.tag_button(pull_request.target_ref_parts.name)} ${h.literal(_('%(target_ref_type)s of %(target_repo_url)s') % data)}</td></tr>
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
76 <tr><td style="padding-right:20px;">${ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
76 <tr><td style="padding-right:20px;">${ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
78 % for commit_id, message in pull_request_commits:
78 % for commit_id, message in pull_request_commits:
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
81 </li>
81 </li>
82 % endfor
82 % endfor
83 </ol></td>
83 </ol></td>
84 </tr>
84 </tr>
85 </table>
85 </table>
@@ -1,21 +1,21 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 Test "Subject" ${_('hello "world"')|n}
5 Test "Subject" ${_('hello "world"')|n}
6 </%def>
6 </%def>
7
7
8 <%def name="headers()" filter="n,trim">
8 <%def name="headers()" filter="n,trim">
9 X=Y
9 X=Y
10 </%def>
10 </%def>
11
11
12 ## plain text version of the email. Empty by default
12 ## plain text version of the email. Empty by default
13 <%def name="body_plaintext()" filter="n,trim">
13 <%def name="body_plaintext()" filter="n,trim">
14 Email Plaintext Body
14 Email Plaintext Body
15 </%def>
15 </%def>
16
16
17 ## BODY GOES BELOW
17 ## BODY GOES BELOW
18 <b>Email Body</b>
18 <b>Email Body</b>
19
19
20 ${h.short_id('0' * 40)}
20 ${h.short_id('0' * 40)}
21 ${_('Translation')} No newline at end of file
21 ${_('Translation')}
@@ -1,27 +1,27 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode new user registration: ${user.username}
5 RhodeCode new user registration: ${user.username}
6 </%def>
6 </%def>
7
7
8 <%def name="body_plaintext()" filter="n,trim">
8 <%def name="body_plaintext()" filter="n,trim">
9
9
10 A new user `${user.username}` has registered on ${h.format_date(date)}
10 A new user `${user.username}` has registered on ${h.format_date(date)}
11
11
12 - Username: ${user.username}
12 - Username: ${user.username}
13 - Full Name: ${user.firstname} ${user.lastname}
13 - Full Name: ${user.firstname} ${user.lastname}
14 - Email: ${user.email}
14 - Email: ${user.email}
15 - Profile link: ${h.route_path('user_profile', username=user.username, qualified=True)}
15 - Profile link: ${h.route_path('user_profile', username=user.username, qualified=True)}
16
16
17 ${self.plaintext_footer()}
17 ${self.plaintext_footer()}
18 </%def>
18 </%def>
19
19
20 ## BODY GOES BELOW
20 ## BODY GOES BELOW
21 <table style="text-align:left;vertical-align:middle;">
21 <table style="text-align:left;vertical-align:middle;">
22 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_url('user_profile', username=user.username)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
22 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_url('user_profile', username=user.username)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
23 <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
23 <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr>
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr>
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
26 <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_url('user_profile', username=user.username)}">${h.route_url('user_profile', username=user.username)}</a></td></tr>
26 <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_url('user_profile', username=user.username)}">${h.route_url('user_profile', username=user.username)}</a></td></tr>
27 </table> No newline at end of file
27 </table>
@@ -1,68 +1,123 b''
1 import collections
1 import collections
2
2
3 import pytest
3 import pytest
4
4
5 from rhodecode.lib.utils import PartialRenderer
5 from rhodecode.lib.utils import PartialRenderer
6 from rhodecode.lib.utils2 import AttributeDict
6 from rhodecode.model.notification import EmailNotificationModel
7 from rhodecode.model.notification import EmailNotificationModel
7
8
8
9
9 def test_get_template_obj(pylonsapp):
10 def test_get_template_obj(pylonsapp):
10 template = EmailNotificationModel().get_renderer(
11 template = EmailNotificationModel().get_renderer(
11 EmailNotificationModel.TYPE_TEST)
12 EmailNotificationModel.TYPE_TEST)
12 assert isinstance(template, PartialRenderer)
13 assert isinstance(template, PartialRenderer)
13
14
14
15
15 def test_render_email(pylonsapp):
16 def test_render_email(pylonsapp):
16 kwargs = {}
17 kwargs = {}
17 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
18 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
18 EmailNotificationModel.TYPE_TEST, **kwargs)
19 EmailNotificationModel.TYPE_TEST, **kwargs)
19
20
20 # subject
21 # subject
21 assert subject == 'Test "Subject" hello "world"'
22 assert subject == 'Test "Subject" hello "world"'
22
23
23 # headers
24 # headers
24 assert headers == 'X=Y'
25 assert headers == 'X=Y'
25
26
26 # body plaintext
27 # body plaintext
27 assert body_plaintext == 'Email Plaintext Body'
28 assert body_plaintext == 'Email Plaintext Body'
28
29
29 # body
30 # body
30 assert 'This is a notification ' \
31 assert 'This is a notification ' \
31 'from RhodeCode. http://test.example.com:80/' in body
32 'from RhodeCode. http://test.example.com:80/' in body
32 assert 'Email Body' in body
33 assert 'Email Body' in body
33
34
34
35
35 def test_render_pr_email(pylonsapp, user_admin):
36 def test_render_pr_email(pylonsapp, user_admin):
36
37
37 ref = collections.namedtuple('Ref',
38 ref = collections.namedtuple('Ref',
38 'name, type')(
39 'name, type')(
39 'fxies123', 'book'
40 'fxies123', 'book'
40 )
41 )
41
42
42 pr = collections.namedtuple('PullRequest',
43 pr = collections.namedtuple('PullRequest',
43 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
44 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
44 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
45 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
45
46
46 source_repo = target_repo = collections.namedtuple('Repo',
47 source_repo = target_repo = collections.namedtuple('Repo',
47 'type, repo_name')(
48 'type, repo_name')(
48 'hg', 'pull_request_1')
49 'hg', 'pull_request_1')
49
50
50 kwargs = {
51 kwargs = {
51 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
52 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
52 'pull_request': pr,
53 'pull_request': pr,
53 'pull_request_commits': [],
54 'pull_request_commits': [],
54
55
55 'pull_request_target_repo': target_repo,
56 'pull_request_target_repo': target_repo,
56 'pull_request_target_repo_url': 'x',
57 'pull_request_target_repo_url': 'x',
57
58
58 'pull_request_source_repo': source_repo,
59 'pull_request_source_repo': source_repo,
59 'pull_request_source_repo_url': 'x',
60 'pull_request_source_repo_url': 'x',
60
61
61 'pull_request_url': 'http://localhost/pr1',
62 'pull_request_url': 'http://localhost/pr1',
62 }
63 }
63
64
64 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
65 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
65 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
66 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
66
67
67 # subject
68 # subject
68 assert subject == 'Marcin Kuzminski wants you to review pull request #200: "Example Pull Request"'
69 assert subject == 'Marcin Kuzminski wants you to review pull request #200: "Example Pull Request"'
70
71
72 @pytest.mark.parametrize('mention', [
73 True,
74 False
75 ])
76 @pytest.mark.parametrize('email_type', [
77 EmailNotificationModel.TYPE_COMMIT_COMMENT,
78 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
79 ])
80 def test_render_comment_subject_no_newlines(pylonsapp, mention, email_type):
81 ref = collections.namedtuple('Ref',
82 'name, type')(
83 'fxies123', 'book'
84 )
85
86 pr = collections.namedtuple('PullRequest',
87 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
88 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
89
90 source_repo = target_repo = collections.namedtuple('Repo',
91 'type, repo_name')(
92 'hg', 'pull_request_1')
93
94 kwargs = {
95 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
96 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
97 'status_change': 'approved',
98 'commit_target_repo': AttributeDict(),
99 'repo_name': 'test-repo',
100 'comment_file': 'test-file.py',
101 'comment_line': 'n100',
102 'comment_type': 'note',
103 'commit_comment_url': 'http://comment-url',
104 'instance_url': 'http://rc-instance',
105 'comment_body': 'hello world',
106 'mention': mention,
107
108 'pr_comment_url': 'http://comment-url',
109 'pr_source_repo': AttributeDict(repo_name='foobar'),
110 'pr_source_repo_url': 'http://soirce-repo/url',
111 'pull_request': pr,
112 'pull_request_commits': [],
113
114 'pull_request_target_repo': target_repo,
115 'pull_request_target_repo_url': 'x',
116
117 'pull_request_source_repo': source_repo,
118 'pull_request_source_repo_url': 'x',
119 }
120 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
121 email_type, **kwargs)
122
123 assert '\n' not in subject
General Comments 0
You need to be logged in to leave comments. Login now