##// END OF EJS Templates
emails: added logic to allow overwriting the default email titles via rcextensions.
marcink -
r4448:824dc51f default
parent child Browse files
Show More
@@ -1,187 +1,203 b''
1 # This code allows override the integrations templates.
2 # Put this into the __init__.py file of rcextensions to override the templates
1 # Below code examples allows override the integrations templates, or email titles.
2 # Append selected parts at the end of the __init__.py file of rcextensions directory
3 # to override the templates
3 4
4 5
5 6 # EMAIL Integration
6 7 from rhodecode.integrations import email
7 8 email.REPO_PUSH_TEMPLATE_HTML = email.Template('''
8 9 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
9 10 <html xmlns="http://www.w3.org/1999/xhtml">
10 11 <head>
11 12 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
12 13 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
13 14 <title>${subject}</title>
14 15 <style type="text/css">
15 16 /* Based on The MailChimp Reset INLINE: Yes. */
16 17 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
17 18 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
18 19 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
19 20 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
20 21 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
21 22 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
22 23 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
23 24 /* End reset */
24 25
25 26 /* defaults for images*/
26 27 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
27 28 a img {border:none;}
28 29 .image_fix {display:block;}
29 30
30 31 body {line-height:1.2em;}
31 32 p {margin: 0 0 20px;}
32 33 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
33 34 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
34 35 a:focus {outline:none;}
35 36 a:hover {color: #305b91;}
36 37 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
37 38 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
38 39 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
39 40 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
40 41 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
41 42 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
42 43 input:focus {outline: 1px solid #979797}
43 44 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
44 45 /* Put your iPhone 4g styles in here */
45 46 }
46 47
47 48 /* Android targeting */
48 49 @media only screen and (-webkit-device-pixel-ratio:.75){
49 50 /* Put CSS for low density (ldpi) Android layouts in here */
50 51 }
51 52 @media only screen and (-webkit-device-pixel-ratio:1){
52 53 /* Put CSS for medium density (mdpi) Android layouts in here */
53 54 }
54 55 @media only screen and (-webkit-device-pixel-ratio:1.5){
55 56 /* Put CSS for high density (hdpi) Android layouts in here */
56 57 }
57 58 /* end Android targeting */
58 59
59 60 </style>
60 61
61 62 <!-- Targeting Windows Mobile -->
62 63 <!--[if IEMobile 7]>
63 64 <style type="text/css">
64 65
65 66 </style>
66 67 <![endif]-->
67 68
68 69 <!--[if gte mso 9]>
69 70 <style>
70 71 /* Target Outlook 2007 and 2010 */
71 72 </style>
72 73 <![endif]-->
73 74 </head>
74 75 <body>
75 76 <!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
76 77 <table cellpadding="0" cellspacing="0" border="0" id="backgroundTable" align="left" style="margin:1%;width:97%;padding:0;font-family:sans-serif;font-weight:100;border:1px solid #dbd9da">
77 78 <tr>
78 79 <td valign="top" style="padding:0;">
79 80 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
80 81 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
81 82 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
82 83 ${'RhodeCode'}
83 84 </a>
84 85 </td></tr>
85 86 <tr>
86 87 <td style="padding:15px;" valign="top">
87 88 % if data['push']['commits']:
88 89 % for commit in data['push']['commits']:
89 90 <a href="${commit['url']}">${commit['short_id']}</a> by ${commit['author']} at ${commit['date']} <br/>
90 91 ${commit['message_html']} <br/>
91 92 <br/>
92 93 % endfor
93 94 % else:
94 95 No commit data
95 96 % endif
96 97 </td>
97 98 </tr>
98 99 </table>
99 100 </td>
100 101 </tr>
101 102 </table>
102 103 <!-- End of wrapper table -->
103 104 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;color:#666666;text-decoration:none;" href="${instance_url}">
104 105 ${'This is a notification from RhodeCode. %(instance_url)s' % {'instance_url': instance_url}}
105 106 </a></p>
106 107 </body>
107 108 </html>
108 109 ''')
109 110
110 111
111 112 # JIRA Integration (EE ONLY)
112 113 # available variables:
113 114 # url, short_id ,author
114 115 # branch, commit_message
115 116 # commit (dict data for commit)
116 117 from rc_integrations import jira_tracker
117 118
118 119 # used for references issues without transition, e.g `This ticket references PROJ-123`
119 120 jira_tracker.COMMENT_TEMPLATE_COMMIT = jira_tracker.Template('''
120 121 Commit `${short_id}` by ${author} on `${branch}` branch references this issue. \n
121 122 ${url}\n
122 123
123 124 ## MODIFICATION add custom COMMIT message to the comment
124 125 ${commit['message']}
125 126 ''')
126 127
127 128 # used when there's a transition, e.g referenced issues status goes from
128 129 # open to resolved this is used in correlation with something like `closes PROJ-123`
129 130 jira_tracker.COMMENT_TEMPLATE_COMMIT_WITH_STATUS = jira_tracker.Template('''
130 131 Commit `${short_id}` by ${author} on `${branch}` branch changed this issue. \n
131 132 '{url}\n
132 133
133 134 ## MODIFICATION add custom COMMIT message to the comment
134 135 ${commit['message']}
135 136 ''')
136 137
137 138 jira_tracker.COMMENT_TEMPLATE_PULL_REQUEST = jira_tracker.Template('''
138 139 ${action} by ${author} (status: ${status}). \n
139 140 pull-request: ${url}
140 141 ''')
141 142
142 143
143 144 # REDMINE (EE ONLY)
144 145 # available variables:
145 146 # url, short_id ,author
146 147 # branch, commit_message
147 148 # commit (dict data for commit)
148 149 from rc_integrations import redmine_tracker
149 150
150 151 # used for references issues without transition, e.g `This ticket references #123`
151 152 redmine_tracker.COMMENT_TEMPLATE_COMMIT = redmine_tracker.Template('''
152 153 Commit `${short_id}` by ${author} on `${branch}` branch references this issue. \n
153 154 commit: ${url}\n
154 155
155 156 ## MODIFICATION add custom COMMIT message to the comment
156 157 message:
157 158 ```
158 159 ${commit['message']}
159 160 ```
160 161
161 162 ''')
162 163
163 164 # used when there's a transition, e.g referenced issues status goes from
164 165 # open to resolved this is used in correlation with something like `closes #123`
165 166 redmine_tracker.COMMENT_TEMPLATE_COMMIT_WITH_STATUS = redmine_tracker.Template('''
166 167 Commit `${short_id}` by ${author} on `${branch}` branch changed this issue. \n
167 168 commit: ${url}\n
168 169
169 170 ## MODIFICATION add custom COMMIT message to the comment
170 171 message:
171 172 ```
172 173 ${commit['message']}
173 174 ```
174 175
175 176 ''')
176 177
177 178 redmine_tracker.COMMENT_TEMPLATE_PULL_REQUEST = redmine_tracker.Template('''
178 179 ${action} by ${author} (status: ${status}). \n'
179 180 ${url}\n
180 181
181 182 ## MODIFICATION add custom COMMIT message to the comment
182 183 message:
183 184 ```
184 185 ${commit['message']}
185 186 ```
186 187
187 188 ''')
189
190
191 # Example to modify emails default title
192 from rhodecode.model import notification
193
194 notification.EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = '{updating_user} updated pull request. !{pr_id}: "{pr_title}"'
195 notification.EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = '{user} requested a pull request review. !{pr_id}: "{pr_title}"'
196
197 notification.EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = '{mention_prefix}{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"'
198 notification.EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '{mention_prefix}[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"'
199 notification.EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = '{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"'
200
201 notification.EMAIL_COMMENT_SUBJECT_TEMPLATE = '{mention_prefix}{user} left a {comment_type} on commit `{commit_id}`'
202 notification.EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '{mention_prefix}[status: {status}] {user} left a {comment_type} on commit `{commit_id}`'
203 notification.EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = '{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`'
@@ -1,406 +1,427 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Model for notifications
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import premailer
30 30 from pyramid.threadlocal import get_current_request
31 31 from sqlalchemy.sql.expression import false, true
32 32
33 33 import rhodecode
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import Notification, User, UserNotification
37 37 from rhodecode.model.meta import Session
38 38 from rhodecode.translation import TranslationString
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 class NotificationModel(BaseModel):
44 44
45 45 cls = Notification
46 46
47 47 def __get_notification(self, notification):
48 48 if isinstance(notification, Notification):
49 49 return notification
50 50 elif isinstance(notification, (int, long)):
51 51 return Notification.get(notification)
52 52 else:
53 53 if notification:
54 54 raise Exception('notification must be int, long or Instance'
55 55 ' of Notification got %s' % type(notification))
56 56
57 57 def create(
58 58 self, created_by, notification_subject, notification_body,
59 59 notification_type=Notification.TYPE_MESSAGE, recipients=None,
60 60 mention_recipients=None, with_email=True, email_kwargs=None):
61 61 """
62 62
63 63 Creates notification of given type
64 64
65 65 :param created_by: int, str or User instance. User who created this
66 66 notification
67 67 :param notification_subject: subject of notification itself
68 68 :param notification_body: body of notification text
69 69 :param notification_type: type of notification, based on that we
70 70 pick templates
71 71
72 72 :param recipients: list of int, str or User objects, when None
73 73 is given send to all admins
74 74 :param mention_recipients: list of int, str or User objects,
75 75 that were mentioned
76 76 :param with_email: send email with this notification
77 77 :param email_kwargs: dict with arguments to generate email
78 78 """
79 79
80 80 from rhodecode.lib.celerylib import tasks, run_task
81 81
82 82 if recipients and not getattr(recipients, '__iter__', False):
83 83 raise Exception('recipients must be an iterable object')
84 84
85 85 created_by_obj = self._get_user(created_by)
86 86 # default MAIN body if not given
87 87 email_kwargs = email_kwargs or {'body': notification_body}
88 88 mention_recipients = mention_recipients or set()
89 89
90 90 if not created_by_obj:
91 91 raise Exception('unknown user %s' % created_by)
92 92
93 93 if recipients is None:
94 94 # recipients is None means to all admins
95 95 recipients_objs = User.query().filter(User.admin == true()).all()
96 96 log.debug('sending notifications %s to admins: %s',
97 97 notification_type, recipients_objs)
98 98 else:
99 99 recipients_objs = set()
100 100 for u in recipients:
101 101 obj = self._get_user(u)
102 102 if obj:
103 103 recipients_objs.add(obj)
104 104 else: # we didn't find this user, log the error and carry on
105 105 log.error('cannot notify unknown user %r', u)
106 106
107 107 if not recipients_objs:
108 108 raise Exception('no valid recipients specified')
109 109
110 110 log.debug('sending notifications %s to %s',
111 111 notification_type, recipients_objs)
112 112
113 113 # add mentioned users into recipients
114 114 final_recipients = set(recipients_objs).union(mention_recipients)
115 115
116 116 notification = Notification.create(
117 117 created_by=created_by_obj, subject=notification_subject,
118 118 body=notification_body, recipients=final_recipients,
119 119 type_=notification_type
120 120 )
121 121
122 122 if not with_email: # skip sending email, and just create notification
123 123 return notification
124 124
125 125 # don't send email to person who created this comment
126 126 rec_objs = set(recipients_objs).difference({created_by_obj})
127 127
128 128 # now notify all recipients in question
129 129
130 130 for recipient in rec_objs.union(mention_recipients):
131 131 # inject current recipient
132 132 email_kwargs['recipient'] = recipient
133 133 email_kwargs['mention'] = recipient in mention_recipients
134 134 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
135 135 notification_type, **email_kwargs)
136 136
137 137 extra_headers = None
138 138 if 'thread_ids' in email_kwargs:
139 139 extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')}
140 140
141 141 log.debug('Creating notification email task for user:`%s`', recipient)
142 142 task = run_task(
143 143 tasks.send_email, recipient.email, subject,
144 144 email_body_plaintext, email_body, extra_headers=extra_headers)
145 145 log.debug('Created email task: %s', task)
146 146
147 147 return notification
148 148
149 149 def delete(self, user, notification):
150 150 # we don't want to remove actual notification just the assignment
151 151 try:
152 152 notification = self.__get_notification(notification)
153 153 user = self._get_user(user)
154 154 if notification and user:
155 155 obj = UserNotification.query()\
156 156 .filter(UserNotification.user == user)\
157 157 .filter(UserNotification.notification == notification)\
158 158 .one()
159 159 Session().delete(obj)
160 160 return True
161 161 except Exception:
162 162 log.error(traceback.format_exc())
163 163 raise
164 164
165 165 def get_for_user(self, user, filter_=None):
166 166 """
167 167 Get mentions for given user, filter them if filter dict is given
168 168 """
169 169 user = self._get_user(user)
170 170
171 171 q = UserNotification.query()\
172 172 .filter(UserNotification.user == user)\
173 173 .join((
174 174 Notification, UserNotification.notification_id ==
175 175 Notification.notification_id))
176 176 if filter_ == ['all']:
177 177 q = q # no filter
178 178 elif filter_ == ['unread']:
179 179 q = q.filter(UserNotification.read == false())
180 180 elif filter_:
181 181 q = q.filter(Notification.type_.in_(filter_))
182 182
183 183 return q
184 184
185 185 def mark_read(self, user, notification):
186 186 try:
187 187 notification = self.__get_notification(notification)
188 188 user = self._get_user(user)
189 189 if notification and user:
190 190 obj = UserNotification.query()\
191 191 .filter(UserNotification.user == user)\
192 192 .filter(UserNotification.notification == notification)\
193 193 .one()
194 194 obj.read = True
195 195 Session().add(obj)
196 196 return True
197 197 except Exception:
198 198 log.error(traceback.format_exc())
199 199 raise
200 200
201 201 def mark_all_read_for_user(self, user, filter_=None):
202 202 user = self._get_user(user)
203 203 q = UserNotification.query()\
204 204 .filter(UserNotification.user == user)\
205 205 .filter(UserNotification.read == false())\
206 206 .join((
207 207 Notification, UserNotification.notification_id ==
208 208 Notification.notification_id))
209 209 if filter_ == ['unread']:
210 210 q = q.filter(UserNotification.read == false())
211 211 elif filter_:
212 212 q = q.filter(Notification.type_.in_(filter_))
213 213
214 214 # this is a little inefficient but sqlalchemy doesn't support
215 215 # update on joined tables :(
216 216 for obj in q.all():
217 217 obj.read = True
218 218 Session().add(obj)
219 219
220 220 def get_unread_cnt_for_user(self, user):
221 221 user = self._get_user(user)
222 222 return UserNotification.query()\
223 223 .filter(UserNotification.read == false())\
224 224 .filter(UserNotification.user == user).count()
225 225
226 226 def get_unread_for_user(self, user):
227 227 user = self._get_user(user)
228 228 return [x.notification for x in UserNotification.query()
229 229 .filter(UserNotification.read == false())
230 230 .filter(UserNotification.user == user).all()]
231 231
232 232 def get_user_notification(self, user, notification):
233 233 user = self._get_user(user)
234 234 notification = self.__get_notification(notification)
235 235
236 236 return UserNotification.query()\
237 237 .filter(UserNotification.notification == notification)\
238 238 .filter(UserNotification.user == user).scalar()
239 239
240 240 def make_description(self, notification, translate, show_age=True):
241 241 """
242 242 Creates a human readable description based on properties
243 243 of notification object
244 244 """
245 245 _ = translate
246 246 _map = {
247 247 notification.TYPE_CHANGESET_COMMENT: [
248 248 _('%(user)s commented on commit %(date_or_age)s'),
249 249 _('%(user)s commented on commit at %(date_or_age)s'),
250 250 ],
251 251 notification.TYPE_MESSAGE: [
252 252 _('%(user)s sent message %(date_or_age)s'),
253 253 _('%(user)s sent message at %(date_or_age)s'),
254 254 ],
255 255 notification.TYPE_MENTION: [
256 256 _('%(user)s mentioned you %(date_or_age)s'),
257 257 _('%(user)s mentioned you at %(date_or_age)s'),
258 258 ],
259 259 notification.TYPE_REGISTRATION: [
260 260 _('%(user)s registered in RhodeCode %(date_or_age)s'),
261 261 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
262 262 ],
263 263 notification.TYPE_PULL_REQUEST: [
264 264 _('%(user)s opened new pull request %(date_or_age)s'),
265 265 _('%(user)s opened new pull request at %(date_or_age)s'),
266 266 ],
267 267 notification.TYPE_PULL_REQUEST_UPDATE: [
268 268 _('%(user)s updated pull request %(date_or_age)s'),
269 269 _('%(user)s updated pull request at %(date_or_age)s'),
270 270 ],
271 271 notification.TYPE_PULL_REQUEST_COMMENT: [
272 272 _('%(user)s commented on pull request %(date_or_age)s'),
273 273 _('%(user)s commented on pull request at %(date_or_age)s'),
274 274 ],
275 275 }
276 276
277 277 templates = _map[notification.type_]
278 278
279 279 if show_age:
280 280 template = templates[0]
281 281 date_or_age = h.age(notification.created_on)
282 282 if translate:
283 283 date_or_age = translate(date_or_age)
284 284
285 285 if isinstance(date_or_age, TranslationString):
286 286 date_or_age = date_or_age.interpolate()
287 287
288 288 else:
289 289 template = templates[1]
290 290 date_or_age = h.format_date(notification.created_on)
291 291
292 292 return template % {
293 293 'user': notification.created_by_user.username,
294 294 'date_or_age': date_or_age,
295 295 }
296 296
297 297
298 # Templates for Titles, that could be overwritten by rcextensions
299 # Title of email for pull-request update
300 EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = ''
301 # Title of email for request for pull request review
302 EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = ''
303
304 # Title of email for general comment on pull request
305 EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = ''
306 # Title of email for general comment which includes status change on pull request
307 EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
308 # Title of email for inline comment on a file in pull request
309 EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = ''
310
311 # Title of email for general comment on commit
312 EMAIL_COMMENT_SUBJECT_TEMPLATE = ''
313 # Title of email for general comment which includes status change on commit
314 EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
315 # Title of email for inline comment on a file in commit
316 EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = ''
317
318
298 319 class EmailNotificationModel(BaseModel):
299 320 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
300 321 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
301 322 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
302 323 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
303 324 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
304 325 TYPE_MAIN = Notification.TYPE_MESSAGE
305 326
306 327 TYPE_PASSWORD_RESET = 'password_reset'
307 328 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
308 329 TYPE_EMAIL_TEST = 'email_test'
309 330 TYPE_EMAIL_EXCEPTION = 'exception'
310 331 TYPE_TEST = 'test'
311 332
312 333 email_types = {
313 334 TYPE_MAIN:
314 335 'rhodecode:templates/email_templates/main.mako',
315 336 TYPE_TEST:
316 337 'rhodecode:templates/email_templates/test.mako',
317 338 TYPE_EMAIL_EXCEPTION:
318 339 'rhodecode:templates/email_templates/exception_tracker.mako',
319 340 TYPE_EMAIL_TEST:
320 341 'rhodecode:templates/email_templates/email_test.mako',
321 342 TYPE_REGISTRATION:
322 343 'rhodecode:templates/email_templates/user_registration.mako',
323 344 TYPE_PASSWORD_RESET:
324 345 'rhodecode:templates/email_templates/password_reset.mako',
325 346 TYPE_PASSWORD_RESET_CONFIRMATION:
326 347 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
327 348 TYPE_COMMIT_COMMENT:
328 349 'rhodecode:templates/email_templates/commit_comment.mako',
329 350 TYPE_PULL_REQUEST:
330 351 'rhodecode:templates/email_templates/pull_request_review.mako',
331 352 TYPE_PULL_REQUEST_COMMENT:
332 353 'rhodecode:templates/email_templates/pull_request_comment.mako',
333 354 TYPE_PULL_REQUEST_UPDATE:
334 355 'rhodecode:templates/email_templates/pull_request_update.mako',
335 356 }
336 357
337 358 premailer_instance = premailer.Premailer(
338 359 cssutils_logging_level=logging.ERROR,
339 360 cssutils_logging_handler=logging.getLogger().handlers[0]
340 361 if logging.getLogger().handlers else None,
341 362 )
342 363
343 364 def __init__(self):
344 365 """
345 366 Example usage::
346 367
347 368 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
348 369 EmailNotificationModel.TYPE_TEST, **email_kwargs)
349 370
350 371 """
351 372 super(EmailNotificationModel, self).__init__()
352 373 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
353 374
354 375 def _update_kwargs_for_render(self, kwargs):
355 376 """
356 377 Inject params required for Mako rendering
357 378
358 379 :param kwargs:
359 380 """
360 381
361 382 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
362 383 kwargs['rhodecode_version'] = rhodecode.__version__
363 384 instance_url = h.route_url('home')
364 385 _kwargs = {
365 386 'instance_url': instance_url,
366 387 'whitespace_filter': self.whitespace_filter
367 388 }
368 389 _kwargs.update(kwargs)
369 390 return _kwargs
370 391
371 392 def whitespace_filter(self, text):
372 393 return text.replace('\n', '').replace('\t', '')
373 394
374 395 def get_renderer(self, type_, request):
375 396 template_name = self.email_types[type_]
376 397 return request.get_partial_renderer(template_name)
377 398
378 399 def render_email(self, type_, **kwargs):
379 400 """
380 401 renders template for email, and returns a tuple of
381 402 (subject, email_headers, email_html_body, email_plaintext_body)
382 403 """
383 404 # translator and helpers inject
384 405 _kwargs = self._update_kwargs_for_render(kwargs)
385 406 request = get_current_request()
386 407 email_template = self.get_renderer(type_, request=request)
387 408
388 409 subject = email_template.render('subject', **_kwargs)
389 410
390 411 try:
391 412 body_plaintext = email_template.render('body_plaintext', **_kwargs)
392 413 except AttributeError:
393 414 # it's not defined in template, ok we can skip it
394 415 body_plaintext = ''
395 416
396 417 # render WHOLE template
397 418 body = email_template.render(None, **_kwargs)
398 419
399 420 try:
400 421 # Inline CSS styles and conversion
401 422 body = self.premailer_instance.transform(body)
402 423 except Exception:
403 424 log.exception('Failed to parse body with premailer')
404 425 pass
405 426
406 427 return subject, body, body_plaintext
@@ -1,173 +1,176 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': '@'+h.person(user),
10 10 'repo_name': repo_name,
11 11 'status': status_change,
12 12 'comment_file': comment_file,
13 13 'comment_line': comment_line,
14 14 'comment_type': comment_type,
15 15 'comment_id': comment_id,
16 16
17 17 'commit_id': h.show_id(commit),
18 18 'mention_prefix': '[mention] ' if mention else '',
19 19 }
20
21
22 if comment_file:
23 subject_template = email_comment_file_subject_template or \
24 _('{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}` in the `{repo_name}` repository').format(**data)
25 else:
26 if status_change:
27 subject_template = email_comment_status_change_subject_template or \
28 _('{mention_prefix}[status: {status}] {user} left a {comment_type} on commit `{commit_id}` in the `{repo_name}` repository').format(**data)
29 else:
30 subject_template = email_comment_subject_template or \
31 _('{mention_prefix}{user} left a {comment_type} on commit `{commit_id}` in the `{repo_name}` repository').format(**data)
20 32 %>
21 33
22 34
23 % if comment_file:
24 ${_('{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)} ${_('in the `{repo_name}` repository').format(**data) |n}
25 % else:
26 % if status_change:
27 ${_('{mention_prefix}[status: {status}] {user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
28 % else:
29 ${_('{mention_prefix}{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
30 % endif
31 % endif
32
35 ${subject_template.format(**data) |n}
33 36 </%def>
34 37
35 38 ## PLAINTEXT VERSION OF BODY
36 39 <%def name="body_plaintext()" filter="n,trim">
37 40 <%
38 41 data = {
39 42 'user': h.person(user),
40 43 'repo_name': repo_name,
41 44 'status': status_change,
42 45 'comment_file': comment_file,
43 46 'comment_line': comment_line,
44 47 'comment_type': comment_type,
45 48 'comment_id': comment_id,
46 49
47 50 'commit_id': h.show_id(commit),
48 51 }
49 52 %>
50 53
51 54 * ${_('Comment link')}: ${commit_comment_url}
52 55
53 56 %if status_change:
54 57 * ${_('Commit status')}: ${_('Status was changed to')}: *${status_change}*
55 58
56 59 %endif
57 60 * ${_('Commit')}: ${h.show_id(commit)}
58 61
59 62 * ${_('Commit message')}: ${commit.message}
60 63
61 64 %if comment_file:
62 65 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
63 66
64 67 %endif
65 68 % if comment_type == 'todo':
66 69 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
67 70 % else:
68 71 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
69 72 % endif
70 73
71 74 ${comment_body |n, trim}
72 75
73 76 ---
74 77 ${self.plaintext_footer()}
75 78 </%def>
76 79
77 80
78 81 <%
79 82 data = {
80 83 'user': h.person(user),
81 84 'comment_file': comment_file,
82 85 'comment_line': comment_line,
83 86 'comment_type': comment_type,
84 87 'comment_id': comment_id,
85 88 'renderer_type': renderer_type or 'plain',
86 89
87 90 'repo': commit_target_repo_url,
88 91 'repo_name': repo_name,
89 92 'commit_id': h.show_id(commit),
90 93 }
91 94 %>
92 95
93 96 ## header
94 97 <table style="text-align:left;vertical-align:middle;width: 100%">
95 98 <tr>
96 99 <td style="width:100%;border-bottom:1px solid #dbd9da;">
97 100
98 101 <div style="margin: 0; font-weight: bold">
99 102 <div class="clear-both" style="margin-bottom: 4px">
100 103 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
101 104 ${_('left a')}
102 105 <a href="${commit_comment_url}" style="${base.link_css()}">
103 106 % if comment_file:
104 107 ${_('{comment_type} on file `{comment_file}` in commit.').format(**data)}
105 108 % else:
106 109 ${_('{comment_type} on commit.').format(**data) |n}
107 110 % endif
108 111 </a>
109 112 </div>
110 113 <div style="margin-top: 10px"></div>
111 114 ${_('Commit')} <code>${data['commit_id']}</code> ${_('of repository')}: ${data['repo_name']}
112 115 </div>
113 116
114 117 </td>
115 118 </tr>
116 119
117 120 </table>
118 121 <div class="clear-both"></div>
119 122 ## main body
120 123 <table style="text-align:left;vertical-align:middle;width: 100%">
121 124
122 125 ## spacing def
123 126 <tr>
124 127 <td style="width: 130px"></td>
125 128 <td></td>
126 129 </tr>
127 130
128 131 % if status_change:
129 132 <tr>
130 133 <td style="padding-right:20px;">${_('Commit Status')}:</td>
131 134 <td>
132 135 ${_('Status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}
133 136 </td>
134 137 </tr>
135 138 % endif
136 139
137 140 <tr>
138 141 <td style="padding-right:20px;">${_('Commit')}:</td>
139 142 <td>
140 143 <a href="${commit_comment_url}" style="${base.link_css()}">${h.show_id(commit)}</a>
141 144 </td>
142 145 </tr>
143 146 <tr>
144 147 <td style="padding-right:20px;">${_('Commit message')}:</td>
145 148 <td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td>
146 149 </tr>
147 150
148 151 % if comment_file:
149 152 <tr>
150 153 <td style="padding-right:20px;">${_('File')}:</td>
151 154 <td><a href="${commit_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
152 155 </tr>
153 156 % endif
154 157
155 158 <tr style="border-bottom:1px solid #dbd9da;">
156 159 <td colspan="2" style="padding-right:20px;">
157 160 % if comment_type == 'todo':
158 161 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
159 162 % else:
160 163 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
161 164 % endif
162 165 </td>
163 166 </tr>
164 167
165 168 <tr>
166 169 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
167 170 </tr>
168 171
169 172 <tr>
170 173 <td><a href="${commit_comment_reply_url}">${_('Reply')}</a></td>
171 174 <td></td>
172 175 </tr>
173 176 </table>
@@ -1,204 +1,206 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': '@'+h.person(user),
10 10 'repo_name': repo_name,
11 11 'status': status_change,
12 12 'comment_file': comment_file,
13 13 'comment_line': comment_line,
14 14 'comment_type': comment_type,
15 15 'comment_id': comment_id,
16 16
17 17 'pr_title': pull_request.title,
18 18 'pr_id': pull_request.pull_request_id,
19 19 'mention_prefix': '[mention] ' if mention else '',
20 20 }
21
22 if comment_file:
23 subject_template = email_pr_comment_file_subject_template or \
24 _('{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data)
25 else:
26 if status_change:
27 subject_template = email_pr_comment_status_change_subject_template or \
28 _('{mention_prefix}[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data)
29 else:
30 subject_template = email_pr_comment_subject_template or \
31 _('{mention_prefix}{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data)
21 32 %>
22 33
23 34
24 % if comment_file:
25 ${_('{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data) |n}
26 % else:
27 % if status_change:
28 ${_('{mention_prefix}[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
29 % else:
30 ${_('{mention_prefix}{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
31 % endif
32 % endif
33
35 ${subject_template.format(**data) |n}
34 36 </%def>
35 37
36 38 ## PLAINTEXT VERSION OF BODY
37 39 <%def name="body_plaintext()" filter="n,trim">
38 40 <%
39 41 data = {
40 42 'user': h.person(user),
41 43 'repo_name': repo_name,
42 44 'status': status_change,
43 45 'comment_file': comment_file,
44 46 'comment_line': comment_line,
45 47 'comment_type': comment_type,
46 48 'comment_id': comment_id,
47 49
48 50 'pr_title': pull_request.title,
49 51 'pr_id': pull_request.pull_request_id,
50 52 'source_ref_type': pull_request.source_ref_parts.type,
51 53 'source_ref_name': pull_request.source_ref_parts.name,
52 54 'target_ref_type': pull_request.target_ref_parts.type,
53 55 'target_ref_name': pull_request.target_ref_parts.name,
54 56 'source_repo': pull_request_source_repo.repo_name,
55 57 'target_repo': pull_request_target_repo.repo_name,
56 58 'source_repo_url': pull_request_source_repo_url,
57 59 'target_repo_url': pull_request_target_repo_url,
58 60 }
59 61 %>
60 62
61 63 * ${_('Comment link')}: ${pr_comment_url}
62 64
63 65 * ${_('Pull Request')}: !${pull_request.pull_request_id}
64 66
65 67 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
66 68
67 69 %if status_change and not closing_pr:
68 70 * ${_('{user} submitted pull request !{pr_id} status: *{status}*').format(**data)}
69 71
70 72 %elif status_change and closing_pr:
71 73 * ${_('{user} submitted pull request !{pr_id} status: *{status} and closed*').format(**data)}
72 74
73 75 %endif
74 76 %if comment_file:
75 77 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
76 78
77 79 %endif
78 80 % if comment_type == 'todo':
79 81 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
80 82 % else:
81 83 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
82 84 % endif
83 85
84 86 ${comment_body |n, trim}
85 87
86 88 ---
87 89 ${self.plaintext_footer()}
88 90 </%def>
89 91
90 92
91 93 <%
92 94 data = {
93 95 'user': h.person(user),
94 96 'comment_file': comment_file,
95 97 'comment_line': comment_line,
96 98 'comment_type': comment_type,
97 99 'comment_id': comment_id,
98 100 'renderer_type': renderer_type or 'plain',
99 101
100 102 'pr_title': pull_request.title,
101 103 'pr_id': pull_request.pull_request_id,
102 104 'status': status_change,
103 105 'source_ref_type': pull_request.source_ref_parts.type,
104 106 'source_ref_name': pull_request.source_ref_parts.name,
105 107 'target_ref_type': pull_request.target_ref_parts.type,
106 108 'target_ref_name': pull_request.target_ref_parts.name,
107 109 'source_repo': pull_request_source_repo.repo_name,
108 110 'target_repo': pull_request_target_repo.repo_name,
109 111 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
110 112 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
111 113 }
112 114 %>
113 115
114 116 ## header
115 117 <table style="text-align:left;vertical-align:middle;width: 100%">
116 118 <tr>
117 119 <td style="width:100%;border-bottom:1px solid #dbd9da;">
118 120
119 121 <div style="margin: 0; font-weight: bold">
120 122 <div class="clear-both" style="margin-bottom: 4px">
121 123 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
122 124 ${_('left a')}
123 125 <a href="${pr_comment_url}" style="${base.link_css()}">
124 126 % if comment_file:
125 127 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
126 128 % else:
127 129 ${_('{comment_type} on pull request.').format(**data) |n}
128 130 % endif
129 131 </a>
130 132 </div>
131 133 <div style="margin-top: 10px"></div>
132 134 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
133 135 </div>
134 136
135 137 </td>
136 138 </tr>
137 139
138 140 </table>
139 141 <div class="clear-both"></div>
140 142 ## main body
141 143 <table style="text-align:left;vertical-align:middle;width: 100%">
142 144
143 145 ## spacing def
144 146 <tr>
145 147 <td style="width: 130px"></td>
146 148 <td></td>
147 149 </tr>
148 150
149 151 % if status_change:
150 152 <tr>
151 153 <td style="padding-right:20px;">${_('Review Status')}:</td>
152 154 <td>
153 155 % if closing_pr:
154 156 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
155 157 % else:
156 158 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
157 159 % endif
158 160 </td>
159 161 </tr>
160 162 % endif
161 163 <tr>
162 164 <td style="padding-right:20px;">${_('Pull request')}:</td>
163 165 <td>
164 166 <a href="${pull_request_url}" style="${base.link_css()}">
165 167 !${pull_request.pull_request_id}
166 168 </a>
167 169 </td>
168 170 </tr>
169 171
170 172 <tr>
171 173 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
172 174 <td style="line-height:20px;">
173 175 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
174 176 &rarr;
175 177 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
176 178 </td>
177 179 </tr>
178 180
179 181 % if comment_file:
180 182 <tr>
181 183 <td style="padding-right:20px;">${_('File')}:</td>
182 184 <td><a href="${pr_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
183 185 </tr>
184 186 % endif
185 187
186 188 <tr style="border-bottom:1px solid #dbd9da;">
187 189 <td colspan="2" style="padding-right:20px;">
188 190 % if comment_type == 'todo':
189 191 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
190 192 % else:
191 193 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
192 194 % endif
193 195 </td>
194 196 </tr>
195 197
196 198 <tr>
197 199 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
198 200 </tr>
199 201
200 202 <tr>
201 203 <td><a href="${pr_comment_reply_url}">${_('Reply')}</a></td>
202 204 <td></td>
203 205 </tr>
204 206 </table>
@@ -1,144 +1,146 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': '@'+h.person(user),
10 10 'pr_id': pull_request.pull_request_id,
11 11 'pr_title': pull_request.title,
12 12 }
13
14 subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"')
13 15 %>
14 16
15 ${_('{user} requested a pull request review. !{pr_id}: "{pr_title}"').format(**data) |n}
17 ${subject_template.format(**data) |n}
16 18 </%def>
17 19
18 20 ## PLAINTEXT VERSION OF BODY
19 21 <%def name="body_plaintext()" filter="n,trim">
20 22 <%
21 23 data = {
22 24 'user': h.person(user),
23 25 'pr_id': pull_request.pull_request_id,
24 26 'pr_title': pull_request.title,
25 27 'source_ref_type': pull_request.source_ref_parts.type,
26 28 'source_ref_name': pull_request.source_ref_parts.name,
27 29 'target_ref_type': pull_request.target_ref_parts.type,
28 30 'target_ref_name': pull_request.target_ref_parts.name,
29 31 'repo_url': pull_request_source_repo_url,
30 32 'source_repo': pull_request_source_repo.repo_name,
31 33 'target_repo': pull_request_target_repo.repo_name,
32 34 'source_repo_url': pull_request_source_repo_url,
33 35 'target_repo_url': pull_request_target_repo_url,
34 36 }
35 37 %>
36 38
37 39 * ${_('Pull Request link')}: ${pull_request_url}
38 40
39 41 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
40 42
41 43 * ${_('Title')}: ${pull_request.title}
42 44
43 45 * ${_('Description')}:
44 46
45 47 ${pull_request.description | trim}
46 48
47 49
48 50 * ${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
49 51
50 52 % for commit_id, message in pull_request_commits:
51 53 - ${h.short_id(commit_id)}
52 54 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
53 55
54 56 % endfor
55 57
56 58 ---
57 59 ${self.plaintext_footer()}
58 60 </%def>
59 61 <%
60 62 data = {
61 63 'user': h.person(user),
62 64 'pr_id': pull_request.pull_request_id,
63 65 'pr_title': pull_request.title,
64 66 'source_ref_type': pull_request.source_ref_parts.type,
65 67 'source_ref_name': pull_request.source_ref_parts.name,
66 68 'target_ref_type': pull_request.target_ref_parts.type,
67 69 'target_ref_name': pull_request.target_ref_parts.name,
68 70 'repo_url': pull_request_source_repo_url,
69 71 'source_repo': pull_request_source_repo.repo_name,
70 72 'target_repo': pull_request_target_repo.repo_name,
71 73 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
72 74 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
73 75 }
74 76 %>
75 77 ## header
76 78 <table style="text-align:left;vertical-align:middle;width: 100%">
77 79 <tr>
78 80 <td style="width:100%;border-bottom:1px solid #dbd9da;">
79 81
80 82 <div style="margin: 0; font-weight: bold">
81 83 <div class="clear-both" class="clear-both" style="margin-bottom: 4px">
82 84 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
83 85 ${_('requested a')}
84 86 <a href="${pull_request_url}" style="${base.link_css()}">
85 87 ${_('pull request review.').format(**data) }
86 88 </a>
87 89 </div>
88 90 <div style="margin-top: 10px"></div>
89 91 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
90 92 </div>
91 93
92 94 </td>
93 95 </tr>
94 96
95 97 </table>
96 98 <div class="clear-both"></div>
97 99 ## main body
98 100 <table style="text-align:left;vertical-align:middle;width: 100%">
99 101 ## spacing def
100 102 <tr>
101 103 <td style="width: 130px"></td>
102 104 <td></td>
103 105 </tr>
104 106
105 107 <tr>
106 108 <td style="padding-right:20px;">${_('Pull request')}:</td>
107 109 <td>
108 110 <a href="${pull_request_url}" style="${base.link_css()}">
109 111 !${pull_request.pull_request_id}
110 112 </a>
111 113 </td>
112 114 </tr>
113 115
114 116 <tr>
115 117 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
116 118 <td style="line-height:20px;">
117 119 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
118 120 &rarr;
119 121 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
120 122 </td>
121 123 </tr>
122 124
123 125 <tr>
124 126 <td style="padding-right:20px;">${_('Description')}:</td>
125 127 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
126 128 </tr>
127 129 <tr>
128 130 <td style="padding-right:20px;">${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits)) % {'num': len(pull_request_commits)}}:</td>
129 131 <td></td>
130 132 </tr>
131 133
132 134 <tr>
133 135 <td colspan="2">
134 136 <ol style="margin:0 0 0 1em;padding:0;text-align:left;">
135 137 % for commit_id, message in pull_request_commits:
136 138 <li style="margin:0 0 1em;">
137 139 <pre style="margin:0 0 .5em"><a href="${h.route_path('repo_commit', repo_name=pull_request_source_repo.repo_name, commit_id=commit_id)}" style="${base.link_css()}">${h.short_id(commit_id)}</a></pre>
138 140 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
139 141 </li>
140 142 % endfor
141 143 </ol>
142 144 </td>
143 145 </tr>
144 146 </table>
@@ -1,170 +1,172 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'updating_user': '@'+h.person(updating_user),
10 10 'pr_id': pull_request.pull_request_id,
11 11 'pr_title': pull_request.title,
12 12 }
13
14 subject_template = email_pr_update_subject_template or _('{updating_user} updated pull request. !{pr_id}: "{pr_title}"')
13 15 %>
14 16
15 ${_('{updating_user} updated pull request. !{pr_id}: "{pr_title}"').format(**data) |n}
17 ${subject_template.format(**data) |n}
16 18 </%def>
17 19
18 20 ## PLAINTEXT VERSION OF BODY
19 21 <%def name="body_plaintext()" filter="n,trim">
20 22 <%
21 23 data = {
22 24 'updating_user': h.person(updating_user),
23 25 'pr_id': pull_request.pull_request_id,
24 26 'pr_title': pull_request.title,
25 27 'source_ref_type': pull_request.source_ref_parts.type,
26 28 'source_ref_name': pull_request.source_ref_parts.name,
27 29 'target_ref_type': pull_request.target_ref_parts.type,
28 30 'target_ref_name': pull_request.target_ref_parts.name,
29 31 'repo_url': pull_request_source_repo_url,
30 32 'source_repo': pull_request_source_repo.repo_name,
31 33 'target_repo': pull_request_target_repo.repo_name,
32 34 'source_repo_url': pull_request_source_repo_url,
33 35 'target_repo_url': pull_request_target_repo_url,
34 36 }
35 37 %>
36 38
37 39 * ${_('Pull Request link')}: ${pull_request_url}
38 40
39 41 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
40 42
41 43 * ${_('Title')}: ${pull_request.title}
42 44
43 45 * ${_('Description')}:
44 46
45 47 ${pull_request.description | trim}
46 48
47 49 * Changed commits:
48 50
49 51 - Added: ${len(added_commits)}
50 52 - Removed: ${len(removed_commits)}
51 53
52 54 * Changed files:
53 55
54 56 %if not changed_files:
55 57 No file changes found
56 58 %else:
57 59 %for file_name in added_files:
58 60 - A `${file_name}`
59 61 %endfor
60 62 %for file_name in modified_files:
61 63 - M `${file_name}`
62 64 %endfor
63 65 %for file_name in removed_files:
64 66 - R `${file_name}`
65 67 %endfor
66 68 %endif
67 69
68 70 ---
69 71 ${self.plaintext_footer()}
70 72 </%def>
71 73 <%
72 74 data = {
73 75 'updating_user': h.person(updating_user),
74 76 'pr_id': pull_request.pull_request_id,
75 77 'pr_title': pull_request.title,
76 78 'source_ref_type': pull_request.source_ref_parts.type,
77 79 'source_ref_name': pull_request.source_ref_parts.name,
78 80 'target_ref_type': pull_request.target_ref_parts.type,
79 81 'target_ref_name': pull_request.target_ref_parts.name,
80 82 'repo_url': pull_request_source_repo_url,
81 83 'source_repo': pull_request_source_repo.repo_name,
82 84 'target_repo': pull_request_target_repo.repo_name,
83 85 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
84 86 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
85 87 }
86 88 %>
87 89
88 90 ## header
89 91 <table style="text-align:left;vertical-align:middle;width: 100%">
90 92 <tr>
91 93 <td style="width:100%;border-bottom:1px solid #dbd9da;">
92 94
93 95 <div style="margin: 0; font-weight: bold">
94 96 <div class="clear-both" style="margin-bottom: 4px">
95 97 <span style="color:#7E7F7F">@${h.person(updating_user.username)}</span>
96 98 ${_('updated')}
97 99 <a href="${pull_request_url}" style="${base.link_css()}">
98 100 ${_('pull request.').format(**data) }
99 101 </a>
100 102 </div>
101 103 <div style="margin-top: 10px"></div>
102 104 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
103 105 </div>
104 106
105 107 </td>
106 108 </tr>
107 109
108 110 </table>
109 111 <div class="clear-both"></div>
110 112 ## main body
111 113 <table style="text-align:left;vertical-align:middle;width: 100%">
112 114 ## spacing def
113 115 <tr>
114 116 <td style="width: 130px"></td>
115 117 <td></td>
116 118 </tr>
117 119
118 120 <tr>
119 121 <td style="padding-right:20px;">${_('Pull request')}:</td>
120 122 <td>
121 123 <a href="${pull_request_url}" style="${base.link_css()}">
122 124 !${pull_request.pull_request_id}
123 125 </a>
124 126 </td>
125 127 </tr>
126 128
127 129 <tr>
128 130 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
129 131 <td style="line-height:20px;">
130 132 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
131 133 &rarr;
132 134 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
133 135 </td>
134 136 </tr>
135 137
136 138 <tr>
137 139 <td style="padding-right:20px;">${_('Description')}:</td>
138 140 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
139 141 </tr>
140 142 <tr>
141 143 <td style="padding-right:20px;">${_('Changes')}:</td>
142 144 <td>
143 145 <strong>Changed commits:</strong>
144 146 <ul class="changes-ul">
145 147 <li>- Added: ${len(added_commits)}</li>
146 148 <li>- Removed: ${len(removed_commits)}</li>
147 149 </ul>
148 150
149 151 <strong>Changed files:</strong>
150 152 <ul class="changes-ul">
151 153
152 154 %if not changed_files:
153 155 <li>No file changes found</li>
154 156 %else:
155 157 %for file_name in added_files:
156 158 <li>- A <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
157 159 %endfor
158 160 %for file_name in modified_files:
159 161 <li>- M <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
160 162 %endfor
161 163 %for file_name in removed_files:
162 164 <li>- R <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
163 165 %endfor
164 166 %endif
165 167
166 168 </ul>
167 169 </td>
168 170 </tr>
169 171
170 172 </table>
General Comments 0
You need to be logged in to leave comments. Login now