##// END OF EJS Templates
emails: updated emails design and data structure they provide....
dan -
r4038:4a4a02a9 default
parent child Browse files
Show More
@@ -0,0 +1,29 b''
1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
2
3 <html>
4 <head></head>
5
6 <body>
7
8 SUBJECT:
9 <pre>${c.subject}</pre>
10
11 HEADERS:
12 <pre>
13 ${c.headers}
14 </pre>
15
16 PLAINTEXT:
17 <pre>
18 ${c.email_body_plaintext|n}
19 </pre>
20
21 </body>
22 </html>
23 <br/><br/>
24
25 HTML:
26
27 ${c.email_body|n}
28
29
@@ -0,0 +1,49 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
3
4 <%def name="title()">
5 ${_('Show notification')} ${c.rhodecode_user.username}
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
9 </%def>
10
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('My Notifications'), h.route_path('notifications_show_all'))}
13 &raquo;
14 ${_('Show notification')}
15 </%def>
16
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='admin')}
19 </%def>
20
21 <%def name="main()">
22 <div class="box">
23
24 <!-- box / title -->
25 <div class="title">
26 Rendered plain text using markup renderer
27 </div>
28 <div class="table">
29 <div >
30 <div class="notification-header">
31 GRAVATAR
32 <div class="desc">
33 DESC
34 </div>
35 </div>
36 <div class="notification-body">
37 <div class="notification-subject">
38 <h3>${_('Subject')}: ${c.subject}</h3>
39 </div>
40 ${c.email_body|n}
41 </div>
42 </div>
43 </div>
44 </div>
45
46 </%def>
47
48
49
@@ -0,0 +1,34 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/debug_style/index.html"/>
3
4 <%def name="breadcrumbs_links()">
5 ${h.link_to(_('Style'), h.route_path('debug_style_home'))}
6 &raquo;
7 ${c.active}
8 </%def>
9
10
11 <%def name="real_main()">
12 <div class="box">
13 <div class="title">
14 ${self.breadcrumbs()}
15 </div>
16
17 <div class='sidebar-col-wrapper'>
18 ${self.sidebar()}
19 <div class="main-content">
20 <h2>Emails</h2>
21 <ul>
22 % for elem in sorted(c.email_types.keys()):
23 <li>
24 <a href="${request.route_path('debug_style_email', email_id=elem, _query={'user':c.rhodecode_user.username})}">${elem}</a>
25 |
26 <a href="${request.route_path('debug_style_email_plain_rendered', email_id=elem, _query={'user':c.rhodecode_user.username})}">plain rendered</a>
27 </li>
28 % endfor
29 </ul>
30
31 </div> <!-- .main-content -->
32 </div>
33 </div> <!-- .box -->
34 </%def>
@@ -43,6 +43,14 b' def includeme(config):'
43 pattern=ADMIN_PREFIX + '/debug_style',
43 pattern=ADMIN_PREFIX + '/debug_style',
44 debug_style=True)
44 debug_style=True)
45 config.add_route(
45 config.add_route(
46 name='debug_style_email',
47 pattern=ADMIN_PREFIX + '/debug_style/email/{email_id}',
48 debug_style=True)
49 config.add_route(
50 name='debug_style_email_plain_rendered',
51 pattern=ADMIN_PREFIX + '/debug_style/email-rendered/{email_id}',
52 debug_style=True)
53 config.add_route(
46 name='debug_style_template',
54 name='debug_style_template',
47 pattern=ADMIN_PREFIX + '/debug_style/t/{t_path}',
55 pattern=ADMIN_PREFIX + '/debug_style/t/{t_path}',
48 debug_style=True)
56 debug_style=True)
@@ -20,10 +20,15 b''
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23
24
24 from pyramid.view import view_config
25 from pyramid.view import view_config
25 from pyramid.renderers import render_to_response
26 from pyramid.renderers import render_to_response
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib.celerylib import run_task, tasks
29 from rhodecode.lib.utils2 import AttributeDict
30 from rhodecode.model.db import User
31 from rhodecode.model.notification import EmailNotificationModel
27
32
28 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
29
34
@@ -46,6 +51,271 b' class DebugStyleView(BaseAppView):'
46 request=self.request)
51 request=self.request)
47
52
48 @view_config(
53 @view_config(
54 route_name='debug_style_email', request_method='GET',
55 renderer=None)
56 @view_config(
57 route_name='debug_style_email_plain_rendered', request_method='GET',
58 renderer=None)
59 def render_email(self):
60 c = self.load_default_context()
61 email_id = self.request.matchdict['email_id']
62 c.active = 'emails'
63
64 pr = AttributeDict(
65 pull_request_id=123,
66 title='digital_ocean: fix redis, elastic search start on boot, '
67 'fix fd limits on supervisor, set postgres 11 version',
68 description='''
69 Check if we should use full-topic or mini-topic.
70
71 - full topic produces some problems with merge states etc
72 - server-mini-topic needs probably tweeks.
73 ''',
74 repo_name='foobar',
75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
76 target_ref_parts=AttributeDict(type='branch', name='master'),
77 )
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
81
82 email_kwargs = {
83 'test': {},
84 'message': {
85 'body': 'message body !'
86 },
87 'email_test': {
88 'user': user,
89 'date': datetime.datetime.now(),
90 'rhodecode_version': c.rhodecode_version
91 },
92 'password_reset': {
93 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
94
95 'user': user,
96 'date': datetime.datetime.now(),
97 'email': 'test@rhodecode.com',
98 'first_admin_email': User.get_first_super_admin().email
99 },
100 'password_reset_confirmation': {
101 'new_password': 'new-password-example',
102 'user': user,
103 'date': datetime.datetime.now(),
104 'email': 'test@rhodecode.com',
105 'first_admin_email': User.get_first_super_admin().email
106 },
107 'registration': {
108 'user': user,
109 'date': datetime.datetime.now(),
110 },
111
112 'pull_request_comment': {
113 'user': user,
114
115 'status_change': None,
116 'status_change_type': None,
117
118 'pull_request': pr,
119 'pull_request_commits': [],
120
121 'pull_request_target_repo': target_repo,
122 'pull_request_target_repo_url': 'http://target-repo/url',
123
124 'pull_request_source_repo': source_repo,
125 'pull_request_source_repo_url': 'http://source-repo/url',
126
127 'pull_request_url': 'http://localhost/pr1',
128 'pr_comment_url': 'http://comment-url',
129
130 'comment_file': None,
131 'comment_line': None,
132 'comment_type': 'note',
133 'comment_body': 'This is my comment body. *I like !*',
134
135 'renderer_type': 'markdown',
136 'mention': True,
137
138 },
139 'pull_request_comment+status': {
140 'user': user,
141
142 'status_change': 'approved',
143 'status_change_type': 'approved',
144
145 'pull_request': pr,
146 'pull_request_commits': [],
147
148 'pull_request_target_repo': target_repo,
149 'pull_request_target_repo_url': 'http://target-repo/url',
150
151 'pull_request_source_repo': source_repo,
152 'pull_request_source_repo_url': 'http://source-repo/url',
153
154 'pull_request_url': 'http://localhost/pr1',
155 'pr_comment_url': 'http://comment-url',
156
157 'comment_type': 'todo',
158 'comment_file': None,
159 'comment_line': None,
160 'comment_body': '''
161 I think something like this would be better
162
163 ```py
164
165 def db():
166 global connection
167 return connection
168
169 ```
170
171 ''',
172
173 'renderer_type': 'markdown',
174 'mention': True,
175
176 },
177 'pull_request_comment+file': {
178 'user': user,
179
180 'status_change': None,
181 'status_change_type': None,
182
183 'pull_request': pr,
184 'pull_request_commits': [],
185
186 'pull_request_target_repo': target_repo,
187 'pull_request_target_repo_url': 'http://target-repo/url',
188
189 'pull_request_source_repo': source_repo,
190 'pull_request_source_repo_url': 'http://source-repo/url',
191
192 'pull_request_url': 'http://localhost/pr1',
193
194 'pr_comment_url': 'http://comment-url',
195
196 'comment_file': 'rhodecode/model/db.py',
197 'comment_line': 'o1210',
198 'comment_type': 'todo',
199 'comment_body': '''
200 I like this !
201
202 But please check this code::
203
204 def main():
205 print 'ok'
206
207 This should work better !
208 ''',
209
210 'renderer_type': 'rst',
211 'mention': True,
212
213 },
214
215 'cs_comment': {
216 'user': user,
217 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
218 'status_change': None,
219 'status_change_type': None,
220
221 'commit_target_repo_url': 'http://foo.example.com/#comment1',
222 'repo_name': 'test-repo',
223 'comment_type': 'note',
224 'comment_file': None,
225 'comment_line': None,
226 'commit_comment_url': 'http://comment-url',
227 'comment_body': 'This is my comment body. *I like !*',
228 'renderer_type': 'markdown',
229 'mention': True,
230 },
231 'cs_comment+status': {
232 'user': user,
233 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
234 'status_change': 'approved',
235 'status_change_type': 'approved',
236
237 'commit_target_repo_url': 'http://foo.example.com/#comment1',
238 'repo_name': 'test-repo',
239 'comment_type': 'note',
240 'comment_file': None,
241 'comment_line': None,
242 'commit_comment_url': 'http://comment-url',
243 'comment_body': '''
244 Hello **world**
245
246 This is a multiline comment :)
247
248 - list
249 - list2
250 ''',
251 'renderer_type': 'markdown',
252 'mention': True,
253 },
254 'cs_comment+file': {
255 'user': user,
256 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
257 'status_change': None,
258 'status_change_type': None,
259
260 'commit_target_repo_url': 'http://foo.example.com/#comment1',
261 'repo_name': 'test-repo',
262
263 'comment_type': 'note',
264 'comment_file': 'test-file.py',
265 'comment_line': 'n100',
266
267 'commit_comment_url': 'http://comment-url',
268 'comment_body': 'This is my comment body. *I like !*',
269 'renderer_type': 'markdown',
270 'mention': True,
271 },
272
273 'pull_request': {
274 'user': user,
275 'pull_request': pr,
276 'pull_request_commits': [
277 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
278 my-account: moved email closer to profile as it's similar data just moved outside.
279 '''),
280 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
281 users: description edit fixes
282
283 - tests
284 - added metatags info
285 '''),
286 ],
287
288 'pull_request_target_repo': target_repo,
289 'pull_request_target_repo_url': 'http://target-repo/url',
290
291 'pull_request_source_repo': source_repo,
292 'pull_request_source_repo_url': 'http://source-repo/url',
293
294 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
295 }
296
297 }
298
299 template_type = email_id.split('+')[0]
300 (c.subject, c.headers, c.email_body,
301 c.email_body_plaintext) = EmailNotificationModel().render_email(
302 template_type, **email_kwargs.get(email_id, {}))
303
304 test_email = self.request.GET.get('email')
305 if test_email:
306 recipients = [test_email]
307 run_task(tasks.send_email, recipients, c.subject,
308 c.email_body_plaintext, c.email_body)
309
310 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
311 template = 'debug_style/email_plain_rendered.mako'
312 else:
313 template = 'debug_style/email.mako'
314 return render_to_response(
315 template, self._get_template_context(c),
316 request=self.request)
317
318 @view_config(
49 route_name='debug_style_template', request_method='GET',
319 route_name='debug_style_template', request_method='GET',
50 renderer=None)
320 renderer=None)
51 def template(self):
321 def template(self):
@@ -53,7 +323,16 b' class DebugStyleView(BaseAppView):'
53 c = self.load_default_context()
323 c = self.load_default_context()
54 c.active = os.path.splitext(t_path)[0]
324 c.active = os.path.splitext(t_path)[0]
55 c.came_from = ''
325 c.came_from = ''
326 c.email_types = {
327 'cs_comment+file': {},
328 'cs_comment+status': {},
329
330 'pull_request_comment+file': {},
331 'pull_request_comment+status': {},
332 }
333 c.email_types.update(EmailNotificationModel.email_types)
56
334
57 return render_to_response(
335 return render_to_response(
58 'debug_style/' + t_path, self._get_template_context(c),
336 'debug_style/' + t_path, self._get_template_context(c),
59 request=self.request) No newline at end of file
337 request=self.request)
338
@@ -98,7 +98,7 b' class TestRepoCommitCommentsView(TestCon'
98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
99
99
100 author = notification.created_by_user.username_and_name
100 author = notification.created_by_user.username_and_name
101 sbj = '{0} left a {1} on commit `{2}` in the {3} repository'.format(
101 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
102 author, comment_type, h.show_id(commit), backend.repo_name)
102 author, comment_type, h.show_id(commit), backend.repo_name)
103 assert sbj == notification.subject
103 assert sbj == notification.subject
104
104
@@ -159,7 +159,7 b' class TestRepoCommitCommentsView(TestCon'
159 assert comment.revision == commit_id
159 assert comment.revision == commit_id
160
160
161 author = notification.created_by_user.username_and_name
161 author = notification.created_by_user.username_and_name
162 sbj = '{0} left a {1} on file `{2}` in commit `{3}` in the {4} repository'.format(
162 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
164
164
165 assert sbj == notification.subject
165 assert sbj == notification.subject
@@ -230,7 +230,7 b' class TestRepoCommitCommentsView(TestCon'
230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
231
231
232 author = notification.created_by_user.username_and_name
232 author = notification.created_by_user.username_and_name
233 sbj = '[status: Approved] {0} left a note on commit `{1}` in the {2} repository'.format(
233 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
234 author, h.show_id(commit), backend.repo_name)
234 author, h.show_id(commit), backend.repo_name)
235 assert sbj == notification.subject
235 assert sbj == notification.subject
236
236
@@ -467,7 +467,7 b' class TestPullrequestsView(object):'
467 .filter(Notification.created_by == pull_request.author.user_id,
467 .filter(Notification.created_by == pull_request.author.user_id,
468 Notification.type_ == Notification.TYPE_PULL_REQUEST,
468 Notification.type_ == Notification.TYPE_PULL_REQUEST,
469 Notification.subject.contains(
469 Notification.subject.contains(
470 "wants you to review pull request #%s" % pull_request_id))
470 "requested a pull request review. !%s" % pull_request_id))
471 assert len(notifications.all()) == 1
471 assert len(notifications.all()) == 1
472
472
473 # Change reviewers and check that a notification was made
473 # Change reviewers and check that a notification was made
@@ -549,11 +549,10 b' class TestPullrequestsView(object):'
549 pull_request_id = pull_request.pull_request_id
549 pull_request_id = pull_request.pull_request_id
550 repo_name = pull_request.target_repo.scm_instance().name,
550 repo_name = pull_request.target_repo.scm_instance().name,
551
551
552 response = self.app.post(
552 url = route_path('pullrequest_merge',
553 route_path('pullrequest_merge',
553 repo_name=str(repo_name[0]),
554 repo_name=str(repo_name[0]),
554 pull_request_id=pull_request_id)
555 pull_request_id=pull_request_id),
555 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
556 params={'csrf_token': csrf_token}).follow()
557
556
558 pull_request = PullRequest.get(pull_request_id)
557 pull_request = PullRequest.get(pull_request_id)
559
558
@@ -735,12 +734,12 b' class TestPullrequestsView(object):'
735 backend.pull_heads(source, heads=['change-rebased'])
734 backend.pull_heads(source, heads=['change-rebased'])
736
735
737 # update PR
736 # update PR
738 self.app.post(
737 url = route_path('pullrequest_update',
739 route_path('pullrequest_update',
738 repo_name=target.repo_name,
740 repo_name=target.repo_name,
739 pull_request_id=pull_request_id)
741 pull_request_id=pull_request_id),
740 self.app.post(url,
742 params={'update_commits': 'true', 'csrf_token': csrf_token},
741 params={'update_commits': 'true', 'csrf_token': csrf_token},
743 status=200)
742 status=200)
744
743
745 # check that we have now both revisions
744 # check that we have now both revisions
746 pull_request = PullRequest.get(pull_request_id)
745 pull_request = PullRequest.get(pull_request_id)
@@ -801,12 +800,12 b' class TestPullrequestsView(object):'
801 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
800 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
802
801
803 # update PR
802 # update PR
804 self.app.post(
803 url = route_path('pullrequest_update',
805 route_path('pullrequest_update',
804 repo_name=target.repo_name,
806 repo_name=target.repo_name,
805 pull_request_id=pull_request_id)
807 pull_request_id=pull_request_id),
806 self.app.post(url,
808 params={'update_commits': 'true', 'csrf_token': csrf_token},
807 params={'update_commits': 'true', 'csrf_token': csrf_token},
809 status=200)
808 status=200)
810
809
811 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
810 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
812 assert response.status_int == 200
811 assert response.status_int == 200
@@ -961,12 +960,12 b' class TestPullrequestsView(object):'
961 else:
960 else:
962 vcs.strip(pr_util.commit_ids['new-feature'])
961 vcs.strip(pr_util.commit_ids['new-feature'])
963
962
964 response = self.app.post(
963 url = route_path('pullrequest_update',
965 route_path('pullrequest_update',
964 repo_name=pull_request.target_repo.repo_name,
966 repo_name=pull_request.target_repo.repo_name,
965 pull_request_id=pull_request.pull_request_id)
967 pull_request_id=pull_request.pull_request_id),
966 response = self.app.post(url,
968 params={'update_commits': 'true',
967 params={'update_commits': 'true',
969 'csrf_token': csrf_token})
968 'csrf_token': csrf_token})
970
969
971 assert response.status_int == 200
970 assert response.status_int == 200
972 assert response.body == 'true'
971 assert response.body == 'true'
@@ -1208,14 +1207,11 b' class TestPullrequestsControllerDelete(o'
1208
1207
1209
1208
1210 def assert_pull_request_status(pull_request, expected_status):
1209 def assert_pull_request_status(pull_request, expected_status):
1211 status = ChangesetStatusModel().calculated_review_status(
1210 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1212 pull_request=pull_request)
1213 assert status == expected_status
1211 assert status == expected_status
1214
1212
1215
1213
1216 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1214 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1217 @pytest.mark.usefixtures("autologin_user")
1215 @pytest.mark.usefixtures("autologin_user")
1218 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1216 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1219 response = app.get(
1217 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
1220 route_path(route, repo_name=backend_svn.repo_name), status=404)
1221
@@ -359,7 +359,7 b' class CommentsModel(BaseModel):'
359 kwargs.update({
359 kwargs.update({
360 'commit': commit_obj,
360 'commit': commit_obj,
361 'commit_message': commit_obj.message,
361 'commit_message': commit_obj.message,
362 'commit_target_repo': target_repo_url,
362 'commit_target_repo_url': target_repo_url,
363 'commit_comment_url': commit_comment_url,
363 'commit_comment_url': commit_comment_url,
364 })
364 })
365
365
@@ -382,6 +382,11 b' class CommentsModel(BaseModel):'
382 pull_request_id=pull_request_obj.pull_request_id,
382 pull_request_id=pull_request_obj.pull_request_id,
383 _anchor='comment-%s' % comment.comment_id)
383 _anchor='comment-%s' % comment.comment_id)
384
384
385 pr_url = h.route_url(
386 'pullrequest_show',
387 repo_name=pr_target_repo.repo_name,
388 pull_request_id=pull_request_obj.pull_request_id, )
389
385 # set some variables for email notification
390 # set some variables for email notification
386 pr_target_repo_url = h.route_url(
391 pr_target_repo_url = h.route_url(
387 'repo_summary', repo_name=pr_target_repo.repo_name)
392 'repo_summary', repo_name=pr_target_repo.repo_name)
@@ -393,10 +398,11 b' class CommentsModel(BaseModel):'
393 kwargs.update({
398 kwargs.update({
394 'pull_request': pull_request_obj,
399 'pull_request': pull_request_obj,
395 'pr_id': pull_request_obj.pull_request_id,
400 'pr_id': pull_request_obj.pull_request_id,
396 'pr_target_repo': pr_target_repo,
401 'pull_request_url': pr_url,
397 'pr_target_repo_url': pr_target_repo_url,
402 'pull_request_target_repo': pr_target_repo,
398 'pr_source_repo': pr_source_repo,
403 'pull_request_target_repo_url': pr_target_repo_url,
399 'pr_source_repo_url': pr_source_repo_url,
404 'pull_request_source_repo': pr_source_repo,
405 'pull_request_source_repo_url': pr_source_repo_url,
400 'pr_comment_url': pr_comment_url,
406 'pr_comment_url': pr_comment_url,
401 'pr_closing': closing_pr,
407 'pr_closing': closing_pr,
402 })
408 })
@@ -1110,8 +1110,8 b' class PullRequestModel(BaseModel):'
1110 pr_target_repo = pull_request_obj.target_repo
1110 pr_target_repo = pull_request_obj.target_repo
1111
1111
1112 pr_url = h.route_url('pullrequest_show',
1112 pr_url = h.route_url('pullrequest_show',
1113 repo_name=pr_target_repo.repo_name,
1113 repo_name=pr_target_repo.repo_name,
1114 pull_request_id=pull_request_obj.pull_request_id,)
1114 pull_request_id=pull_request_obj.pull_request_id,)
1115
1115
1116 # set some variables for email notification
1116 # set some variables for email notification
1117 pr_target_repo_url = h.route_url(
1117 pr_target_repo_url = h.route_url(
@@ -612,7 +612,8 b' class UserModel(BaseModel):'
612 'password_reset_url': pwd_reset_url,
612 'password_reset_url': pwd_reset_url,
613 'user': user,
613 'user': user,
614 'email': user_email,
614 'email': user_email,
615 'date': datetime.datetime.now()
615 'date': datetime.datetime.now(),
616 'first_admin_email': User.get_first_super_admin().email
616 }
617 }
617
618
618 (subject, headers, email_body,
619 (subject, headers, email_body,
@@ -670,7 +671,8 b' class UserModel(BaseModel):'
670 'new_password': new_passwd,
671 'new_password': new_passwd,
671 'user': user,
672 'user': user,
672 'email': user_email,
673 'email': user_email,
673 'date': datetime.datetime.now()
674 'date': datetime.datetime.now(),
675 'first_admin_email': User.get_first_super_admin().email
674 }
676 }
675
677
676 (subject, headers, email_body,
678 (subject, headers, email_body,
@@ -342,6 +342,8 b' function registerRCRoutes() {'
342 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
342 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
343 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
343 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
345 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
346 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
345 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
347 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
346 pyroutes.register('apiv2', '/_admin/api', []);
348 pyroutes.register('apiv2', '/_admin/api', []);
347 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
349 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
@@ -52,6 +52,7 b''
52 <div class="sidebar">
52 <div class="sidebar">
53 <ul class="nav nav-pills nav-stacked">
53 <ul class="nav nav-pills nav-stacked">
54 <li class="${'active' if c.active=='index' else ''}"><a href="${h.route_path('debug_style_home')}">${_('Index')}</a></li>
54 <li class="${'active' if c.active=='index' else ''}"><a href="${h.route_path('debug_style_home')}">${_('Index')}</a></li>
55 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('debug_style_template', t_path='emails.html')}">${_('Emails')}</a></li>
55 <li class="${'active' if c.active=='typography' else ''}"><a href="${h.route_path('debug_style_template', t_path='typography.html')}">${_('Typography')}</a></li>
56 <li class="${'active' if c.active=='typography' else ''}"><a href="${h.route_path('debug_style_template', t_path='typography.html')}">${_('Typography')}</a></li>
56 <li class="${'active' if c.active=='forms' else ''}"><a href="${h.route_path('debug_style_template', t_path='forms.html')}">${_('Forms')}</a></li>
57 <li class="${'active' if c.active=='forms' else ''}"><a href="${h.route_path('debug_style_template', t_path='forms.html')}">${_('Forms')}</a></li>
57 <li class="${'active' if c.active=='buttons' else ''}"><a href="${h.route_path('debug_style_template', t_path='buttons.html')}">${_('Buttons')}</a></li>
58 <li class="${'active' if c.active=='buttons' else ''}"><a href="${h.route_path('debug_style_template', t_path='buttons.html')}">${_('Buttons')}</a></li>
@@ -2,15 +2,23 b''
2
2
3 ## helpers
3 ## helpers
4 <%def name="tag_button(text, tag_type=None)">
4 <%def name="tag_button(text, tag_type=None)">
5 <%
5 <%
6 color_scheme = {
6 color_scheme = {
7 'default': 'border:1px solid #979797;color:#666666;background-color:#f9f9f9',
7 'default': 'border:1px solid #979797;color:#666666;background-color:#f9f9f9',
8 'approved': 'border:1px solid #0ac878;color:#0ac878;background-color:#f9f9f9',
8 'approved': 'border:1px solid #0ac878;color:#0ac878;background-color:#f9f9f9',
9 'rejected': 'border:1px solid #e85e4d;color:#e85e4d;background-color:#f9f9f9',
9 'rejected': 'border:1px solid #e85e4d;color:#e85e4d;background-color:#f9f9f9',
10 'under_review': 'border:1px solid #ffc854;color:#ffc854;background-color:#f9f9f9',
10 'under_review': 'border:1px solid #ffc854;color:#ffc854;background-color:#f9f9f9',
11 }
11 }
12 %>
12
13 <pre style="display:inline;border-radius:2px;font-size:12px;padding:.2em;${color_scheme.get(tag_type, color_scheme['default'])}">${text}</pre>
13 css_style = ';'.join([
14 'display:inline',
15 'border-radius:2px',
16 'font-size:12px',
17 'padding:.2em',
18 ])
19
20 %>
21 <pre style="${css_style}; ${color_scheme.get(tag_type, color_scheme['default'])}">${text}</pre>
14 </%def>
22 </%def>
15
23
16 <%def name="status_text(text, tag_type=None)">
24 <%def name="status_text(text, tag_type=None)">
@@ -25,6 +33,34 b''
25 <span style="font-weight:bold;font-size:12px;padding:.2em;${color_scheme.get(tag_type, color_scheme['default'])}">${text}</span>
33 <span style="font-weight:bold;font-size:12px;padding:.2em;${color_scheme.get(tag_type, color_scheme['default'])}">${text}</span>
26 </%def>
34 </%def>
27
35
36 <%def name="gravatar_img(email, size=16)">
37 <%
38 css_style = ';'.join([
39 'padding: 0',
40 'margin: -4px 0',
41 'border-radius: 50%',
42 'box-sizing: content-box',
43 'display: inline',
44 'line-height: 1em',
45 'min-width: 16px',
46 'min-height: 16px',
47 ])
48 %>
49
50 <img alt="gravatar" style="${css_style}" src="${h.gravatar_url(email, size)}" height="${size}" width="${size}">
51 </%def>
52
53 <%def name="link_css()">\
54 <%
55 css_style = ';'.join([
56 'color:#427cc9',
57 'text-decoration:none',
58 'cursor:pointer'
59 ])
60 %>\
61 ${css_style}\
62 </%def>
63
28 ## Constants
64 ## Constants
29 <%
65 <%
30 text_regular = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;"
66 text_regular = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;"
@@ -35,8 +71,8 b' text_monospace = "\'Menlo\', \'Liberation M'
35 ## headers we additionally can set for email
71 ## headers we additionally can set for email
36 <%def name="headers()" filter="n,trim"></%def>
72 <%def name="headers()" filter="n,trim"></%def>
37
73
38 <%def name="plaintext_footer()">
74 <%def name="plaintext_footer()" filter="trim">
39 ${_('This is a notification from RhodeCode. %(instance_url)s') % {'instance_url': instance_url}}
75 ${_('This is a notification from RhodeCode.')} ${instance_url}
40 </%def>
76 </%def>
41
77
42 <%def name="body_plaintext()" filter="n,trim">
78 <%def name="body_plaintext()" filter="n,trim">
@@ -52,36 +88,122 b' text_monospace = "\'Menlo\', \'Liberation M'
52 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
88 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
53 <title>${self.subject()}</title>
89 <title>${self.subject()}</title>
54 <style type="text/css">
90 <style type="text/css">
55 /* Based on The MailChimp Reset INLINE: Yes. */
91 /* Based on The MailChimp Reset INLINE: Yes. */
56 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
92 #outlook a {
57 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0; font-family: ${text_regular|n}}
93 padding: 0;
58 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
94 }
59 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
95
60 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
96 /* Force Outlook to provide a "view in browser" menu link. */
61 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
97 body {
62 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
98 width: 100% !important;
99 -webkit-text-size-adjust: 100%;
100 -ms-text-size-adjust: 100%;
101 margin: 0;
102 padding: 0;
103 font-family: ${text_regular|n}
104 }
105
106 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
107 .ExternalClass {
108 width: 100%;
109 }
110
111 /* Force Hotmail to display emails at full width */
112 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {
113 line-height: 100%;
114 }
115
116 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
117 #backgroundTable {
118 margin: 0;
119 padding: 0;
120 line-height: 100% !important;
121 }
122
63 /* End reset */
123 /* End reset */
64
124
65 /* defaults for images*/
125 /* defaults for images*/
66 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
126 img {
67 a img {border:none;}
127 outline: none;
68 .image_fix {display:block;}
128 text-decoration: none;
129 -ms-interpolation-mode: bicubic;
130 }
131
132 a img {
133 border: none;
134 }
135
136 .image_fix {
137 display: block;
138 }
139
140 body {
141 line-height: 1.2em;
142 }
143
144 p {
145 margin: 0 0 20px;
146 }
147
148 h1, h2, h3, h4, h5, h6 {
149 color: #323232 !important;
150 }
151
152 a {
153 color: #427cc9;
154 text-decoration: none;
155 outline: none;
156 cursor: pointer;
157 }
158
159 a:focus {
160 outline: none;
161 }
162
163 a:hover {
164 color: #305b91;
165 }
69
166
70 body {line-height:1.2em;}
167 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
71 p {margin: 0 0 20px;}
168 color: #427cc9 !important;
72 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
169 text-decoration: none !important;
73 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
170 }
74 a:focus {outline:none;}
171
75 a:hover {color: #305b91;}
172 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {
76 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
173 color: #305b91 !important;
77 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
174 }
78 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
175
79 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
176 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
80 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
177 color: #305b91 !important;
81 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
178 }
82 input:focus {outline: 1px solid #979797}
179
180 table {
181 font-size: 13px;
182 border-collapse: collapse;
183 mso-table-lspace: 0pt;
184 mso-table-rspace: 0pt;
185 }
186
187 table td {
188 padding: .65em 1em .65em 0;
189 border-collapse: collapse;
190 vertical-align: top;
191 text-align: left;
192 }
193
194 input {
195 display: inline;
196 border-radius: 2px;
197 border: 1px solid #dbd9da;
198 padding: .5em;
199 }
200
201 input:focus {
202 outline: 1px solid #979797
203 }
204
83 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
205 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
84 /* Put your iPhone 4g styles in here */
206 /* Put your iPhone 4g styles in here */
85 }
207 }
86
208
87 /* Android targeting */
209 /* Android targeting */
@@ -96,6 +218,262 b' text_monospace = "\'Menlo\', \'Liberation M'
96 }
218 }
97 /* end Android targeting */
219 /* end Android targeting */
98
220
221 /** MARKDOWN styling **/
222 div.markdown-block {
223 clear: both;
224 overflow: hidden;
225 margin: 0;
226 padding: 3px 5px 3px
227 }
228
229 div.markdown-block h1, div.markdown-block h2, div.markdown-block h3, div.markdown-block h4, div.markdown-block h5, div.markdown-block h6 {
230 border-bottom: none !important;
231 padding: 0 !important;
232 overflow: visible !important
233 }
234
235 div.markdown-block h1, div.markdown-block h2 {
236 border-bottom: 1px #e6e5e5 solid !important
237 }
238
239 div.markdown-block h1 {
240 font-size: 32px;
241 margin: 15px 0 15px 0 !important;
242 padding-bottom: 5px !important
243 }
244
245 div.markdown-block h2 {
246 font-size: 24px !important;
247 margin: 34px 0 10px 0 !important;
248 padding-top: 15px !important;
249 padding-bottom: 8px !important
250 }
251
252 div.markdown-block h3 {
253 font-size: 18px !important;
254 margin: 30px 0 8px 0 !important;
255 padding-bottom: 2px !important
256 }
257
258 div.markdown-block h4 {
259 font-size: 13px !important;
260 margin: 18px 0 3px 0 !important
261 }
262
263 div.markdown-block h5 {
264 font-size: 12px !important;
265 margin: 15px 0 3px 0 !important
266 }
267
268 div.markdown-block h6 {
269 font-size: 12px;
270 color: #777777;
271 margin: 15px 0 3px 0 !important
272 }
273
274 div.markdown-block hr {
275 border: 0;
276 color: #e6e5e5;
277 background-color: #e6e5e5;
278 height: 3px;
279 margin-bottom: 13px
280 }
281
282 div.markdown-block ol, div.markdown-block ul, div.markdown-block p, div.markdown-block blockquote, div.markdown-block dl, div.markdown-block li, div.markdown-block table {
283 margin: 3px 0 13px 0 !important;
284 color: #424242 !important;
285 font-size: 13px !important;
286 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
287 font-weight: normal !important;
288 overflow: visible !important;
289 line-height: 140% !important
290 }
291
292 div.markdown-block pre {
293 margin: 3px 0 13px 0 !important;
294 padding: .5em;
295 color: #424242 !important;
296 font-size: 13px !important;
297 overflow: visible !important;
298 line-height: 140% !important;
299 background-color: #F5F5F5
300 }
301
302 div.markdown-block img {
303 border-style: none;
304 background-color: #fff;
305 padding-right: 20px;
306 max-width: 100%
307 }
308
309 div.markdown-block strong {
310 font-weight: 600;
311 margin: 0
312 }
313
314 div.markdown-block ul.checkbox, div.markdown-block ol.checkbox {
315 padding-left: 20px !important;
316 margin-top: 0 !important;
317 margin-bottom: 18px !important
318 }
319
320 div.markdown-block ul, div.markdown-block ol {
321 padding-left: 30px !important;
322 margin-top: 0 !important;
323 margin-bottom: 18px !important
324 }
325
326 div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li {
327 list-style: none !important;
328 margin: 6px !important;
329 padding: 0 !important
330 }
331
332 div.markdown-block ul li, div.markdown-block ol li {
333 list-style: disc !important;
334 margin: 6px !important;
335 padding: 0 !important
336 }
337
338 div.markdown-block ol li {
339 list-style: decimal !important
340 }
341
342 div.markdown-block #message {
343 -webkit-border-radius: 2px;
344 -moz-border-radius: 2px;
345 border-radius: 2px;
346 border: 1px solid #dbd9da;
347 display: block;
348 width: 100%;
349 height: 60px;
350 margin: 6px 0
351 }
352
353 div.markdown-block button, div.markdown-block #ws {
354 font-size: 13px;
355 padding: 4px 6px;
356 -webkit-border-radius: 2px;
357 -moz-border-radius: 2px;
358 border-radius: 2px;
359 border: 1px solid #dbd9da;
360 background-color: #eeeeee
361 }
362
363 div.markdown-block code, div.markdown-block pre, div.markdown-block #ws, div.markdown-block #message {
364 font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
365 font-size: 11px;
366 -webkit-border-radius: 2px;
367 -moz-border-radius: 2px;
368 border-radius: 2px;
369 background-color: white;
370 color: #7E7F7F
371 }
372
373 div.markdown-block code {
374 border: 1px solid #eeeeee;
375 margin: 0 2px;
376 padding: 0 5px
377 }
378
379 div.markdown-block pre {
380 border: 1px solid #dbd9da;
381 overflow: auto;
382 padding: .5em;
383 background-color: #F5F5F5
384 }
385
386 div.markdown-block pre > code {
387 border: 0;
388 margin: 0;
389 padding: 0
390 }
391
392 div.rst-block {
393 clear: both;
394 overflow: hidden;
395 margin: 0;
396 padding: 3px 5px 3px
397 }
398
399 div.rst-block h2 {
400 font-weight: normal
401 }
402
403 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
404 border-bottom: 0 !important;
405 margin: 0 !important;
406 padding: 0 !important;
407 line-height: 1.5em !important
408 }
409
410 div.rst-block h1:first-child {
411 padding-top: .25em !important
412 }
413
414 div.rst-block h2, div.rst-block h3 {
415 margin: 1em 0 !important
416 }
417
418 div.rst-block h1, div.rst-block h2 {
419 border-bottom: 1px #e6e5e5 solid !important
420 }
421
422 div.rst-block h2 {
423 margin-top: 1.5em !important;
424 padding-top: .5em !important
425 }
426
427 div.rst-block p {
428 color: black !important;
429 margin: 1em 0 !important;
430 line-height: 1.5em !important
431 }
432
433 div.rst-block ul {
434 list-style: disc !important;
435 margin: 1em 0 1em 2em !important;
436 clear: both
437 }
438
439 div.rst-block ol {
440 list-style: decimal;
441 margin: 1em 0 1em 2em !important
442 }
443
444 div.rst-block pre, div.rst-block code {
445 font: 12px "Bitstream Vera Sans Mono", "Courier", monospace
446 }
447
448 div.rst-block code {
449 font-size: 12px !important;
450 background-color: ghostWhite !important;
451 color: #444 !important;
452 padding: 0 .2em !important;
453 border: 1px solid #dedede !important
454 }
455
456 div.rst-block pre code {
457 padding: 0 !important;
458 font-size: 12px !important;
459 background-color: #eee !important;
460 border: none !important
461 }
462
463 div.rst-block pre {
464 margin: 1em 0;
465 padding: 15px;
466 border: 1px solid #eeeeee;
467 -webkit-border-radius: 2px;
468 -moz-border-radius: 2px;
469 border-radius: 2px;
470 overflow: auto;
471 font-size: 12px;
472 color: #444;
473 background-color: #F5F5F5
474 }
475
476
99 </style>
477 </style>
100
478
101 <!-- Targeting Windows Mobile -->
479 <!-- Targeting Windows Mobile -->
@@ -106,26 +484,30 b' text_monospace = "\'Menlo\', \'Liberation M'
106 <![endif]-->
484 <![endif]-->
107
485
108 <!--[if gte mso 9]>
486 <!--[if gte mso 9]>
109 <style>
487 <style>
110 /* Target Outlook 2007 and 2010 */
488 /* Target Outlook 2007 and 2010 */
111 </style>
489 </style>
112 <![endif]-->
490 <![endif]-->
113 </head>
491 </head>
114 <body>
492 <body>
115 <!-- 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. -->
493 <!-- 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. -->
116 <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">
494 <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">
117 <tr>
495 <tr>
118 <td valign="top" style="padding:0;">
496 <td valign="top" style="padding:0;">
119 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
497 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
120 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
498 <tr>
121 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
499 <td style="width:100%;padding:10px 15px;background-color:#202020" valign="top">
122 ${_('RhodeCode')}
500 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
123 % if rhodecode_instance_name:
501 ${_('RhodeCode')}
124 - ${rhodecode_instance_name}
502 % if rhodecode_instance_name:
125 % endif
503 - ${rhodecode_instance_name}
126 </a>
504 % endif
127 </td></tr>
505 </a>
128 <tr><td style="padding:15px;" valign="top">${self.body()}</td></tr>
506 </td>
507 </tr>
508 <tr>
509 <td style="padding:15px;" valign="top">${self.body()}</td>
510 </tr>
129 </table>
511 </table>
130 </td>
512 </td>
131 </tr>
513 </tr>
@@ -133,10 +515,11 b' text_monospace = "\'Menlo\', \'Liberation M'
133 <!-- End of wrapper table -->
515 <!-- End of wrapper table -->
134
516
135 <div style="clear: both"></div>
517 <div style="clear: both"></div>
136 <p>
518 <div style="margin-left:1%;font-weight:100;font-size:11px;color:#666666;text-decoration:none;font-family:${text_monospace}">
137 <a style="margin-top:15px;margin-left:1%;font-weight:100;font-size:11px;color:#666666;text-decoration:none;font-family:${text_monospace} " href="${instance_url}">
519 ${_('This is a notification from RhodeCode.')}
138 ${self.plaintext_footer()}
520 <a style="font-weight:100;font-size:11px;color:#666666;text-decoration:none;font-family:${text_monospace}" href="${instance_url}">
139 </a>
521 ${instance_url}
140 </p>
522 </a>
523 </div>
141 </body>
524 </body>
142 </html>
525 </html>
@@ -6,24 +6,25 b''
6 <%def name="subject()" filter="n,trim,whitespace_filter">
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),
12 'status': status_change,
11 'status': status_change,
13 'comment_file': comment_file,
12 'comment_file': comment_file,
14 'comment_line': comment_line,
13 'comment_line': comment_line,
15 'comment_type': comment_type,
14 'comment_type': comment_type,
15
16 'commit_id': h.show_id(commit),
16 }
17 }
17 %>
18 %>
18 ${_('[mention]') if mention else ''} \
19
19
20
20 % if comment_file:
21 % if comment_file:
21 ${_('{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)} ${_('in the {repo_name} repository').format(**data) |n}
22 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)} ${_('in the `{repo_name}` repository').format(**data) |n}
22 % else:
23 % else:
23 % if status_change:
24 % if status_change:
24 ${_('[status: {status}] {user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the {repo_name} repository').format(**data) |n}
25 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
25 % else:
26 % else:
26 ${_('{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the {repo_name} repository').format(**data) |n}
27 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
27 % endif
28 % endif
28 % endif
29 % endif
29
30
@@ -35,31 +36,38 b' data = {'
35 data = {
36 data = {
36 'user': h.person(user),
37 'user': h.person(user),
37 'repo_name': repo_name,
38 'repo_name': repo_name,
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
44 'commit_id': h.show_id(commit),
43 }
45 }
44 %>
46 %>
45 ${self.subject()}
46
47
47 * ${_('Comment link')}: ${commit_comment_url}
48 * ${_('Comment link')}: ${commit_comment_url}
48
49
50 %if status_change:
51 * ${_('Commit status')}: ${_('Status was changed to')}: *${status_change}*
52
53 %endif
49 * ${_('Commit')}: ${h.show_id(commit)}
54 * ${_('Commit')}: ${h.show_id(commit)}
50
55
56 * ${_('Commit message')}: ${commit.message}
57
51 %if comment_file:
58 %if comment_file:
52 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
59 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
60
53 %endif
61 %endif
62 % if comment_type == 'todo':
63 ${_('`TODO` comment')}:
64 % else:
65 ${_('`Note` comment')}:
66 % endif
67
68 ${comment_body |n, trim}
54
69
55 ---
70 ---
56
57 %if status_change:
58 ${_('Commit status was changed to')}: *${status_change}*
59 %endif
60
61 ${comment_body|n}
62
63 ${self.plaintext_footer()}
71 ${self.plaintext_footer()}
64 </%def>
72 </%def>
65
73
@@ -67,42 +75,87 b' data = {'
67 <%
75 <%
68 data = {
76 data = {
69 'user': h.person(user),
77 'user': h.person(user),
70 'repo': commit_target_repo,
71 'repo_name': repo_name,
72 'commit_id': h.show_id(commit),
73 'comment_file': comment_file,
78 'comment_file': comment_file,
74 'comment_line': comment_line,
79 'comment_line': comment_line,
75 'comment_type': comment_type,
80 'comment_type': comment_type,
81 'renderer_type': renderer_type or 'plain',
82
83 'repo': commit_target_repo_url,
84 'repo_name': repo_name,
85 'commit_id': h.show_id(commit),
76 }
86 }
77 %>
87 %>
78 <table style="text-align:left;vertical-align:middle;">
88
79 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
89 <table style="text-align:left;vertical-align:middle;width: 100%">
90 <tr>
91 <td style="width:100%;border-bottom:1px solid #dbd9da;">
80
92
81 % if comment_file:
93 <h4 style="margin: 0">
82 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)}</a> ${_('in the {repo} repository').format(**data) |n}</h4>
94 <div style="margin-bottom: 4px; color:#7E7F7F">
83 % else:
95 @${h.person(user.username)}
84 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n}</a> ${_('in the {repo} repository').format(**data) |n}</h4>
96 </div>
85 % endif
97 ${_('left a')}
86 </td></tr>
98 <a href="${commit_comment_url}" style="${base.link_css()}">
99 % if comment_file:
100 ${_('{comment_type} on file `{comment_file}` in commit.').format(**data)}
101 % else:
102 ${_('{comment_type} on commit.').format(**data) |n}
103 % endif
104 </a>
105 <div style="margin-top: 10px"></div>
106 ${_('Commit')} <code>${data['commit_id']}</code> ${_('of repository')}: ${data['repo_name']}
107 </h4>
87
108
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>
109 </td>
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>
110 </tr>
111
112 </table>
113
114 <table style="text-align:left;vertical-align:middle;width: 100%">
115
116 ## spacing def
117 <tr>
118 <td style="width: 130px"></td>
119 <td></td>
120 </tr>
90
121
91 % if status_change:
122 % if status_change:
92 <tr>
123 <tr>
93 <td style="padding-right:20px;">${_('Status')}</td>
124 <td style="padding-right:20px;">${_('Commit Status')}:</td>
94 <td>
125 <td>
95 ${_('The commit status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}
126 ${_('Status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}
96 </td>
127 </td>
97 </tr>
128 </tr>
98 % endif
129 % endif
130
99 <tr>
131 <tr>
100 <td style="padding-right:20px;">
132 <td style="padding-right:20px;">${_('Commit')}:</td>
133 <td>
134 <a href="${commit_comment_url}" style="${base.link_css()}">${h.show_id(commit)}</a>
135 </td>
136 </tr>
137 <tr>
138 <td style="padding-right:20px;">${_('Commit message')}:</td>
139 <td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td>
140 </tr>
141
142 % if comment_file:
143 <tr>
144 <td style="padding-right:20px;">${_('File')}:</td>
145 <td><a href="${commit_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
146 </tr>
147 % endif
148
149 <tr style="background-image: linear-gradient(to right, black 33%, rgba(255,255,255,0) 0%);background-position: bottom;background-size: 3px 1px;background-repeat: repeat-x;">
150 <td colspan="2" style="padding-right:20px;">
101 % if comment_type == 'todo':
151 % if comment_type == 'todo':
102 ${(_('TODO comment on line: {comment_line}') if comment_file else _('TODO comment')).format(**data)}
152 ${_('`TODO` comment')}:
103 % else:
153 % else:
104 ${(_('Note comment on line: {comment_line}') if comment_file else _('Note comment')).format(**data)}
154 ${_('`Note` comment')}:
105 % endif
155 % endif
106 </td>
156 </td>
107 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td></tr>
157 </tr>
158
159 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
160 </tr>
108 </table>
161 </table>
@@ -1,5 +1,6 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
4
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode test email: ${h.format_date(date)}
6 RhodeCode test email: ${h.format_date(date)}
@@ -7,7 +8,13 b' RhodeCode test email: ${h.format_date(da'
7
8
8 ## plain text version of the email. Empty by default
9 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
10 <%def name="body_plaintext()" filter="n,trim">
10 Test Email from RhodeCode version: ${rhodecode_version}, sent by: ${user}
11 Test Email from RhodeCode version: ${rhodecode_version}
12 Email sent by: ${h.person(user)}
13
14 ---
15 ${self.plaintext_footer()}
11 </%def>
16 </%def>
12
17
13 ${body_plaintext()} No newline at end of file
18 Test Email from RhodeCode version: ${rhodecode_version}
19 <br/><br/>
20 Email sent by: <strong>${h.person(user)}</strong>
@@ -9,13 +9,13 b''
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 ${body}
10 ${body}
11
11
12 ---
12 ${self.plaintext_footer()}
13 ${self.plaintext_footer()}
13 </%def>
14 </%def>
14
15
15 ## BODY GOES BELOW
16 ## BODY GOES BELOW
16 <table style="text-align:left;vertical-align:top;">
17 <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>
18 <tr>
19 <td style="padding-right:20px;padding-top:15px;white-space:pre-wrap">${body}</td>
20 </tr>
18 </table>
21 </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}">
20 ${self.plaintext_footer()}
21 </a></p> No newline at end of file
@@ -1,5 +1,6 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
4
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode Password reset
6 RhodeCode Password reset
@@ -7,16 +8,18 b' RhodeCode Password reset'
7
8
8 ## plain text version of the email. Empty by default
9 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
10 <%def name="body_plaintext()" filter="n,trim">
10 Hi ${user.username},
11 Hello ${user.username},
11
12
12 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
13 On ${h.format_date(date)} there was a request to reset your password using the email address `${email}`
13
14
14 *If you didn't do this, please contact your RhodeCode administrator.*
15 *If you did not request a password reset, please contact your RhodeCode administrator at: ${first_admin_email}*
15
16
16 You can continue, and generate new password by clicking following URL:
17 You can continue, and generate new password by clicking following URL:
17 ${password_reset_url}
18 ${password_reset_url}
18
19
19 This link will be active for 10 minutes.
20 This link will be active for 10 minutes.
21
22 ---
20 ${self.plaintext_footer()}
23 ${self.plaintext_footer()}
21 </%def>
24 </%def>
22
25
@@ -24,10 +27,11 b' This link will be active for 10 minutes.'
24 <p>
27 <p>
25 Hello ${user.username},
28 Hello ${user.username},
26 </p><p>
29 </p><p>
27 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
30 On ${h.format_date(date)} there was a request to reset your password using the email address `${email}`
28 <br/>
31 <br/><br/>
29 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
32 <strong>If you did not request a password reset, please contact your RhodeCode administrator at: ${first_admin_email}.</strong>
30 </p><p>
33 </p><p>
31 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
34 You can continue, and generate new password by clicking following URL:<br/><br/>
32 This link will be active for 10 minutes.
35 <a href="${password_reset_url}" style="${base.link_css()}">${password_reset_url}</a>
36 <br/><br/>This link will be active for 10 minutes.
33 </p>
37 </p>
@@ -1,5 +1,6 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
4
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
5 Your new RhodeCode password
6 Your new RhodeCode password
@@ -7,14 +8,15 b' Your new RhodeCode password'
7
8
8 ## plain text version of the email. Empty by default
9 ## plain text version of the email. Empty by default
9 <%def name="body_plaintext()" filter="n,trim">
10 <%def name="body_plaintext()" filter="n,trim">
10 Hi ${user.username},
11 Hello ${user.username},
11
12
12 Below is your new access password for RhodeCode.
13 Below is your new access password for RhodeCode requested via password reset link.
13
14
14 *If you didn't do this, please contact your RhodeCode administrator.*
15 *If you did not request a password reset, please contact your RhodeCode administrator at: ${first_admin_email}.*
15
16
16 password: ${new_password}
17 new password: ${new_password}
17
18
19 ---
18 ${self.plaintext_footer()}
20 ${self.plaintext_footer()}
19 </%def>
21 </%def>
20
22
@@ -22,8 +24,8 b' password: ${new_password}'
22 <p>
24 <p>
23 Hello ${user.username},
25 Hello ${user.username},
24 </p><p>
26 </p><p>
25 Below is your new access password for RhodeCode.
27 Below is your new access password for RhodeCode requested via password reset link.
26 <br/>
28 <br/><br/>
27 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
29 <strong>If you did not request a password reset, please contact your RhodeCode administrator at: ${first_admin_email}.</strong>
28 </p>
30 </p>
29 <p>password: <pre>${new_password}</pre>
31 <p>new password: <code>${new_password}</code>
@@ -6,27 +6,29 b''
6 <%def name="subject()" filter="n,trim,whitespace_filter">
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 'repo_name': repo_name,
11 'pr_id': pull_request.pull_request_id,
12 'status': status_change,
11 'status': status_change,
13 'comment_file': comment_file,
12 'comment_file': comment_file,
14 'comment_line': comment_line,
13 'comment_line': comment_line,
15 'comment_type': comment_type,
14 'comment_type': comment_type,
15
16 'pr_title': pull_request.title,
17 'pr_id': pull_request.pull_request_id,
16 }
18 }
17 %>
19 %>
18
20
19 ${(_('[mention]') if mention else '')} \
20
21
21 % if comment_file:
22 % if comment_file:
22 ${_('{user} left a {comment_type} on file `{comment_file}` in pull request #{pr_id} "{pr_title}"').format(**data) |n}
23 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data) |n}
23 % else:
24 % else:
24 % if status_change:
25 % if status_change:
25 ${_('[status: {status}] {user} left a {comment_type} on pull request #{pr_id} "{pr_title}"').format(**data) |n}
26 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
26 % else:
27 % else:
27 ${_('{user} left a {comment_type} on pull request #{pr_id} "{pr_title}"').format(**data) |n}
28 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
28 % endif
29 % endif
29 % endif
30 % endif
31
30 </%def>
32 </%def>
31
33
32 ## PLAINTEXT VERSION OF BODY
34 ## PLAINTEXT VERSION OF BODY
@@ -34,34 +36,51 b' data = {'
34 <%
36 <%
35 data = {
37 data = {
36 'user': h.person(user),
38 'user': h.person(user),
37 'pr_title': pull_request.title,
39 'repo_name': repo_name,
38 'pr_id': pull_request.pull_request_id,
39 'status': status_change,
40 'status': status_change,
40 'comment_file': comment_file,
41 'comment_file': comment_file,
41 'comment_line': comment_line,
42 'comment_line': comment_line,
42 'comment_type': comment_type,
43 'comment_type': comment_type,
44
45 'pr_title': pull_request.title,
46 'pr_id': pull_request.pull_request_id,
47 'source_ref_type': pull_request.source_ref_parts.type,
48 'source_ref_name': pull_request.source_ref_parts.name,
49 'target_ref_type': pull_request.target_ref_parts.type,
50 'target_ref_name': pull_request.target_ref_parts.name,
51 'source_repo': pull_request_source_repo.repo_name,
52 'target_repo': pull_request_target_repo.repo_name,
53 'source_repo_url': pull_request_source_repo_url,
54 'target_repo_url': pull_request_target_repo_url,
43 }
55 }
44 %>
56 %>
45 ${self.subject()}
57
58 ${h.literal(_('Pull request !{pr_id}: `{pr_title}`').format(**data))}
59
60 * ${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))}
46
61
47 * ${_('Comment link')}: ${pr_comment_url}
62 * ${_('Comment link')}: ${pr_comment_url}
48
63
49 * ${_('Source repository')}: ${pr_source_repo_url}
64 %if status_change and not closing_pr:
65 * ${_('{user} submitted pull request !{pr_id} status: *{status}*').format(**data)}
50
66
67 %elif status_change and closing_pr:
68 * ${_('{user} submitted pull request !{pr_id} status: *{status} and closed*').format(**data)}
69
70 %endif
51 %if comment_file:
71 %if comment_file:
52 * ${_('File: {comment_file} on line {comment_line}').format(comment_file=comment_file, comment_line=comment_line)}
72 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
73
53 %endif
74 %endif
75 % if comment_type == 'todo':
76 ${_('`TODO` comment')}:
77 % else:
78 ${_('`Note` comment')}:
79 % endif
80
81 ${comment_body |n, trim}
54
82
55 ---
83 ---
56
57 %if status_change and not closing_pr:
58 ${_('{user} submitted pull request #{pr_id} status: *{status}*').format(**data)}
59 %elif status_change and closing_pr:
60 ${_('{user} submitted pull request #{pr_id} status: *{status} and closed*').format(**data)}
61 %endif
62
63 ${comment_body |n}
64
65 ${self.plaintext_footer()}
84 ${self.plaintext_footer()}
66 </%def>
85 </%def>
67
86
@@ -69,46 +88,104 b' data = {'
69 <%
88 <%
70 data = {
89 data = {
71 'user': h.person(user),
90 'user': h.person(user),
72 'pr_title': pull_request.title,
73 'pr_id': pull_request.pull_request_id,
74 'status': status_change,
75 'comment_file': comment_file,
91 'comment_file': comment_file,
76 'comment_line': comment_line,
92 'comment_line': comment_line,
77 'comment_type': comment_type,
93 'comment_type': comment_type,
94 'renderer_type': renderer_type or 'plain',
95
96 'pr_title': pull_request.title,
97 'pr_id': pull_request.pull_request_id,
98 'status': status_change,
99 'source_ref_type': pull_request.source_ref_parts.type,
100 'source_ref_name': pull_request.source_ref_parts.name,
101 'target_ref_type': pull_request.target_ref_parts.type,
102 'target_ref_name': pull_request.target_ref_parts.name,
103 'source_repo': pull_request_source_repo.repo_name,
104 'target_repo': pull_request_target_repo.repo_name,
105 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
106 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
78 }
107 }
79 %>
108 %>
80 <table style="text-align:left;vertical-align:middle;">
109
81 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
110 <table style="text-align:left;vertical-align:middle;width: 100%">
111 <tr>
112 <td style="width:100%;border-bottom:1px solid #dbd9da;">
82
113
83 % if comment_file:
114 <h4 style="margin: 0">
84 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('{user} left a {comment_type} on file `{comment_file}` in pull request #{pr_id} "{pr_title}"').format(**data) |n}</a></h4>
115 <div style="margin-bottom: 4px; color:#7E7F7F">
85 % else:
116 @${h.person(user.username)}
86 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('{user} left a {comment_type} on pull request #{pr_id} "{pr_title}"').format(**data) |n}</a></h4>
117 </div>
87 % endif
118 ${_('left a')}
119 <a href="${pr_comment_url}" style="${base.link_css()}">
120 % if comment_file:
121 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
122 % else:
123 ${_('{comment_type} on pull request.').format(**data) |n}
124 % endif
125 </a>
126 <div style="margin-top: 10px"></div>
127 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
128 </h4>
88
129
89 </td></tr>
130 </td>
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>
131 </tr>
132
133 </table>
134
135 <table style="text-align:left;vertical-align:middle;width: 100%">
136
137 ## spacing def
138 <tr>
139 <td style="width: 130px"></td>
140 <td></td>
141 </tr>
91
142
92 % if status_change:
143 % if status_change:
144 <tr>
145 <td style="padding-right:20px;">${_('Review Status')}:</td>
146 <td>
147 % if closing_pr:
148 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
149 % else:
150 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
151 % endif
152 </td>
153 </tr>
154 % endif
155
156 <tr>
157 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
158 <td style="line-height:20px;">
159 ${base.tag_button('{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name))} ${_('of')} ${data['source_repo_url']}
160 &rarr;
161 ${base.tag_button('{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name))} ${_('of')} ${data['target_repo_url']}
162 </td>
163 </tr>
164 <tr>
165 <td style="padding-right:20px;">${_('Pull request')}:</td>
166 <td>
167 <a href="${pull_request_url}" style="${base.link_css()}">
168 !${pull_request.pull_request_id}
169 </a>
170 </td>
171 </tr>
172 % if comment_file:
93 <tr>
173 <tr>
94 <td style="padding-right:20px;">${_('Status')}</td>
174 <td style="padding-right:20px;">${_('File')}:</td>
95 <td>
175 <td><a href="${pr_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
96 % if closing_pr:
97 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
98 % else:
99 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
100 % endif
101 </td>
102 </tr>
176 </tr>
103 % endif
177 % endif
104 <tr>
178
105 <td style="padding-right:20px;">
179 <tr style="background-image: linear-gradient(to right, black 33%, rgba(255,255,255,0) 0%);background-position: bottom;background-size: 3px 1px;background-repeat: repeat-x;">
180 <td colspan="2" style="padding-right:20px;">
106 % if comment_type == 'todo':
181 % if comment_type == 'todo':
107 ${(_('TODO comment on line: {comment_line}') if comment_file else _('TODO comment')).format(**data)}
182 ${_('`TODO` comment')}:
108 % else:
183 % else:
109 ${(_('Note comment on line: {comment_line}') if comment_file else _('Note comment')).format(**data)}
184 ${_('`Note` comment')}:
110 % endif
185 % endif
111 </td>
186 </td>
112 <td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td>
187 </tr>
188
189 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
113 </tr>
190 </tr>
114 </table>
191 </table>
@@ -2,19 +2,20 b''
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 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%
7 <%
7 data = {
8 data = {
8 'user': h.person(user),
9 'user': '@'+h.person(user),
9 'pr_id': pull_request.pull_request_id,
10 'pr_id': pull_request.pull_request_id,
10 'pr_title': pull_request.title,
11 'pr_title': pull_request.title,
11 }
12 }
12 %>
13 %>
13
14
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
15 ${_('{user} requested a pull request review. !{pr_id}: "{pr_title}"').format(**data) |n}
15 </%def>
16 </%def>
16
17
17
18 ## PLAINTEXT VERSION OF BODY
18 <%def name="body_plaintext()" filter="n,trim">
19 <%def name="body_plaintext()" filter="n,trim">
19 <%
20 <%
20 data = {
21 data = {
@@ -25,32 +26,36 b' data = {'
25 'source_ref_name': pull_request.source_ref_parts.name,
26 'source_ref_name': pull_request.source_ref_parts.name,
26 'target_ref_type': pull_request.target_ref_parts.type,
27 'target_ref_type': pull_request.target_ref_parts.type,
27 'target_ref_name': pull_request.target_ref_parts.name,
28 'target_ref_name': pull_request.target_ref_parts.name,
28 'repo_url': pull_request_source_repo_url
29 'repo_url': pull_request_source_repo_url,
30 'source_repo': pull_request_source_repo.repo_name,
31 'target_repo': pull_request_target_repo.repo_name,
32 'source_repo_url': pull_request_source_repo_url,
33 'target_repo_url': pull_request_target_repo_url,
29 }
34 }
30 %>
35 %>
31 ${self.subject()}
32
36
37 ${h.literal(_('Pull request !{pr_id}: `{pr_title}`').format(**data))}
33
38
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)}
39 * ${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))}
35
40
36
41 * ${_('Pull Request link')}: ${pull_request_url}
37 * ${_('Link')}: ${pull_request_url}
38
42
39 * ${_('Title')}: ${pull_request.title}
43 * ${_('Title')}: ${pull_request.title}
40
44
41 * ${_('Description')}:
45 * ${_('Description')}:
42
46
43 ${pull_request.description}
47 ${pull_request.description | trim}
44
48
45
49
46 * ${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
50 * ${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
47
51
48 % for commit_id, message in pull_request_commits:
52 % for commit_id, message in pull_request_commits:
49 - ${h.short_id(commit_id)}
53 - ${h.short_id(commit_id)}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
54 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
51
55
52 % endfor
56 % endfor
53
57
58 ---
54 ${self.plaintext_footer()}
59 ${self.plaintext_footer()}
55 </%def>
60 </%def>
56 <%
61 <%
@@ -63,23 +68,77 b' data = {'
63 'target_ref_type': pull_request.target_ref_parts.type,
68 'target_ref_type': pull_request.target_ref_parts.type,
64 'target_ref_name': pull_request.target_ref_parts.name,
69 'target_ref_name': pull_request.target_ref_parts.name,
65 'repo_url': pull_request_source_repo_url,
70 'repo_url': pull_request_source_repo_url,
71 'source_repo': pull_request_source_repo.repo_name,
72 'target_repo': pull_request_target_repo.repo_name,
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
73 '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)
74 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
68 }
75 }
69 %>
76 %>
70 <table style="text-align:left;vertical-align:middle;">
77
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>
78 <table style="text-align:left;vertical-align:middle;width: 100%">
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
79 <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>
80 <td style="width:100%;border-bottom:1px solid #dbd9da;">
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>
81
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
82 <h4 style="margin: 0">
76 <tr><td style="padding-right:20px;">${_ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
83 <div style="margin-bottom: 4px; color:#7E7F7F">
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
84 @${h.person(user.username)}
78 % for commit_id, message in pull_request_commits:
85 </div>
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
86 ${_('requested a')}
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
87 <a href="${pull_request_url}" style="${base.link_css()}">
81 </li>
88 ${_('pull request review.').format(**data) }
82 % endfor
89 </a>
83 </ol></td>
90 <div style="margin-top: 10px"></div>
91 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
92 </h4>
93
94 </td>
95 </tr>
96
97 </table>
98
99 <table style="text-align:left;vertical-align:middle;width: 100%">
100 ## spacing def
101 <tr>
102 <td style="width: 130px"></td>
103 <td></td>
104 </tr>
105
106 <tr>
107 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
108 <td style="line-height:20px;">
109 ${base.tag_button('{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name))} ${_('of')} ${data['source_repo_url']}
110 &rarr;
111 ${base.tag_button('{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name))} ${_('of')} ${data['target_repo_url']}
112 </td>
113 </tr>
114
115 <tr>
116 <td style="padding-right:20px;">${_('Pull request')}:</td>
117 <td>
118 <a href="${pull_request_url}" style="${base.link_css()}">
119 !${pull_request.pull_request_id}
120 </a>
121 </td>
122 </tr>
123 <tr>
124 <td style="padding-right:20px;">${_('Description')}:</td>
125 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
126 </tr>
127 <tr>
128 <td style="padding-right:20px;">${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits)) % {'num': len(pull_request_commits)}}:</td>
129 <td></td>
130 </tr>
131
132 <tr>
133 <td colspan="2">
134 <ol style="margin:0 0 0 1em;padding:0;text-align:left;">
135 % for commit_id, message in pull_request_commits:
136 <li style="margin:0 0 1em;">
137 <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 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
139 </li>
140 % endfor
141 </ol>
142 </td>
84 </tr>
143 </tr>
85 </table>
144 </table>
@@ -15,7 +15,8 b' Email Plaintext Body'
15 </%def>
15 </%def>
16
16
17 ## BODY GOES BELOW
17 ## BODY GOES BELOW
18 <b>Email Body</b>
18 <strong>Email Body</strong>
19
19 <br/>
20 ${h.short_id('0' * 40)}
20 <br/>
21 ${_('Translation')} No newline at end of file
21 `h.short_id()`: ${h.short_id('0' * 40)}<br/>
22 ${_('Translation String')}<br/>
@@ -1,5 +1,6 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
4
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode new user registration: ${user.username}
6 RhodeCode new user registration: ${user.username}
@@ -14,14 +15,45 b' A new user `${user.username}` has regist'
14 - Email: ${user.email}
15 - Email: ${user.email}
15 - Profile link: ${h.route_url('user_profile', username=user.username)}
16 - Profile link: ${h.route_url('user_profile', username=user.username)}
16
17
18 ---
17 ${self.plaintext_footer()}
19 ${self.plaintext_footer()}
18 </%def>
20 </%def>
19
21
20 ## BODY GOES BELOW
22
21 <table style="text-align:left;vertical-align:middle;">
23 <table style="text-align:left;vertical-align:middle;width: 100%">
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>
24 <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>
25 <td style="width:100%;border-bottom:1px solid #dbd9da;">
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.first_name} ${user.last_name}</td></tr>
26 <h4 style="margin: 0">
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
27 <a href="${h.route_url('user_profile', username=user.username)}" style="${base.link_css()}">
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>
28 ${_('New user {user} has registered on {date}').format(user=user.username, date=h.format_date(date))}
27 </table> No newline at end of file
29 </a>
30 </h4>
31 </td>
32 </tr>
33 </table>
34
35 <table style="text-align:left;vertical-align:middle;width: 100%">
36 ## spacing def
37 <tr>
38 <td style="width: 130px"></td>
39 <td></td>
40 </tr>
41 <tr>
42 <td style="padding-right:20px;padding-top:20px;">${_('Username')}:</td>
43 <td style="line-height:1;padding-top:20px;">${user.username}</td>
44 </tr>
45 <tr>
46 <td style="padding-right:20px;">${_('Full Name')}:</td>
47 <td>${user.first_name} ${user.last_name}</td>
48 </tr>
49 <tr>
50 <td style="padding-right:20px;">${_('Email')}:</td>
51 <td>${user.email}</td>
52 </tr>
53 <tr>
54 <td style="padding-right:20px;">${_('Profile')}:</td>
55 <td>
56 <a href="${h.route_url('user_profile', username=user.username)}">${h.route_url('user_profile', username=user.username)}</a>
57 </td>
58 </tr>
59 </table>
@@ -1,4 +1,3 b''
1 import collections
2 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
3
2
4 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
@@ -20,9 +19,11 b' import collections'
20 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
21
20
22 import pytest
21 import pytest
22 import collections
23
23
24 from rhodecode.lib.partial_renderer import PyramidPartialRenderer
24 from rhodecode.lib.partial_renderer import PyramidPartialRenderer
25 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.lib.utils2 import AttributeDict
26 from rhodecode.model.db import User
26 from rhodecode.model.notification import EmailNotificationModel
27 from rhodecode.model.notification import EmailNotificationModel
27
28
28
29
@@ -47,29 +48,26 b' def test_render_email(app, http_host_onl'
47 assert body_plaintext == 'Email Plaintext Body'
48 assert body_plaintext == 'Email Plaintext Body'
48
49
49 # body
50 # body
50 notification_footer = 'This is a notification from RhodeCode. http://%s/' \
51 notification_footer1 = 'This is a notification from RhodeCode.'
51 % http_host_only_stub
52 notification_footer2 = 'http://{}/'.format(http_host_only_stub)
52 assert notification_footer in body
53 assert notification_footer1 in body
54 assert notification_footer2 in body
53 assert 'Email Body' in body
55 assert 'Email Body' in body
54
56
55
57
56 def test_render_pr_email(app, user_admin):
58 def test_render_pr_email(app, user_admin):
57
59 ref = collections.namedtuple(
58 ref = collections.namedtuple('Ref',
60 'Ref', 'name, type')('fxies123', 'book')
59 'name, type')(
60 'fxies123', 'book'
61 )
62
61
63 pr = collections.namedtuple('PullRequest',
62 pr = collections.namedtuple('PullRequest',
64 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
63 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
65 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
64 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
66
65
67 source_repo = target_repo = collections.namedtuple('Repo',
66 source_repo = target_repo = collections.namedtuple(
68 'type, repo_name')(
67 'Repo', 'type, repo_name')('hg', 'pull_request_1')
69 'hg', 'pull_request_1')
70
68
71 kwargs = {
69 kwargs = {
72 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
70 'user': User.get_first_super_admin(),
73 'pull_request': pr,
71 'pull_request': pr,
74 'pull_request_commits': [],
72 'pull_request_commits': [],
75
73
@@ -86,7 +84,7 b' def test_render_pr_email(app, user_admin'
86 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
84 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
87
85
88 # subject
86 # subject
89 assert subject == 'Marcin Kuzminski wants you to review pull request #200: "Example Pull Request"'
87 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
90
88
91
89
92 @pytest.mark.parametrize('mention', [
90 @pytest.mark.parametrize('mention', [
@@ -98,24 +96,21 b' def test_render_pr_email(app, user_admin'
98 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
96 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
99 ])
97 ])
100 def test_render_comment_subject_no_newlines(app, mention, email_type):
98 def test_render_comment_subject_no_newlines(app, mention, email_type):
101 ref = collections.namedtuple('Ref',
99 ref = collections.namedtuple(
102 'name, type')(
100 'Ref', 'name, type')('fxies123', 'book')
103 'fxies123', 'book'
104 )
105
101
106 pr = collections.namedtuple('PullRequest',
102 pr = collections.namedtuple('PullRequest',
107 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
103 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
108 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
104 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
109
105
110 source_repo = target_repo = collections.namedtuple('Repo',
106 source_repo = target_repo = collections.namedtuple(
111 'type, repo_name')(
107 'Repo', 'type, repo_name')('hg', 'pull_request_1')
112 'hg', 'pull_request_1')
113
108
114 kwargs = {
109 kwargs = {
115 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
110 'user': User.get_first_super_admin(),
116 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
111 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
117 'status_change': 'approved',
112 'status_change': 'approved',
118 'commit_target_repo': AttributeDict(),
113 'commit_target_repo_url': 'http://foo.example.com/#comment1',
119 'repo_name': 'test-repo',
114 'repo_name': 'test-repo',
120 'comment_file': 'test-file.py',
115 'comment_file': 'test-file.py',
121 'comment_line': 'n100',
116 'comment_line': 'n100',
@@ -126,8 +121,6 b' def test_render_comment_subject_no_newli'
126 'mention': mention,
121 'mention': mention,
127
122
128 'pr_comment_url': 'http://comment-url',
123 'pr_comment_url': 'http://comment-url',
129 'pr_source_repo': AttributeDict(repo_name='foobar'),
130 'pr_source_repo_url': 'http://soirce-repo/url',
131 'pull_request': pr,
124 'pull_request': pr,
132 'pull_request_commits': [],
125 'pull_request_commits': [],
133
126
@@ -136,6 +129,8 b' def test_render_comment_subject_no_newli'
136
129
137 'pull_request_source_repo': source_repo,
130 'pull_request_source_repo': source_repo,
138 'pull_request_source_repo_url': 'x',
131 'pull_request_source_repo_url': 'x',
132
133 'pull_request_url': 'http://code.rc.com/_pr/123'
139 }
134 }
140 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
135 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
141 email_type, **kwargs)
136 email_type, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now