##// END OF EJS Templates
emails: updated emails design and data structure they provide....
dan -
r4038:4a4a02a9 default
parent child
Show More
@@ -0,0 +1,29
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
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
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 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
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
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
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
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 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 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;}