Show More
@@ -0,0 +1,32 b'' | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%inherit file="base.mako"/> | |||
|
3 | ||||
|
4 | <%def name="subject()" filter="n,trim,whitespace_filter"> | |||
|
5 | New Version of RhodeCode is available ! | |||
|
6 | </%def> | |||
|
7 | ||||
|
8 | ## plain text version of the email. Empty by default | |||
|
9 | <%def name="body_plaintext()" filter="n,trim"> | |||
|
10 | A new version of RhodeCode is available! | |||
|
11 | ||||
|
12 | Your version: ${current_ver} | |||
|
13 | New version: ${latest_ver} | |||
|
14 | ||||
|
15 | Release notes: | |||
|
16 | ||||
|
17 | https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html | |||
|
18 | </%def> | |||
|
19 | ||||
|
20 | ## BODY GOES BELOW | |||
|
21 | ||||
|
22 | <h3>A new version of RhodeCode is available!</h3> | |||
|
23 | <br/> | |||
|
24 | Your version: ${current_ver}<br/> | |||
|
25 | New version: <strong>${latest_ver}</strong><br/> | |||
|
26 | ||||
|
27 | <h4>Release notes</h4> | |||
|
28 | ||||
|
29 | <a href="https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html"> | |||
|
30 | https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html | |||
|
31 | </a> | |||
|
32 |
@@ -1,471 +1,476 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2016-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import os |
|
21 | import os | |
22 | import logging |
|
22 | import logging | |
23 | import datetime |
|
23 | import datetime | |
24 |
|
24 | |||
25 | from pyramid.renderers import render_to_response |
|
25 | from pyramid.renderers import render_to_response | |
26 | from rhodecode.apps._base import BaseAppView |
|
26 | from rhodecode.apps._base import BaseAppView | |
27 | from rhodecode.lib.celerylib import run_task, tasks |
|
27 | from rhodecode.lib.celerylib import run_task, tasks | |
28 | from rhodecode.lib.utils2 import AttributeDict |
|
28 | from rhodecode.lib.utils2 import AttributeDict | |
29 | from rhodecode.model.db import User |
|
29 | from rhodecode.model.db import User | |
30 | from rhodecode.model.notification import EmailNotificationModel |
|
30 | from rhodecode.model.notification import EmailNotificationModel | |
31 |
|
31 | |||
32 | log = logging.getLogger(__name__) |
|
32 | log = logging.getLogger(__name__) | |
33 |
|
33 | |||
34 |
|
34 | |||
35 | class DebugStyleView(BaseAppView): |
|
35 | class DebugStyleView(BaseAppView): | |
36 |
|
36 | |||
37 | def load_default_context(self): |
|
37 | def load_default_context(self): | |
38 | c = self._get_local_tmpl_context() |
|
38 | c = self._get_local_tmpl_context() | |
39 | return c |
|
39 | return c | |
40 |
|
40 | |||
41 | def index(self): |
|
41 | def index(self): | |
42 | c = self.load_default_context() |
|
42 | c = self.load_default_context() | |
43 | c.active = 'index' |
|
43 | c.active = 'index' | |
44 |
|
44 | |||
45 | return render_to_response( |
|
45 | return render_to_response( | |
46 | 'debug_style/index.html', self._get_template_context(c), |
|
46 | 'debug_style/index.html', self._get_template_context(c), | |
47 | request=self.request) |
|
47 | request=self.request) | |
48 |
|
48 | |||
49 | def render_email(self): |
|
49 | def render_email(self): | |
50 | c = self.load_default_context() |
|
50 | c = self.load_default_context() | |
51 | email_id = self.request.matchdict['email_id'] |
|
51 | email_id = self.request.matchdict['email_id'] | |
52 | c.active = 'emails' |
|
52 | c.active = 'emails' | |
53 |
|
53 | |||
54 | pr = AttributeDict( |
|
54 | pr = AttributeDict( | |
55 | pull_request_id=123, |
|
55 | pull_request_id=123, | |
56 | title='digital_ocean: fix redis, elastic search start on boot, ' |
|
56 | title='digital_ocean: fix redis, elastic search start on boot, ' | |
57 | 'fix fd limits on supervisor, set postgres 11 version', |
|
57 | 'fix fd limits on supervisor, set postgres 11 version', | |
58 | description=''' |
|
58 | description=''' | |
59 | Check if we should use full-topic or mini-topic. |
|
59 | Check if we should use full-topic or mini-topic. | |
60 |
|
60 | |||
61 | - full topic produces some problems with merge states etc |
|
61 | - full topic produces some problems with merge states etc | |
62 | - server-mini-topic needs probably tweeks. |
|
62 | - server-mini-topic needs probably tweeks. | |
63 | ''', |
|
63 | ''', | |
64 | repo_name='foobar', |
|
64 | repo_name='foobar', | |
65 | source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'), |
|
65 | source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'), | |
66 | target_ref_parts=AttributeDict(type='branch', name='master'), |
|
66 | target_ref_parts=AttributeDict(type='branch', name='master'), | |
67 | ) |
|
67 | ) | |
68 |
|
68 | |||
69 | target_repo = AttributeDict(repo_name='repo_group/target_repo') |
|
69 | target_repo = AttributeDict(repo_name='repo_group/target_repo') | |
70 | source_repo = AttributeDict(repo_name='repo_group/source_repo') |
|
70 | source_repo = AttributeDict(repo_name='repo_group/source_repo') | |
71 | user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user |
|
71 | user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user | |
72 | # file/commit changes for PR update |
|
72 | # file/commit changes for PR update | |
73 | commit_changes = AttributeDict({ |
|
73 | commit_changes = AttributeDict({ | |
74 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], |
|
74 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], | |
75 | 'removed': ['eeeeeeeeeee'], |
|
75 | 'removed': ['eeeeeeeeeee'], | |
76 | }) |
|
76 | }) | |
77 |
|
77 | |||
78 | file_changes = AttributeDict({ |
|
78 | file_changes = AttributeDict({ | |
79 | 'added': ['a/file1.md', 'file2.py'], |
|
79 | 'added': ['a/file1.md', 'file2.py'], | |
80 | 'modified': ['b/modified_file.rst'], |
|
80 | 'modified': ['b/modified_file.rst'], | |
81 | 'removed': ['.idea'], |
|
81 | 'removed': ['.idea'], | |
82 | }) |
|
82 | }) | |
83 |
|
83 | |||
84 | exc_traceback = { |
|
84 | exc_traceback = { | |
85 | 'exc_utc_date': '2020-03-26T12:54:50.683281', |
|
85 | 'exc_utc_date': '2020-03-26T12:54:50.683281', | |
86 | 'exc_id': 139638856342656, |
|
86 | 'exc_id': 139638856342656, | |
87 | 'exc_timestamp': '1585227290.683288', |
|
87 | 'exc_timestamp': '1585227290.683288', | |
88 | 'version': 'v1', |
|
88 | 'version': 'v1', | |
89 | 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n', |
|
89 | 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n', | |
90 | 'exc_type': 'AttributeError' |
|
90 | 'exc_type': 'AttributeError' | |
91 | } |
|
91 | } | |
92 |
|
92 | |||
93 | email_kwargs = { |
|
93 | email_kwargs = { | |
94 | 'test': {}, |
|
94 | 'test': {}, | |
95 |
|
95 | |||
96 | 'message': { |
|
96 | 'message': { | |
97 | 'body': 'message body !' |
|
97 | 'body': 'message body !' | |
98 | }, |
|
98 | }, | |
99 |
|
99 | |||
100 | 'email_test': { |
|
100 | 'email_test': { | |
101 | 'user': user, |
|
101 | 'user': user, | |
102 | 'date': datetime.datetime.now(), |
|
102 | 'date': datetime.datetime.now(), | |
103 | }, |
|
103 | }, | |
104 |
|
104 | |||
|
105 | 'update_available': { | |||
|
106 | 'current_ver': '4.23.0', | |||
|
107 | 'latest_ver': '4.24.0', | |||
|
108 | }, | |||
|
109 | ||||
105 | 'exception': { |
|
110 | 'exception': { | |
106 | 'email_prefix': '[RHODECODE ERROR]', |
|
111 | 'email_prefix': '[RHODECODE ERROR]', | |
107 | 'exc_id': exc_traceback['exc_id'], |
|
112 | 'exc_id': exc_traceback['exc_id'], | |
108 | 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']), |
|
113 | 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']), | |
109 | 'exc_type_name': 'NameError', |
|
114 | 'exc_type_name': 'NameError', | |
110 | 'exc_traceback': exc_traceback, |
|
115 | 'exc_traceback': exc_traceback, | |
111 | }, |
|
116 | }, | |
112 |
|
117 | |||
113 | 'password_reset': { |
|
118 | 'password_reset': { | |
114 | 'password_reset_url': 'http://example.com/reset-rhodecode-password/token', |
|
119 | 'password_reset_url': 'http://example.com/reset-rhodecode-password/token', | |
115 |
|
120 | |||
116 | 'user': user, |
|
121 | 'user': user, | |
117 | 'date': datetime.datetime.now(), |
|
122 | 'date': datetime.datetime.now(), | |
118 | 'email': 'test@rhodecode.com', |
|
123 | 'email': 'test@rhodecode.com', | |
119 | 'first_admin_email': User.get_first_super_admin().email |
|
124 | 'first_admin_email': User.get_first_super_admin().email | |
120 | }, |
|
125 | }, | |
121 |
|
126 | |||
122 | 'password_reset_confirmation': { |
|
127 | 'password_reset_confirmation': { | |
123 | 'new_password': 'new-password-example', |
|
128 | 'new_password': 'new-password-example', | |
124 | 'user': user, |
|
129 | 'user': user, | |
125 | 'date': datetime.datetime.now(), |
|
130 | 'date': datetime.datetime.now(), | |
126 | 'email': 'test@rhodecode.com', |
|
131 | 'email': 'test@rhodecode.com', | |
127 | 'first_admin_email': User.get_first_super_admin().email |
|
132 | 'first_admin_email': User.get_first_super_admin().email | |
128 | }, |
|
133 | }, | |
129 |
|
134 | |||
130 | 'registration': { |
|
135 | 'registration': { | |
131 | 'user': user, |
|
136 | 'user': user, | |
132 | 'date': datetime.datetime.now(), |
|
137 | 'date': datetime.datetime.now(), | |
133 | }, |
|
138 | }, | |
134 |
|
139 | |||
135 | 'pull_request_comment': { |
|
140 | 'pull_request_comment': { | |
136 | 'user': user, |
|
141 | 'user': user, | |
137 |
|
142 | |||
138 | 'status_change': None, |
|
143 | 'status_change': None, | |
139 | 'status_change_type': None, |
|
144 | 'status_change_type': None, | |
140 |
|
145 | |||
141 | 'pull_request': pr, |
|
146 | 'pull_request': pr, | |
142 | 'pull_request_commits': [], |
|
147 | 'pull_request_commits': [], | |
143 |
|
148 | |||
144 | 'pull_request_target_repo': target_repo, |
|
149 | 'pull_request_target_repo': target_repo, | |
145 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
150 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
146 |
|
151 | |||
147 | 'pull_request_source_repo': source_repo, |
|
152 | 'pull_request_source_repo': source_repo, | |
148 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
153 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
149 |
|
154 | |||
150 | 'pull_request_url': 'http://localhost/pr1', |
|
155 | 'pull_request_url': 'http://localhost/pr1', | |
151 | 'pr_comment_url': 'http://comment-url', |
|
156 | 'pr_comment_url': 'http://comment-url', | |
152 | 'pr_comment_reply_url': 'http://comment-url#reply', |
|
157 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
153 |
|
158 | |||
154 | 'comment_file': None, |
|
159 | 'comment_file': None, | |
155 | 'comment_line': None, |
|
160 | 'comment_line': None, | |
156 | 'comment_type': 'note', |
|
161 | 'comment_type': 'note', | |
157 | 'comment_body': 'This is my comment body. *I like !*', |
|
162 | 'comment_body': 'This is my comment body. *I like !*', | |
158 | 'comment_id': 2048, |
|
163 | 'comment_id': 2048, | |
159 | 'renderer_type': 'markdown', |
|
164 | 'renderer_type': 'markdown', | |
160 | 'mention': True, |
|
165 | 'mention': True, | |
161 |
|
166 | |||
162 | }, |
|
167 | }, | |
163 |
|
168 | |||
164 | 'pull_request_comment+status': { |
|
169 | 'pull_request_comment+status': { | |
165 | 'user': user, |
|
170 | 'user': user, | |
166 |
|
171 | |||
167 | 'status_change': 'approved', |
|
172 | 'status_change': 'approved', | |
168 | 'status_change_type': 'approved', |
|
173 | 'status_change_type': 'approved', | |
169 |
|
174 | |||
170 | 'pull_request': pr, |
|
175 | 'pull_request': pr, | |
171 | 'pull_request_commits': [], |
|
176 | 'pull_request_commits': [], | |
172 |
|
177 | |||
173 | 'pull_request_target_repo': target_repo, |
|
178 | 'pull_request_target_repo': target_repo, | |
174 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
179 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
175 |
|
180 | |||
176 | 'pull_request_source_repo': source_repo, |
|
181 | 'pull_request_source_repo': source_repo, | |
177 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
182 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
178 |
|
183 | |||
179 | 'pull_request_url': 'http://localhost/pr1', |
|
184 | 'pull_request_url': 'http://localhost/pr1', | |
180 | 'pr_comment_url': 'http://comment-url', |
|
185 | 'pr_comment_url': 'http://comment-url', | |
181 | 'pr_comment_reply_url': 'http://comment-url#reply', |
|
186 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
182 |
|
187 | |||
183 | 'comment_type': 'todo', |
|
188 | 'comment_type': 'todo', | |
184 | 'comment_file': None, |
|
189 | 'comment_file': None, | |
185 | 'comment_line': None, |
|
190 | 'comment_line': None, | |
186 | 'comment_body': ''' |
|
191 | 'comment_body': ''' | |
187 | I think something like this would be better |
|
192 | I think something like this would be better | |
188 |
|
193 | |||
189 | ```py |
|
194 | ```py | |
190 | // markdown renderer |
|
195 | // markdown renderer | |
191 |
|
196 | |||
192 | def db(): |
|
197 | def db(): | |
193 | global connection |
|
198 | global connection | |
194 | return connection |
|
199 | return connection | |
195 |
|
200 | |||
196 | ``` |
|
201 | ``` | |
197 |
|
202 | |||
198 | ''', |
|
203 | ''', | |
199 | 'comment_id': 2048, |
|
204 | 'comment_id': 2048, | |
200 | 'renderer_type': 'markdown', |
|
205 | 'renderer_type': 'markdown', | |
201 | 'mention': True, |
|
206 | 'mention': True, | |
202 |
|
207 | |||
203 | }, |
|
208 | }, | |
204 |
|
209 | |||
205 | 'pull_request_comment+file': { |
|
210 | 'pull_request_comment+file': { | |
206 | 'user': user, |
|
211 | 'user': user, | |
207 |
|
212 | |||
208 | 'status_change': None, |
|
213 | 'status_change': None, | |
209 | 'status_change_type': None, |
|
214 | 'status_change_type': None, | |
210 |
|
215 | |||
211 | 'pull_request': pr, |
|
216 | 'pull_request': pr, | |
212 | 'pull_request_commits': [], |
|
217 | 'pull_request_commits': [], | |
213 |
|
218 | |||
214 | 'pull_request_target_repo': target_repo, |
|
219 | 'pull_request_target_repo': target_repo, | |
215 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
220 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
216 |
|
221 | |||
217 | 'pull_request_source_repo': source_repo, |
|
222 | 'pull_request_source_repo': source_repo, | |
218 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
223 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
219 |
|
224 | |||
220 | 'pull_request_url': 'http://localhost/pr1', |
|
225 | 'pull_request_url': 'http://localhost/pr1', | |
221 |
|
226 | |||
222 | 'pr_comment_url': 'http://comment-url', |
|
227 | 'pr_comment_url': 'http://comment-url', | |
223 | 'pr_comment_reply_url': 'http://comment-url#reply', |
|
228 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
224 |
|
229 | |||
225 | 'comment_file': 'rhodecode/model/get_flow_commits', |
|
230 | 'comment_file': 'rhodecode/model/get_flow_commits', | |
226 | 'comment_line': 'o1210', |
|
231 | 'comment_line': 'o1210', | |
227 | 'comment_type': 'todo', |
|
232 | 'comment_type': 'todo', | |
228 | 'comment_body': ''' |
|
233 | 'comment_body': ''' | |
229 | I like this ! |
|
234 | I like this ! | |
230 |
|
235 | |||
231 | But please check this code |
|
236 | But please check this code | |
232 |
|
237 | |||
233 | .. code-block:: javascript |
|
238 | .. code-block:: javascript | |
234 |
|
239 | |||
235 | // THIS IS RST CODE |
|
240 | // THIS IS RST CODE | |
236 |
|
241 | |||
237 | this.createResolutionComment = function(commentId) { |
|
242 | this.createResolutionComment = function(commentId) { | |
238 | // hide the trigger text |
|
243 | // hide the trigger text | |
239 | $('#resolve-comment-{0}'.format(commentId)).hide(); |
|
244 | $('#resolve-comment-{0}'.format(commentId)).hide(); | |
240 |
|
245 | |||
241 | var comment = $('#comment-'+commentId); |
|
246 | var comment = $('#comment-'+commentId); | |
242 | var commentData = comment.data(); |
|
247 | var commentData = comment.data(); | |
243 | if (commentData.commentInline) { |
|
248 | if (commentData.commentInline) { | |
244 | this.createComment(comment, f_path, line_no, commentId) |
|
249 | this.createComment(comment, f_path, line_no, commentId) | |
245 | } else { |
|
250 | } else { | |
246 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) |
|
251 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) | |
247 | } |
|
252 | } | |
248 |
|
253 | |||
249 | return false; |
|
254 | return false; | |
250 | }; |
|
255 | }; | |
251 |
|
256 | |||
252 | This should work better ! |
|
257 | This should work better ! | |
253 | ''', |
|
258 | ''', | |
254 | 'comment_id': 2048, |
|
259 | 'comment_id': 2048, | |
255 | 'renderer_type': 'rst', |
|
260 | 'renderer_type': 'rst', | |
256 | 'mention': True, |
|
261 | 'mention': True, | |
257 |
|
262 | |||
258 | }, |
|
263 | }, | |
259 |
|
264 | |||
260 | 'pull_request_update': { |
|
265 | 'pull_request_update': { | |
261 | 'updating_user': user, |
|
266 | 'updating_user': user, | |
262 |
|
267 | |||
263 | 'status_change': None, |
|
268 | 'status_change': None, | |
264 | 'status_change_type': None, |
|
269 | 'status_change_type': None, | |
265 |
|
270 | |||
266 | 'pull_request': pr, |
|
271 | 'pull_request': pr, | |
267 | 'pull_request_commits': [], |
|
272 | 'pull_request_commits': [], | |
268 |
|
273 | |||
269 | 'pull_request_target_repo': target_repo, |
|
274 | 'pull_request_target_repo': target_repo, | |
270 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
275 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
271 |
|
276 | |||
272 | 'pull_request_source_repo': source_repo, |
|
277 | 'pull_request_source_repo': source_repo, | |
273 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
278 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
274 |
|
279 | |||
275 | 'pull_request_url': 'http://localhost/pr1', |
|
280 | 'pull_request_url': 'http://localhost/pr1', | |
276 |
|
281 | |||
277 | # update comment links |
|
282 | # update comment links | |
278 | 'pr_comment_url': 'http://comment-url', |
|
283 | 'pr_comment_url': 'http://comment-url', | |
279 | 'pr_comment_reply_url': 'http://comment-url#reply', |
|
284 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
280 | 'ancestor_commit_id': 'f39bd443', |
|
285 | 'ancestor_commit_id': 'f39bd443', | |
281 | 'added_commits': commit_changes.added, |
|
286 | 'added_commits': commit_changes.added, | |
282 | 'removed_commits': commit_changes.removed, |
|
287 | 'removed_commits': commit_changes.removed, | |
283 | 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed), |
|
288 | 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed), | |
284 | 'added_files': file_changes.added, |
|
289 | 'added_files': file_changes.added, | |
285 | 'modified_files': file_changes.modified, |
|
290 | 'modified_files': file_changes.modified, | |
286 | 'removed_files': file_changes.removed, |
|
291 | 'removed_files': file_changes.removed, | |
287 | }, |
|
292 | }, | |
288 |
|
293 | |||
289 | 'cs_comment': { |
|
294 | 'cs_comment': { | |
290 | 'user': user, |
|
295 | 'user': user, | |
291 | 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'), |
|
296 | 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'), | |
292 | 'status_change': None, |
|
297 | 'status_change': None, | |
293 | 'status_change_type': None, |
|
298 | 'status_change_type': None, | |
294 |
|
299 | |||
295 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', |
|
300 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', | |
296 | 'repo_name': 'test-repo', |
|
301 | 'repo_name': 'test-repo', | |
297 | 'comment_type': 'note', |
|
302 | 'comment_type': 'note', | |
298 | 'comment_file': None, |
|
303 | 'comment_file': None, | |
299 | 'comment_line': None, |
|
304 | 'comment_line': None, | |
300 | 'commit_comment_url': 'http://comment-url', |
|
305 | 'commit_comment_url': 'http://comment-url', | |
301 | 'commit_comment_reply_url': 'http://comment-url#reply', |
|
306 | 'commit_comment_reply_url': 'http://comment-url#reply', | |
302 | 'comment_body': 'This is my comment body. *I like !*', |
|
307 | 'comment_body': 'This is my comment body. *I like !*', | |
303 | 'comment_id': 2048, |
|
308 | 'comment_id': 2048, | |
304 | 'renderer_type': 'markdown', |
|
309 | 'renderer_type': 'markdown', | |
305 | 'mention': True, |
|
310 | 'mention': True, | |
306 | }, |
|
311 | }, | |
307 |
|
312 | |||
308 | 'cs_comment+status': { |
|
313 | 'cs_comment+status': { | |
309 | 'user': user, |
|
314 | 'user': user, | |
310 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
|
315 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), | |
311 | 'status_change': 'approved', |
|
316 | 'status_change': 'approved', | |
312 | 'status_change_type': 'approved', |
|
317 | 'status_change_type': 'approved', | |
313 |
|
318 | |||
314 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', |
|
319 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', | |
315 | 'repo_name': 'test-repo', |
|
320 | 'repo_name': 'test-repo', | |
316 | 'comment_type': 'note', |
|
321 | 'comment_type': 'note', | |
317 | 'comment_file': None, |
|
322 | 'comment_file': None, | |
318 | 'comment_line': None, |
|
323 | 'comment_line': None, | |
319 | 'commit_comment_url': 'http://comment-url', |
|
324 | 'commit_comment_url': 'http://comment-url', | |
320 | 'commit_comment_reply_url': 'http://comment-url#reply', |
|
325 | 'commit_comment_reply_url': 'http://comment-url#reply', | |
321 | 'comment_body': ''' |
|
326 | 'comment_body': ''' | |
322 | Hello **world** |
|
327 | Hello **world** | |
323 |
|
328 | |||
324 | This is a multiline comment :) |
|
329 | This is a multiline comment :) | |
325 |
|
330 | |||
326 | - list |
|
331 | - list | |
327 | - list2 |
|
332 | - list2 | |
328 | ''', |
|
333 | ''', | |
329 | 'comment_id': 2048, |
|
334 | 'comment_id': 2048, | |
330 | 'renderer_type': 'markdown', |
|
335 | 'renderer_type': 'markdown', | |
331 | 'mention': True, |
|
336 | 'mention': True, | |
332 | }, |
|
337 | }, | |
333 |
|
338 | |||
334 | 'cs_comment+file': { |
|
339 | 'cs_comment+file': { | |
335 | 'user': user, |
|
340 | 'user': user, | |
336 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
|
341 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), | |
337 | 'status_change': None, |
|
342 | 'status_change': None, | |
338 | 'status_change_type': None, |
|
343 | 'status_change_type': None, | |
339 |
|
344 | |||
340 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', |
|
345 | 'commit_target_repo_url': 'http://foo.example.com/#comment1', | |
341 | 'repo_name': 'test-repo', |
|
346 | 'repo_name': 'test-repo', | |
342 |
|
347 | |||
343 | 'comment_type': 'note', |
|
348 | 'comment_type': 'note', | |
344 | 'comment_file': 'test-file.py', |
|
349 | 'comment_file': 'test-file.py', | |
345 | 'comment_line': 'n100', |
|
350 | 'comment_line': 'n100', | |
346 |
|
351 | |||
347 | 'commit_comment_url': 'http://comment-url', |
|
352 | 'commit_comment_url': 'http://comment-url', | |
348 | 'commit_comment_reply_url': 'http://comment-url#reply', |
|
353 | 'commit_comment_reply_url': 'http://comment-url#reply', | |
349 | 'comment_body': 'This is my comment body. *I like !*', |
|
354 | 'comment_body': 'This is my comment body. *I like !*', | |
350 | 'comment_id': 2048, |
|
355 | 'comment_id': 2048, | |
351 | 'renderer_type': 'markdown', |
|
356 | 'renderer_type': 'markdown', | |
352 | 'mention': True, |
|
357 | 'mention': True, | |
353 | }, |
|
358 | }, | |
354 |
|
359 | |||
355 | 'pull_request': { |
|
360 | 'pull_request': { | |
356 | 'user': user, |
|
361 | 'user': user, | |
357 | 'pull_request': pr, |
|
362 | 'pull_request': pr, | |
358 | 'pull_request_commits': [ |
|
363 | 'pull_request_commits': [ | |
359 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ |
|
364 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ | |
360 | my-account: moved email closer to profile as it's similar data just moved outside. |
|
365 | my-account: moved email closer to profile as it's similar data just moved outside. | |
361 | '''), |
|
366 | '''), | |
362 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ |
|
367 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ | |
363 | users: description edit fixes |
|
368 | users: description edit fixes | |
364 |
|
369 | |||
365 | - tests |
|
370 | - tests | |
366 | - added metatags info |
|
371 | - added metatags info | |
367 | '''), |
|
372 | '''), | |
368 | ], |
|
373 | ], | |
369 |
|
374 | |||
370 | 'pull_request_target_repo': target_repo, |
|
375 | 'pull_request_target_repo': target_repo, | |
371 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
376 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
372 |
|
377 | |||
373 | 'pull_request_source_repo': source_repo, |
|
378 | 'pull_request_source_repo': source_repo, | |
374 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
379 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
375 |
|
380 | |||
376 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', |
|
381 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |
377 | 'user_role': 'reviewer', |
|
382 | 'user_role': 'reviewer', | |
378 | }, |
|
383 | }, | |
379 |
|
384 | |||
380 | 'pull_request+reviewer_role': { |
|
385 | 'pull_request+reviewer_role': { | |
381 | 'user': user, |
|
386 | 'user': user, | |
382 | 'pull_request': pr, |
|
387 | 'pull_request': pr, | |
383 | 'pull_request_commits': [ |
|
388 | 'pull_request_commits': [ | |
384 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ |
|
389 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ | |
385 | my-account: moved email closer to profile as it's similar data just moved outside. |
|
390 | my-account: moved email closer to profile as it's similar data just moved outside. | |
386 | '''), |
|
391 | '''), | |
387 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ |
|
392 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ | |
388 | users: description edit fixes |
|
393 | users: description edit fixes | |
389 |
|
394 | |||
390 | - tests |
|
395 | - tests | |
391 | - added metatags info |
|
396 | - added metatags info | |
392 | '''), |
|
397 | '''), | |
393 | ], |
|
398 | ], | |
394 |
|
399 | |||
395 | 'pull_request_target_repo': target_repo, |
|
400 | 'pull_request_target_repo': target_repo, | |
396 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
401 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
397 |
|
402 | |||
398 | 'pull_request_source_repo': source_repo, |
|
403 | 'pull_request_source_repo': source_repo, | |
399 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
404 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
400 |
|
405 | |||
401 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', |
|
406 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |
402 | 'user_role': 'reviewer', |
|
407 | 'user_role': 'reviewer', | |
403 | }, |
|
408 | }, | |
404 |
|
409 | |||
405 | 'pull_request+observer_role': { |
|
410 | 'pull_request+observer_role': { | |
406 | 'user': user, |
|
411 | 'user': user, | |
407 | 'pull_request': pr, |
|
412 | 'pull_request': pr, | |
408 | 'pull_request_commits': [ |
|
413 | 'pull_request_commits': [ | |
409 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ |
|
414 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ | |
410 | my-account: moved email closer to profile as it's similar data just moved outside. |
|
415 | my-account: moved email closer to profile as it's similar data just moved outside. | |
411 | '''), |
|
416 | '''), | |
412 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ |
|
417 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ | |
413 | users: description edit fixes |
|
418 | users: description edit fixes | |
414 |
|
419 | |||
415 | - tests |
|
420 | - tests | |
416 | - added metatags info |
|
421 | - added metatags info | |
417 | '''), |
|
422 | '''), | |
418 | ], |
|
423 | ], | |
419 |
|
424 | |||
420 | 'pull_request_target_repo': target_repo, |
|
425 | 'pull_request_target_repo': target_repo, | |
421 | 'pull_request_target_repo_url': 'http://target-repo/url', |
|
426 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
422 |
|
427 | |||
423 | 'pull_request_source_repo': source_repo, |
|
428 | 'pull_request_source_repo': source_repo, | |
424 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
429 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
425 |
|
430 | |||
426 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', |
|
431 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |
427 | 'user_role': 'observer' |
|
432 | 'user_role': 'observer' | |
428 | } |
|
433 | } | |
429 | } |
|
434 | } | |
430 |
|
435 | |||
431 | template_type = email_id.split('+')[0] |
|
436 | template_type = email_id.split('+')[0] | |
432 | (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email( |
|
437 | (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email( | |
433 | template_type, **email_kwargs.get(email_id, {})) |
|
438 | template_type, **email_kwargs.get(email_id, {})) | |
434 |
|
439 | |||
435 | test_email = self.request.GET.get('email') |
|
440 | test_email = self.request.GET.get('email') | |
436 | if test_email: |
|
441 | if test_email: | |
437 | recipients = [test_email] |
|
442 | recipients = [test_email] | |
438 | run_task(tasks.send_email, recipients, c.subject, |
|
443 | run_task(tasks.send_email, recipients, c.subject, | |
439 | c.email_body_plaintext, c.email_body) |
|
444 | c.email_body_plaintext, c.email_body) | |
440 |
|
445 | |||
441 | if self.request.matched_route.name == 'debug_style_email_plain_rendered': |
|
446 | if self.request.matched_route.name == 'debug_style_email_plain_rendered': | |
442 | template = 'debug_style/email_plain_rendered.mako' |
|
447 | template = 'debug_style/email_plain_rendered.mako' | |
443 | else: |
|
448 | else: | |
444 | template = 'debug_style/email.mako' |
|
449 | template = 'debug_style/email.mako' | |
445 | return render_to_response( |
|
450 | return render_to_response( | |
446 | template, self._get_template_context(c), |
|
451 | template, self._get_template_context(c), | |
447 | request=self.request) |
|
452 | request=self.request) | |
448 |
|
453 | |||
449 | def template(self): |
|
454 | def template(self): | |
450 | t_path = self.request.matchdict['t_path'] |
|
455 | t_path = self.request.matchdict['t_path'] | |
451 | c = self.load_default_context() |
|
456 | c = self.load_default_context() | |
452 | c.active = os.path.splitext(t_path)[0] |
|
457 | c.active = os.path.splitext(t_path)[0] | |
453 | c.came_from = '' |
|
458 | c.came_from = '' | |
454 | # NOTE(marcink): extend the email types with variations based on data sets |
|
459 | # NOTE(marcink): extend the email types with variations based on data sets | |
455 | c.email_types = { |
|
460 | c.email_types = { | |
456 | 'cs_comment+file': {}, |
|
461 | 'cs_comment+file': {}, | |
457 | 'cs_comment+status': {}, |
|
462 | 'cs_comment+status': {}, | |
458 |
|
463 | |||
459 | 'pull_request_comment+file': {}, |
|
464 | 'pull_request_comment+file': {}, | |
460 | 'pull_request_comment+status': {}, |
|
465 | 'pull_request_comment+status': {}, | |
461 |
|
466 | |||
462 | 'pull_request_update': {}, |
|
467 | 'pull_request_update': {}, | |
463 |
|
468 | |||
464 | 'pull_request+reviewer_role': {}, |
|
469 | 'pull_request+reviewer_role': {}, | |
465 | 'pull_request+observer_role': {}, |
|
470 | 'pull_request+observer_role': {}, | |
466 | } |
|
471 | } | |
467 | c.email_types.update(EmailNotificationModel.email_types) |
|
472 | c.email_types.update(EmailNotificationModel.email_types) | |
468 |
|
473 | |||
469 | return render_to_response( |
|
474 | return render_to_response( | |
470 | 'debug_style/' + t_path, self._get_template_context(c), |
|
475 | 'debug_style/' + t_path, self._get_template_context(c), | |
471 | request=self.request) |
|
476 | request=self.request) |
@@ -1,379 +1,403 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2012-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2012-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | RhodeCode task modules, containing all task that suppose to be run |
|
22 | RhodeCode task modules, containing all task that suppose to be run | |
23 | by celery daemon |
|
23 | by celery daemon | |
24 | """ |
|
24 | """ | |
25 |
|
25 | |||
26 | import os |
|
26 | import os | |
27 | import time |
|
27 | import time | |
28 |
|
28 | |||
29 | from pyramid import compat |
|
29 | from pyramid import compat | |
30 | from pyramid_mailer.mailer import Mailer |
|
30 | from pyramid_mailer.mailer import Mailer | |
31 | from pyramid_mailer.message import Message |
|
31 | from pyramid_mailer.message import Message | |
32 | from email.utils import formatdate |
|
32 | from email.utils import formatdate | |
33 |
|
33 | |||
34 | import rhodecode |
|
34 | import rhodecode | |
35 | from rhodecode.lib import audit_logger |
|
35 | from rhodecode.lib import audit_logger | |
36 | from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask |
|
36 | from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask, run_task | |
37 | from rhodecode.lib import hooks_base |
|
37 | from rhodecode.lib import hooks_base | |
38 | from rhodecode.lib.utils2 import safe_int, str2bool |
|
38 | from rhodecode.lib.utils2 import safe_int, str2bool, aslist | |
39 | from rhodecode.model.db import ( |
|
39 | from rhodecode.model.db import ( | |
40 | Session, IntegrityError, true, Repository, RepoGroup, User) |
|
40 | Session, IntegrityError, true, Repository, RepoGroup, User) | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | @async_task(ignore_result=True, base=RequestContextTask) |
|
43 | @async_task(ignore_result=True, base=RequestContextTask) | |
44 | def send_email(recipients, subject, body='', html_body='', email_config=None, |
|
44 | def send_email(recipients, subject, body='', html_body='', email_config=None, | |
45 | extra_headers=None): |
|
45 | extra_headers=None): | |
46 | """ |
|
46 | """ | |
47 | Sends an email with defined parameters from the .ini files. |
|
47 | Sends an email with defined parameters from the .ini files. | |
48 |
|
48 | |||
49 | :param recipients: list of recipients, it this is empty the defined email |
|
49 | :param recipients: list of recipients, it this is empty the defined email | |
50 | address from field 'email_to' is used instead |
|
50 | address from field 'email_to' is used instead | |
51 | :param subject: subject of the mail |
|
51 | :param subject: subject of the mail | |
52 | :param body: body of the mail |
|
52 | :param body: body of the mail | |
53 | :param html_body: html version of body |
|
53 | :param html_body: html version of body | |
54 | :param email_config: specify custom configuration for mailer |
|
54 | :param email_config: specify custom configuration for mailer | |
55 | :param extra_headers: specify custom headers |
|
55 | :param extra_headers: specify custom headers | |
56 | """ |
|
56 | """ | |
57 | log = get_logger(send_email) |
|
57 | log = get_logger(send_email) | |
58 |
|
58 | |||
59 | email_config = email_config or rhodecode.CONFIG |
|
59 | email_config = email_config or rhodecode.CONFIG | |
60 |
|
60 | |||
61 | mail_server = email_config.get('smtp_server') or None |
|
61 | mail_server = email_config.get('smtp_server') or None | |
62 | if mail_server is None: |
|
62 | if mail_server is None: | |
63 | log.error("SMTP server information missing. Sending email failed. " |
|
63 | log.error("SMTP server information missing. Sending email failed. " | |
64 | "Make sure that `smtp_server` variable is configured " |
|
64 | "Make sure that `smtp_server` variable is configured " | |
65 | "inside the .ini file") |
|
65 | "inside the .ini file") | |
66 | return False |
|
66 | return False | |
67 |
|
67 | |||
68 | subject = "%s %s" % (email_config.get('email_prefix', ''), subject) |
|
68 | subject = "%s %s" % (email_config.get('email_prefix', ''), subject) | |
69 |
|
69 | |||
70 | if recipients: |
|
70 | if recipients: | |
71 | if isinstance(recipients, compat.string_types): |
|
71 | if isinstance(recipients, compat.string_types): | |
72 | recipients = recipients.split(',') |
|
72 | recipients = recipients.split(',') | |
73 | else: |
|
73 | else: | |
74 | # if recipients are not defined we send to email_config + all admins |
|
74 | # if recipients are not defined we send to email_config + all admins | |
75 | admins = [] |
|
75 | admins = [] | |
76 | for u in User.query().filter(User.admin == true()).all(): |
|
76 | for u in User.query().filter(User.admin == true()).all(): | |
77 | if u.email: |
|
77 | if u.email: | |
78 | admins.append(u.email) |
|
78 | admins.append(u.email) | |
79 | recipients = [] |
|
79 | recipients = [] | |
80 | config_email = email_config.get('email_to') |
|
80 | config_email = email_config.get('email_to') | |
81 | if config_email: |
|
81 | if config_email: | |
82 | recipients += [config_email] |
|
82 | recipients += [config_email] | |
83 | recipients += admins |
|
83 | recipients += admins | |
84 |
|
84 | |||
85 | # translate our LEGACY config into the one that pyramid_mailer supports |
|
85 | # translate our LEGACY config into the one that pyramid_mailer supports | |
86 | email_conf = dict( |
|
86 | email_conf = dict( | |
87 | host=mail_server, |
|
87 | host=mail_server, | |
88 | port=email_config.get('smtp_port', 25), |
|
88 | port=email_config.get('smtp_port', 25), | |
89 | username=email_config.get('smtp_username'), |
|
89 | username=email_config.get('smtp_username'), | |
90 | password=email_config.get('smtp_password'), |
|
90 | password=email_config.get('smtp_password'), | |
91 |
|
91 | |||
92 | tls=str2bool(email_config.get('smtp_use_tls')), |
|
92 | tls=str2bool(email_config.get('smtp_use_tls')), | |
93 | ssl=str2bool(email_config.get('smtp_use_ssl')), |
|
93 | ssl=str2bool(email_config.get('smtp_use_ssl')), | |
94 |
|
94 | |||
95 | # SSL key file |
|
95 | # SSL key file | |
96 | # keyfile='', |
|
96 | # keyfile='', | |
97 |
|
97 | |||
98 | # SSL certificate file |
|
98 | # SSL certificate file | |
99 | # certfile='', |
|
99 | # certfile='', | |
100 |
|
100 | |||
101 | # Location of maildir |
|
101 | # Location of maildir | |
102 | # queue_path='', |
|
102 | # queue_path='', | |
103 |
|
103 | |||
104 | default_sender=email_config.get('app_email_from', 'RhodeCode'), |
|
104 | default_sender=email_config.get('app_email_from', 'RhodeCode'), | |
105 |
|
105 | |||
106 | debug=str2bool(email_config.get('smtp_debug')), |
|
106 | debug=str2bool(email_config.get('smtp_debug')), | |
107 | # /usr/sbin/sendmail Sendmail executable |
|
107 | # /usr/sbin/sendmail Sendmail executable | |
108 | # sendmail_app='', |
|
108 | # sendmail_app='', | |
109 |
|
109 | |||
110 | # {sendmail_app} -t -i -f {sender} Template for sendmail execution |
|
110 | # {sendmail_app} -t -i -f {sender} Template for sendmail execution | |
111 | # sendmail_template='', |
|
111 | # sendmail_template='', | |
112 | ) |
|
112 | ) | |
113 |
|
113 | |||
114 | if extra_headers is None: |
|
114 | if extra_headers is None: | |
115 | extra_headers = {} |
|
115 | extra_headers = {} | |
116 |
|
116 | |||
117 | extra_headers.setdefault('Date', formatdate(time.time())) |
|
117 | extra_headers.setdefault('Date', formatdate(time.time())) | |
118 |
|
118 | |||
119 | if 'thread_ids' in extra_headers: |
|
119 | if 'thread_ids' in extra_headers: | |
120 | thread_ids = extra_headers.pop('thread_ids') |
|
120 | thread_ids = extra_headers.pop('thread_ids') | |
121 | extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids) |
|
121 | extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids) | |
122 |
|
122 | |||
123 | try: |
|
123 | try: | |
124 | mailer = Mailer(**email_conf) |
|
124 | mailer = Mailer(**email_conf) | |
125 |
|
125 | |||
126 | message = Message(subject=subject, |
|
126 | message = Message(subject=subject, | |
127 | sender=email_conf['default_sender'], |
|
127 | sender=email_conf['default_sender'], | |
128 | recipients=recipients, |
|
128 | recipients=recipients, | |
129 | body=body, html=html_body, |
|
129 | body=body, html=html_body, | |
130 | extra_headers=extra_headers) |
|
130 | extra_headers=extra_headers) | |
131 | mailer.send_immediately(message) |
|
131 | mailer.send_immediately(message) | |
132 |
|
132 | |||
133 | except Exception: |
|
133 | except Exception: | |
134 | log.exception('Mail sending failed') |
|
134 | log.exception('Mail sending failed') | |
135 | return False |
|
135 | return False | |
136 | return True |
|
136 | return True | |
137 |
|
137 | |||
138 |
|
138 | |||
139 | @async_task(ignore_result=True, base=RequestContextTask) |
|
139 | @async_task(ignore_result=True, base=RequestContextTask) | |
140 | def create_repo(form_data, cur_user): |
|
140 | def create_repo(form_data, cur_user): | |
141 | from rhodecode.model.repo import RepoModel |
|
141 | from rhodecode.model.repo import RepoModel | |
142 | from rhodecode.model.user import UserModel |
|
142 | from rhodecode.model.user import UserModel | |
143 | from rhodecode.model.scm import ScmModel |
|
143 | from rhodecode.model.scm import ScmModel | |
144 | from rhodecode.model.settings import SettingsModel |
|
144 | from rhodecode.model.settings import SettingsModel | |
145 |
|
145 | |||
146 | log = get_logger(create_repo) |
|
146 | log = get_logger(create_repo) | |
147 |
|
147 | |||
148 | cur_user = UserModel()._get_user(cur_user) |
|
148 | cur_user = UserModel()._get_user(cur_user) | |
149 | owner = cur_user |
|
149 | owner = cur_user | |
150 |
|
150 | |||
151 | repo_name = form_data['repo_name'] |
|
151 | repo_name = form_data['repo_name'] | |
152 | repo_name_full = form_data['repo_name_full'] |
|
152 | repo_name_full = form_data['repo_name_full'] | |
153 | repo_type = form_data['repo_type'] |
|
153 | repo_type = form_data['repo_type'] | |
154 | description = form_data['repo_description'] |
|
154 | description = form_data['repo_description'] | |
155 | private = form_data['repo_private'] |
|
155 | private = form_data['repo_private'] | |
156 | clone_uri = form_data.get('clone_uri') |
|
156 | clone_uri = form_data.get('clone_uri') | |
157 | repo_group = safe_int(form_data['repo_group']) |
|
157 | repo_group = safe_int(form_data['repo_group']) | |
158 | copy_fork_permissions = form_data.get('copy_permissions') |
|
158 | copy_fork_permissions = form_data.get('copy_permissions') | |
159 | copy_group_permissions = form_data.get('repo_copy_permissions') |
|
159 | copy_group_permissions = form_data.get('repo_copy_permissions') | |
160 | fork_of = form_data.get('fork_parent_id') |
|
160 | fork_of = form_data.get('fork_parent_id') | |
161 | state = form_data.get('repo_state', Repository.STATE_PENDING) |
|
161 | state = form_data.get('repo_state', Repository.STATE_PENDING) | |
162 |
|
162 | |||
163 | # repo creation defaults, private and repo_type are filled in form |
|
163 | # repo creation defaults, private and repo_type are filled in form | |
164 | defs = SettingsModel().get_default_repo_settings(strip_prefix=True) |
|
164 | defs = SettingsModel().get_default_repo_settings(strip_prefix=True) | |
165 | enable_statistics = form_data.get( |
|
165 | enable_statistics = form_data.get( | |
166 | 'enable_statistics', defs.get('repo_enable_statistics')) |
|
166 | 'enable_statistics', defs.get('repo_enable_statistics')) | |
167 | enable_locking = form_data.get( |
|
167 | enable_locking = form_data.get( | |
168 | 'enable_locking', defs.get('repo_enable_locking')) |
|
168 | 'enable_locking', defs.get('repo_enable_locking')) | |
169 | enable_downloads = form_data.get( |
|
169 | enable_downloads = form_data.get( | |
170 | 'enable_downloads', defs.get('repo_enable_downloads')) |
|
170 | 'enable_downloads', defs.get('repo_enable_downloads')) | |
171 |
|
171 | |||
172 | # set landing rev based on default branches for SCM |
|
172 | # set landing rev based on default branches for SCM | |
173 | landing_ref, _label = ScmModel.backend_landing_ref(repo_type) |
|
173 | landing_ref, _label = ScmModel.backend_landing_ref(repo_type) | |
174 |
|
174 | |||
175 | try: |
|
175 | try: | |
176 | RepoModel()._create_repo( |
|
176 | RepoModel()._create_repo( | |
177 | repo_name=repo_name_full, |
|
177 | repo_name=repo_name_full, | |
178 | repo_type=repo_type, |
|
178 | repo_type=repo_type, | |
179 | description=description, |
|
179 | description=description, | |
180 | owner=owner, |
|
180 | owner=owner, | |
181 | private=private, |
|
181 | private=private, | |
182 | clone_uri=clone_uri, |
|
182 | clone_uri=clone_uri, | |
183 | repo_group=repo_group, |
|
183 | repo_group=repo_group, | |
184 | landing_rev=landing_ref, |
|
184 | landing_rev=landing_ref, | |
185 | fork_of=fork_of, |
|
185 | fork_of=fork_of, | |
186 | copy_fork_permissions=copy_fork_permissions, |
|
186 | copy_fork_permissions=copy_fork_permissions, | |
187 | copy_group_permissions=copy_group_permissions, |
|
187 | copy_group_permissions=copy_group_permissions, | |
188 | enable_statistics=enable_statistics, |
|
188 | enable_statistics=enable_statistics, | |
189 | enable_locking=enable_locking, |
|
189 | enable_locking=enable_locking, | |
190 | enable_downloads=enable_downloads, |
|
190 | enable_downloads=enable_downloads, | |
191 | state=state |
|
191 | state=state | |
192 | ) |
|
192 | ) | |
193 | Session().commit() |
|
193 | Session().commit() | |
194 |
|
194 | |||
195 | # now create this repo on Filesystem |
|
195 | # now create this repo on Filesystem | |
196 | RepoModel()._create_filesystem_repo( |
|
196 | RepoModel()._create_filesystem_repo( | |
197 | repo_name=repo_name, |
|
197 | repo_name=repo_name, | |
198 | repo_type=repo_type, |
|
198 | repo_type=repo_type, | |
199 | repo_group=RepoModel()._get_repo_group(repo_group), |
|
199 | repo_group=RepoModel()._get_repo_group(repo_group), | |
200 | clone_uri=clone_uri, |
|
200 | clone_uri=clone_uri, | |
201 | ) |
|
201 | ) | |
202 | repo = Repository.get_by_repo_name(repo_name_full) |
|
202 | repo = Repository.get_by_repo_name(repo_name_full) | |
203 | hooks_base.create_repository(created_by=owner.username, **repo.get_dict()) |
|
203 | hooks_base.create_repository(created_by=owner.username, **repo.get_dict()) | |
204 |
|
204 | |||
205 | # update repo commit caches initially |
|
205 | # update repo commit caches initially | |
206 | repo.update_commit_cache() |
|
206 | repo.update_commit_cache() | |
207 |
|
207 | |||
208 | # set new created state |
|
208 | # set new created state | |
209 | repo.set_state(Repository.STATE_CREATED) |
|
209 | repo.set_state(Repository.STATE_CREATED) | |
210 | repo_id = repo.repo_id |
|
210 | repo_id = repo.repo_id | |
211 | repo_data = repo.get_api_data() |
|
211 | repo_data = repo.get_api_data() | |
212 |
|
212 | |||
213 | audit_logger.store( |
|
213 | audit_logger.store( | |
214 | 'repo.create', action_data={'data': repo_data}, |
|
214 | 'repo.create', action_data={'data': repo_data}, | |
215 | user=cur_user, |
|
215 | user=cur_user, | |
216 | repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id)) |
|
216 | repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id)) | |
217 |
|
217 | |||
218 | Session().commit() |
|
218 | Session().commit() | |
219 | except Exception as e: |
|
219 | except Exception as e: | |
220 | log.warning('Exception occurred when creating repository, ' |
|
220 | log.warning('Exception occurred when creating repository, ' | |
221 | 'doing cleanup...', exc_info=True) |
|
221 | 'doing cleanup...', exc_info=True) | |
222 | if isinstance(e, IntegrityError): |
|
222 | if isinstance(e, IntegrityError): | |
223 | Session().rollback() |
|
223 | Session().rollback() | |
224 |
|
224 | |||
225 | # rollback things manually ! |
|
225 | # rollback things manually ! | |
226 | repo = Repository.get_by_repo_name(repo_name_full) |
|
226 | repo = Repository.get_by_repo_name(repo_name_full) | |
227 | if repo: |
|
227 | if repo: | |
228 | Repository.delete(repo.repo_id) |
|
228 | Repository.delete(repo.repo_id) | |
229 | Session().commit() |
|
229 | Session().commit() | |
230 | RepoModel()._delete_filesystem_repo(repo) |
|
230 | RepoModel()._delete_filesystem_repo(repo) | |
231 | log.info('Cleanup of repo %s finished', repo_name_full) |
|
231 | log.info('Cleanup of repo %s finished', repo_name_full) | |
232 | raise |
|
232 | raise | |
233 |
|
233 | |||
234 | return True |
|
234 | return True | |
235 |
|
235 | |||
236 |
|
236 | |||
237 | @async_task(ignore_result=True, base=RequestContextTask) |
|
237 | @async_task(ignore_result=True, base=RequestContextTask) | |
238 | def create_repo_fork(form_data, cur_user): |
|
238 | def create_repo_fork(form_data, cur_user): | |
239 | """ |
|
239 | """ | |
240 | Creates a fork of repository using internal VCS methods |
|
240 | Creates a fork of repository using internal VCS methods | |
241 | """ |
|
241 | """ | |
242 | from rhodecode.model.repo import RepoModel |
|
242 | from rhodecode.model.repo import RepoModel | |
243 | from rhodecode.model.user import UserModel |
|
243 | from rhodecode.model.user import UserModel | |
244 |
|
244 | |||
245 | log = get_logger(create_repo_fork) |
|
245 | log = get_logger(create_repo_fork) | |
246 |
|
246 | |||
247 | cur_user = UserModel()._get_user(cur_user) |
|
247 | cur_user = UserModel()._get_user(cur_user) | |
248 | owner = cur_user |
|
248 | owner = cur_user | |
249 |
|
249 | |||
250 | repo_name = form_data['repo_name'] # fork in this case |
|
250 | repo_name = form_data['repo_name'] # fork in this case | |
251 | repo_name_full = form_data['repo_name_full'] |
|
251 | repo_name_full = form_data['repo_name_full'] | |
252 | repo_type = form_data['repo_type'] |
|
252 | repo_type = form_data['repo_type'] | |
253 | description = form_data['description'] |
|
253 | description = form_data['description'] | |
254 | private = form_data['private'] |
|
254 | private = form_data['private'] | |
255 | clone_uri = form_data.get('clone_uri') |
|
255 | clone_uri = form_data.get('clone_uri') | |
256 | repo_group = safe_int(form_data['repo_group']) |
|
256 | repo_group = safe_int(form_data['repo_group']) | |
257 | landing_ref = form_data['landing_rev'] |
|
257 | landing_ref = form_data['landing_rev'] | |
258 | copy_fork_permissions = form_data.get('copy_permissions') |
|
258 | copy_fork_permissions = form_data.get('copy_permissions') | |
259 | fork_id = safe_int(form_data.get('fork_parent_id')) |
|
259 | fork_id = safe_int(form_data.get('fork_parent_id')) | |
260 |
|
260 | |||
261 | try: |
|
261 | try: | |
262 | fork_of = RepoModel()._get_repo(fork_id) |
|
262 | fork_of = RepoModel()._get_repo(fork_id) | |
263 | RepoModel()._create_repo( |
|
263 | RepoModel()._create_repo( | |
264 | repo_name=repo_name_full, |
|
264 | repo_name=repo_name_full, | |
265 | repo_type=repo_type, |
|
265 | repo_type=repo_type, | |
266 | description=description, |
|
266 | description=description, | |
267 | owner=owner, |
|
267 | owner=owner, | |
268 | private=private, |
|
268 | private=private, | |
269 | clone_uri=clone_uri, |
|
269 | clone_uri=clone_uri, | |
270 | repo_group=repo_group, |
|
270 | repo_group=repo_group, | |
271 | landing_rev=landing_ref, |
|
271 | landing_rev=landing_ref, | |
272 | fork_of=fork_of, |
|
272 | fork_of=fork_of, | |
273 | copy_fork_permissions=copy_fork_permissions |
|
273 | copy_fork_permissions=copy_fork_permissions | |
274 | ) |
|
274 | ) | |
275 |
|
275 | |||
276 | Session().commit() |
|
276 | Session().commit() | |
277 |
|
277 | |||
278 | base_path = Repository.base_path() |
|
278 | base_path = Repository.base_path() | |
279 | source_repo_path = os.path.join(base_path, fork_of.repo_name) |
|
279 | source_repo_path = os.path.join(base_path, fork_of.repo_name) | |
280 |
|
280 | |||
281 | # now create this repo on Filesystem |
|
281 | # now create this repo on Filesystem | |
282 | RepoModel()._create_filesystem_repo( |
|
282 | RepoModel()._create_filesystem_repo( | |
283 | repo_name=repo_name, |
|
283 | repo_name=repo_name, | |
284 | repo_type=repo_type, |
|
284 | repo_type=repo_type, | |
285 | repo_group=RepoModel()._get_repo_group(repo_group), |
|
285 | repo_group=RepoModel()._get_repo_group(repo_group), | |
286 | clone_uri=source_repo_path, |
|
286 | clone_uri=source_repo_path, | |
287 | ) |
|
287 | ) | |
288 | repo = Repository.get_by_repo_name(repo_name_full) |
|
288 | repo = Repository.get_by_repo_name(repo_name_full) | |
289 | hooks_base.create_repository(created_by=owner.username, **repo.get_dict()) |
|
289 | hooks_base.create_repository(created_by=owner.username, **repo.get_dict()) | |
290 |
|
290 | |||
291 | # update repo commit caches initially |
|
291 | # update repo commit caches initially | |
292 | config = repo._config |
|
292 | config = repo._config | |
293 | config.set('extensions', 'largefiles', '') |
|
293 | config.set('extensions', 'largefiles', '') | |
294 | repo.update_commit_cache(config=config) |
|
294 | repo.update_commit_cache(config=config) | |
295 |
|
295 | |||
296 | # set new created state |
|
296 | # set new created state | |
297 | repo.set_state(Repository.STATE_CREATED) |
|
297 | repo.set_state(Repository.STATE_CREATED) | |
298 |
|
298 | |||
299 | repo_id = repo.repo_id |
|
299 | repo_id = repo.repo_id | |
300 | repo_data = repo.get_api_data() |
|
300 | repo_data = repo.get_api_data() | |
301 | audit_logger.store( |
|
301 | audit_logger.store( | |
302 | 'repo.fork', action_data={'data': repo_data}, |
|
302 | 'repo.fork', action_data={'data': repo_data}, | |
303 | user=cur_user, |
|
303 | user=cur_user, | |
304 | repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id)) |
|
304 | repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id)) | |
305 |
|
305 | |||
306 | Session().commit() |
|
306 | Session().commit() | |
307 | except Exception as e: |
|
307 | except Exception as e: | |
308 | log.warning('Exception occurred when forking repository, ' |
|
308 | log.warning('Exception occurred when forking repository, ' | |
309 | 'doing cleanup...', exc_info=True) |
|
309 | 'doing cleanup...', exc_info=True) | |
310 | if isinstance(e, IntegrityError): |
|
310 | if isinstance(e, IntegrityError): | |
311 | Session().rollback() |
|
311 | Session().rollback() | |
312 |
|
312 | |||
313 | # rollback things manually ! |
|
313 | # rollback things manually ! | |
314 | repo = Repository.get_by_repo_name(repo_name_full) |
|
314 | repo = Repository.get_by_repo_name(repo_name_full) | |
315 | if repo: |
|
315 | if repo: | |
316 | Repository.delete(repo.repo_id) |
|
316 | Repository.delete(repo.repo_id) | |
317 | Session().commit() |
|
317 | Session().commit() | |
318 | RepoModel()._delete_filesystem_repo(repo) |
|
318 | RepoModel()._delete_filesystem_repo(repo) | |
319 | log.info('Cleanup of repo %s finished', repo_name_full) |
|
319 | log.info('Cleanup of repo %s finished', repo_name_full) | |
320 | raise |
|
320 | raise | |
321 |
|
321 | |||
322 | return True |
|
322 | return True | |
323 |
|
323 | |||
324 |
|
324 | |||
325 | @async_task(ignore_result=True) |
|
325 | @async_task(ignore_result=True) | |
326 | def repo_maintenance(repoid): |
|
326 | def repo_maintenance(repoid): | |
327 | from rhodecode.lib import repo_maintenance as repo_maintenance_lib |
|
327 | from rhodecode.lib import repo_maintenance as repo_maintenance_lib | |
328 | log = get_logger(repo_maintenance) |
|
328 | log = get_logger(repo_maintenance) | |
329 | repo = Repository.get_by_id_or_repo_name(repoid) |
|
329 | repo = Repository.get_by_id_or_repo_name(repoid) | |
330 | if repo: |
|
330 | if repo: | |
331 | maintenance = repo_maintenance_lib.RepoMaintenance() |
|
331 | maintenance = repo_maintenance_lib.RepoMaintenance() | |
332 | tasks = maintenance.get_tasks_for_repo(repo) |
|
332 | tasks = maintenance.get_tasks_for_repo(repo) | |
333 | log.debug('Executing %s tasks on repo `%s`', tasks, repoid) |
|
333 | log.debug('Executing %s tasks on repo `%s`', tasks, repoid) | |
334 | executed_types = maintenance.execute(repo) |
|
334 | executed_types = maintenance.execute(repo) | |
335 | log.debug('Got execution results %s', executed_types) |
|
335 | log.debug('Got execution results %s', executed_types) | |
336 | else: |
|
336 | else: | |
337 | log.debug('Repo `%s` not found or without a clone_url', repoid) |
|
337 | log.debug('Repo `%s` not found or without a clone_url', repoid) | |
338 |
|
338 | |||
339 |
|
339 | |||
340 | @async_task(ignore_result=True) |
|
340 | @async_task(ignore_result=True) | |
341 | def check_for_update(): |
|
341 | def check_for_update(send_email_notification=True, email_recipients=None): | |
342 | from rhodecode.model.update import UpdateModel |
|
342 | from rhodecode.model.update import UpdateModel | |
|
343 | from rhodecode.model.notification import EmailNotificationModel | |||
|
344 | ||||
|
345 | log = get_logger(check_for_update) | |||
343 | update_url = UpdateModel().get_update_url() |
|
346 | update_url = UpdateModel().get_update_url() | |
344 | cur_ver = rhodecode.__version__ |
|
347 | cur_ver = rhodecode.__version__ | |
345 |
|
348 | |||
346 | try: |
|
349 | try: | |
347 | data = UpdateModel().get_update_data(update_url) |
|
350 | data = UpdateModel().get_update_data(update_url) | |
348 | latest = data['versions'][0] |
|
351 | ||
349 |
UpdateModel().store_version( |
|
352 | current_ver = UpdateModel().get_stored_version(fallback=cur_ver) | |
|
353 | latest_ver = data['versions'][0]['version'] | |||
|
354 | UpdateModel().store_version(latest_ver) | |||
|
355 | ||||
|
356 | if send_email_notification: | |||
|
357 | log.debug('Send email notification is enabled. ' | |||
|
358 | 'Current RhodeCode version: %s, latest known: %s', current_ver, latest_ver) | |||
|
359 | if UpdateModel().is_outdated(current_ver, latest_ver): | |||
|
360 | ||||
|
361 | email_kwargs = { | |||
|
362 | 'current_ver': current_ver, | |||
|
363 | 'latest_ver': latest_ver, | |||
|
364 | } | |||
|
365 | ||||
|
366 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( | |||
|
367 | EmailNotificationModel.TYPE_UPDATE_AVAILABLE, **email_kwargs) | |||
|
368 | ||||
|
369 | email_recipients = aslist(email_recipients, sep=',') or \ | |||
|
370 | [user.email for user in User.get_all_super_admins()] | |||
|
371 | run_task(send_email, email_recipients, subject, | |||
|
372 | email_body_plaintext, email_body) | |||
|
373 | ||||
350 | except Exception: |
|
374 | except Exception: | |
351 | pass |
|
375 | pass | |
352 |
|
376 | |||
353 |
|
377 | |||
354 | @async_task(ignore_result=False) |
|
378 | @async_task(ignore_result=False) | |
355 | def beat_check(*args, **kwargs): |
|
379 | def beat_check(*args, **kwargs): | |
356 | log = get_logger(beat_check) |
|
380 | log = get_logger(beat_check) | |
357 | log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs) |
|
381 | log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs) | |
358 | return time.time() |
|
382 | return time.time() | |
359 |
|
383 | |||
360 |
|
384 | |||
361 | @async_task(ignore_result=True) |
|
385 | @async_task(ignore_result=True) | |
362 | def sync_last_update(*args, **kwargs): |
|
386 | def sync_last_update(*args, **kwargs): | |
363 |
|
387 | |||
364 | skip_repos = kwargs.get('skip_repos') |
|
388 | skip_repos = kwargs.get('skip_repos') | |
365 | if not skip_repos: |
|
389 | if not skip_repos: | |
366 | repos = Repository.query() \ |
|
390 | repos = Repository.query() \ | |
367 | .order_by(Repository.group_id.asc()) |
|
391 | .order_by(Repository.group_id.asc()) | |
368 |
|
392 | |||
369 | for repo in repos: |
|
393 | for repo in repos: | |
370 | repo.update_commit_cache() |
|
394 | repo.update_commit_cache() | |
371 |
|
395 | |||
372 | skip_groups = kwargs.get('skip_groups') |
|
396 | skip_groups = kwargs.get('skip_groups') | |
373 | if not skip_groups: |
|
397 | if not skip_groups: | |
374 | repo_groups = RepoGroup.query() \ |
|
398 | repo_groups = RepoGroup.query() \ | |
375 | .filter(RepoGroup.group_parent_id == None) |
|
399 | .filter(RepoGroup.group_parent_id == None) | |
376 |
|
400 | |||
377 | for root_gr in repo_groups: |
|
401 | for root_gr in repo_groups: | |
378 | for repo_gr in reversed(root_gr.recursive_groups()): |
|
402 | for repo_gr in reversed(root_gr.recursive_groups()): | |
379 | repo_gr.update_commit_cache() |
|
403 | repo_gr.update_commit_cache() |
@@ -1,450 +1,453 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2011-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2011-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | """ |
|
22 | """ | |
23 | Model for notifications |
|
23 | Model for notifications | |
24 | """ |
|
24 | """ | |
25 |
|
25 | |||
26 | import logging |
|
26 | import logging | |
27 | import traceback |
|
27 | import traceback | |
28 |
|
28 | |||
29 | import premailer |
|
29 | import premailer | |
30 | from pyramid.threadlocal import get_current_request |
|
30 | from pyramid.threadlocal import get_current_request | |
31 | from sqlalchemy.sql.expression import false, true |
|
31 | from sqlalchemy.sql.expression import false, true | |
32 |
|
32 | |||
33 | import rhodecode |
|
33 | import rhodecode | |
34 | from rhodecode.lib import helpers as h |
|
34 | from rhodecode.lib import helpers as h | |
35 | from rhodecode.model import BaseModel |
|
35 | from rhodecode.model import BaseModel | |
36 | from rhodecode.model.db import Notification, User, UserNotification |
|
36 | from rhodecode.model.db import Notification, User, UserNotification | |
37 | from rhodecode.model.meta import Session |
|
37 | from rhodecode.model.meta import Session | |
38 | from rhodecode.translation import TranslationString |
|
38 | from rhodecode.translation import TranslationString | |
39 |
|
39 | |||
40 | log = logging.getLogger(__name__) |
|
40 | log = logging.getLogger(__name__) | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | class NotificationModel(BaseModel): |
|
43 | class NotificationModel(BaseModel): | |
44 |
|
44 | |||
45 | cls = Notification |
|
45 | cls = Notification | |
46 |
|
46 | |||
47 | def __get_notification(self, notification): |
|
47 | def __get_notification(self, notification): | |
48 | if isinstance(notification, Notification): |
|
48 | if isinstance(notification, Notification): | |
49 | return notification |
|
49 | return notification | |
50 | elif isinstance(notification, (int, long)): |
|
50 | elif isinstance(notification, (int, long)): | |
51 | return Notification.get(notification) |
|
51 | return Notification.get(notification) | |
52 | else: |
|
52 | else: | |
53 | if notification: |
|
53 | if notification: | |
54 | raise Exception('notification must be int, long or Instance' |
|
54 | raise Exception('notification must be int, long or Instance' | |
55 | ' of Notification got %s' % type(notification)) |
|
55 | ' of Notification got %s' % type(notification)) | |
56 |
|
56 | |||
57 | def create( |
|
57 | def create( | |
58 | self, created_by, notification_subject='', notification_body='', |
|
58 | self, created_by, notification_subject='', notification_body='', | |
59 | notification_type=Notification.TYPE_MESSAGE, recipients=None, |
|
59 | notification_type=Notification.TYPE_MESSAGE, recipients=None, | |
60 | mention_recipients=None, with_email=True, email_kwargs=None): |
|
60 | mention_recipients=None, with_email=True, email_kwargs=None): | |
61 | """ |
|
61 | """ | |
62 |
|
62 | |||
63 | Creates notification of given type |
|
63 | Creates notification of given type | |
64 |
|
64 | |||
65 | :param created_by: int, str or User instance. User who created this |
|
65 | :param created_by: int, str or User instance. User who created this | |
66 | notification |
|
66 | notification | |
67 | :param notification_subject: subject of notification itself, |
|
67 | :param notification_subject: subject of notification itself, | |
68 | it will be generated automatically from notification_type if not specified |
|
68 | it will be generated automatically from notification_type if not specified | |
69 | :param notification_body: body of notification text |
|
69 | :param notification_body: body of notification text | |
70 | it will be generated automatically from notification_type if not specified |
|
70 | it will be generated automatically from notification_type if not specified | |
71 | :param notification_type: type of notification, based on that we |
|
71 | :param notification_type: type of notification, based on that we | |
72 | pick templates |
|
72 | pick templates | |
73 | :param recipients: list of int, str or User objects, when None |
|
73 | :param recipients: list of int, str or User objects, when None | |
74 | is given send to all admins |
|
74 | is given send to all admins | |
75 | :param mention_recipients: list of int, str or User objects, |
|
75 | :param mention_recipients: list of int, str or User objects, | |
76 | that were mentioned |
|
76 | that were mentioned | |
77 | :param with_email: send email with this notification |
|
77 | :param with_email: send email with this notification | |
78 | :param email_kwargs: dict with arguments to generate email |
|
78 | :param email_kwargs: dict with arguments to generate email | |
79 | """ |
|
79 | """ | |
80 |
|
80 | |||
81 | from rhodecode.lib.celerylib import tasks, run_task |
|
81 | from rhodecode.lib.celerylib import tasks, run_task | |
82 |
|
82 | |||
83 | if recipients and not getattr(recipients, '__iter__', False): |
|
83 | if recipients and not getattr(recipients, '__iter__', False): | |
84 | raise Exception('recipients must be an iterable object') |
|
84 | raise Exception('recipients must be an iterable object') | |
85 |
|
85 | |||
86 | if not (notification_subject and notification_body) and not notification_type: |
|
86 | if not (notification_subject and notification_body) and not notification_type: | |
87 | raise ValueError('notification_subject, and notification_body ' |
|
87 | raise ValueError('notification_subject, and notification_body ' | |
88 | 'cannot be empty when notification_type is not specified') |
|
88 | 'cannot be empty when notification_type is not specified') | |
89 |
|
89 | |||
90 | created_by_obj = self._get_user(created_by) |
|
90 | created_by_obj = self._get_user(created_by) | |
91 |
|
91 | |||
92 | if not created_by_obj: |
|
92 | if not created_by_obj: | |
93 | raise Exception('unknown user %s' % created_by) |
|
93 | raise Exception('unknown user %s' % created_by) | |
94 |
|
94 | |||
95 | # default MAIN body if not given |
|
95 | # default MAIN body if not given | |
96 | email_kwargs = email_kwargs or {'body': notification_body} |
|
96 | email_kwargs = email_kwargs or {'body': notification_body} | |
97 | mention_recipients = mention_recipients or set() |
|
97 | mention_recipients = mention_recipients or set() | |
98 |
|
98 | |||
99 | if recipients is None: |
|
99 | if recipients is None: | |
100 | # recipients is None means to all admins |
|
100 | # recipients is None means to all admins | |
101 | recipients_objs = User.query().filter(User.admin == true()).all() |
|
101 | recipients_objs = User.query().filter(User.admin == true()).all() | |
102 | log.debug('sending notifications %s to admins: %s', |
|
102 | log.debug('sending notifications %s to admins: %s', | |
103 | notification_type, recipients_objs) |
|
103 | notification_type, recipients_objs) | |
104 | else: |
|
104 | else: | |
105 | recipients_objs = set() |
|
105 | recipients_objs = set() | |
106 | for u in recipients: |
|
106 | for u in recipients: | |
107 | obj = self._get_user(u) |
|
107 | obj = self._get_user(u) | |
108 | if obj: |
|
108 | if obj: | |
109 | recipients_objs.add(obj) |
|
109 | recipients_objs.add(obj) | |
110 | else: # we didn't find this user, log the error and carry on |
|
110 | else: # we didn't find this user, log the error and carry on | |
111 | log.error('cannot notify unknown user %r', u) |
|
111 | log.error('cannot notify unknown user %r', u) | |
112 |
|
112 | |||
113 | if not recipients_objs: |
|
113 | if not recipients_objs: | |
114 | raise Exception('no valid recipients specified') |
|
114 | raise Exception('no valid recipients specified') | |
115 |
|
115 | |||
116 | log.debug('sending notifications %s to %s', |
|
116 | log.debug('sending notifications %s to %s', | |
117 | notification_type, recipients_objs) |
|
117 | notification_type, recipients_objs) | |
118 |
|
118 | |||
119 | # add mentioned users into recipients |
|
119 | # add mentioned users into recipients | |
120 | final_recipients = set(recipients_objs).union(mention_recipients) |
|
120 | final_recipients = set(recipients_objs).union(mention_recipients) | |
121 |
|
121 | |||
122 | (subject, email_body, email_body_plaintext) = \ |
|
122 | (subject, email_body, email_body_plaintext) = \ | |
123 | EmailNotificationModel().render_email(notification_type, **email_kwargs) |
|
123 | EmailNotificationModel().render_email(notification_type, **email_kwargs) | |
124 |
|
124 | |||
125 | if not notification_subject: |
|
125 | if not notification_subject: | |
126 | notification_subject = subject |
|
126 | notification_subject = subject | |
127 |
|
127 | |||
128 | if not notification_body: |
|
128 | if not notification_body: | |
129 | notification_body = email_body_plaintext |
|
129 | notification_body = email_body_plaintext | |
130 |
|
130 | |||
131 | notification = Notification.create( |
|
131 | notification = Notification.create( | |
132 | created_by=created_by_obj, subject=notification_subject, |
|
132 | created_by=created_by_obj, subject=notification_subject, | |
133 | body=notification_body, recipients=final_recipients, |
|
133 | body=notification_body, recipients=final_recipients, | |
134 | type_=notification_type |
|
134 | type_=notification_type | |
135 | ) |
|
135 | ) | |
136 |
|
136 | |||
137 | if not with_email: # skip sending email, and just create notification |
|
137 | if not with_email: # skip sending email, and just create notification | |
138 | return notification |
|
138 | return notification | |
139 |
|
139 | |||
140 | # don't send email to person who created this comment |
|
140 | # don't send email to person who created this comment | |
141 | rec_objs = set(recipients_objs).difference({created_by_obj}) |
|
141 | rec_objs = set(recipients_objs).difference({created_by_obj}) | |
142 |
|
142 | |||
143 | # now notify all recipients in question |
|
143 | # now notify all recipients in question | |
144 |
|
144 | |||
145 | for recipient in rec_objs.union(mention_recipients): |
|
145 | for recipient in rec_objs.union(mention_recipients): | |
146 | # inject current recipient |
|
146 | # inject current recipient | |
147 | email_kwargs['recipient'] = recipient |
|
147 | email_kwargs['recipient'] = recipient | |
148 | email_kwargs['mention'] = recipient in mention_recipients |
|
148 | email_kwargs['mention'] = recipient in mention_recipients | |
149 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( |
|
149 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( | |
150 | notification_type, **email_kwargs) |
|
150 | notification_type, **email_kwargs) | |
151 |
|
151 | |||
152 | extra_headers = None |
|
152 | extra_headers = None | |
153 | if 'thread_ids' in email_kwargs: |
|
153 | if 'thread_ids' in email_kwargs: | |
154 | extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')} |
|
154 | extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')} | |
155 |
|
155 | |||
156 | log.debug('Creating notification email task for user:`%s`', recipient) |
|
156 | log.debug('Creating notification email task for user:`%s`', recipient) | |
157 | task = run_task( |
|
157 | task = run_task( | |
158 | tasks.send_email, recipient.email, subject, |
|
158 | tasks.send_email, recipient.email, subject, | |
159 | email_body_plaintext, email_body, extra_headers=extra_headers) |
|
159 | email_body_plaintext, email_body, extra_headers=extra_headers) | |
160 | log.debug('Created email task: %s', task) |
|
160 | log.debug('Created email task: %s', task) | |
161 |
|
161 | |||
162 | return notification |
|
162 | return notification | |
163 |
|
163 | |||
164 | def delete(self, user, notification): |
|
164 | def delete(self, user, notification): | |
165 | # we don't want to remove actual notification just the assignment |
|
165 | # we don't want to remove actual notification just the assignment | |
166 | try: |
|
166 | try: | |
167 | notification = self.__get_notification(notification) |
|
167 | notification = self.__get_notification(notification) | |
168 | user = self._get_user(user) |
|
168 | user = self._get_user(user) | |
169 | if notification and user: |
|
169 | if notification and user: | |
170 | obj = UserNotification.query()\ |
|
170 | obj = UserNotification.query()\ | |
171 | .filter(UserNotification.user == user)\ |
|
171 | .filter(UserNotification.user == user)\ | |
172 | .filter(UserNotification.notification == notification)\ |
|
172 | .filter(UserNotification.notification == notification)\ | |
173 | .one() |
|
173 | .one() | |
174 | Session().delete(obj) |
|
174 | Session().delete(obj) | |
175 | return True |
|
175 | return True | |
176 | except Exception: |
|
176 | except Exception: | |
177 | log.error(traceback.format_exc()) |
|
177 | log.error(traceback.format_exc()) | |
178 | raise |
|
178 | raise | |
179 |
|
179 | |||
180 | def get_for_user(self, user, filter_=None): |
|
180 | def get_for_user(self, user, filter_=None): | |
181 | """ |
|
181 | """ | |
182 | Get mentions for given user, filter them if filter dict is given |
|
182 | Get mentions for given user, filter them if filter dict is given | |
183 | """ |
|
183 | """ | |
184 | user = self._get_user(user) |
|
184 | user = self._get_user(user) | |
185 |
|
185 | |||
186 | q = UserNotification.query()\ |
|
186 | q = UserNotification.query()\ | |
187 | .filter(UserNotification.user == user)\ |
|
187 | .filter(UserNotification.user == user)\ | |
188 | .join(( |
|
188 | .join(( | |
189 | Notification, UserNotification.notification_id == |
|
189 | Notification, UserNotification.notification_id == | |
190 | Notification.notification_id)) |
|
190 | Notification.notification_id)) | |
191 | if filter_ == ['all']: |
|
191 | if filter_ == ['all']: | |
192 | q = q # no filter |
|
192 | q = q # no filter | |
193 | elif filter_ == ['unread']: |
|
193 | elif filter_ == ['unread']: | |
194 | q = q.filter(UserNotification.read == false()) |
|
194 | q = q.filter(UserNotification.read == false()) | |
195 | elif filter_: |
|
195 | elif filter_: | |
196 | q = q.filter(Notification.type_.in_(filter_)) |
|
196 | q = q.filter(Notification.type_.in_(filter_)) | |
197 |
|
197 | |||
198 | return q |
|
198 | return q | |
199 |
|
199 | |||
200 | def mark_read(self, user, notification): |
|
200 | def mark_read(self, user, notification): | |
201 | try: |
|
201 | try: | |
202 | notification = self.__get_notification(notification) |
|
202 | notification = self.__get_notification(notification) | |
203 | user = self._get_user(user) |
|
203 | user = self._get_user(user) | |
204 | if notification and user: |
|
204 | if notification and user: | |
205 | obj = UserNotification.query()\ |
|
205 | obj = UserNotification.query()\ | |
206 | .filter(UserNotification.user == user)\ |
|
206 | .filter(UserNotification.user == user)\ | |
207 | .filter(UserNotification.notification == notification)\ |
|
207 | .filter(UserNotification.notification == notification)\ | |
208 | .one() |
|
208 | .one() | |
209 | obj.read = True |
|
209 | obj.read = True | |
210 | Session().add(obj) |
|
210 | Session().add(obj) | |
211 | return True |
|
211 | return True | |
212 | except Exception: |
|
212 | except Exception: | |
213 | log.error(traceback.format_exc()) |
|
213 | log.error(traceback.format_exc()) | |
214 | raise |
|
214 | raise | |
215 |
|
215 | |||
216 | def mark_all_read_for_user(self, user, filter_=None): |
|
216 | def mark_all_read_for_user(self, user, filter_=None): | |
217 | user = self._get_user(user) |
|
217 | user = self._get_user(user) | |
218 | q = UserNotification.query()\ |
|
218 | q = UserNotification.query()\ | |
219 | .filter(UserNotification.user == user)\ |
|
219 | .filter(UserNotification.user == user)\ | |
220 | .filter(UserNotification.read == false())\ |
|
220 | .filter(UserNotification.read == false())\ | |
221 | .join(( |
|
221 | .join(( | |
222 | Notification, UserNotification.notification_id == |
|
222 | Notification, UserNotification.notification_id == | |
223 | Notification.notification_id)) |
|
223 | Notification.notification_id)) | |
224 | if filter_ == ['unread']: |
|
224 | if filter_ == ['unread']: | |
225 | q = q.filter(UserNotification.read == false()) |
|
225 | q = q.filter(UserNotification.read == false()) | |
226 | elif filter_: |
|
226 | elif filter_: | |
227 | q = q.filter(Notification.type_.in_(filter_)) |
|
227 | q = q.filter(Notification.type_.in_(filter_)) | |
228 |
|
228 | |||
229 | # this is a little inefficient but sqlalchemy doesn't support |
|
229 | # this is a little inefficient but sqlalchemy doesn't support | |
230 | # update on joined tables :( |
|
230 | # update on joined tables :( | |
231 | for obj in q.all(): |
|
231 | for obj in q.all(): | |
232 | obj.read = True |
|
232 | obj.read = True | |
233 | Session().add(obj) |
|
233 | Session().add(obj) | |
234 |
|
234 | |||
235 | def get_unread_cnt_for_user(self, user): |
|
235 | def get_unread_cnt_for_user(self, user): | |
236 | user = self._get_user(user) |
|
236 | user = self._get_user(user) | |
237 | return UserNotification.query()\ |
|
237 | return UserNotification.query()\ | |
238 | .filter(UserNotification.read == false())\ |
|
238 | .filter(UserNotification.read == false())\ | |
239 | .filter(UserNotification.user == user).count() |
|
239 | .filter(UserNotification.user == user).count() | |
240 |
|
240 | |||
241 | def get_unread_for_user(self, user): |
|
241 | def get_unread_for_user(self, user): | |
242 | user = self._get_user(user) |
|
242 | user = self._get_user(user) | |
243 | return [x.notification for x in UserNotification.query() |
|
243 | return [x.notification for x in UserNotification.query() | |
244 | .filter(UserNotification.read == false()) |
|
244 | .filter(UserNotification.read == false()) | |
245 | .filter(UserNotification.user == user).all()] |
|
245 | .filter(UserNotification.user == user).all()] | |
246 |
|
246 | |||
247 | def get_user_notification(self, user, notification): |
|
247 | def get_user_notification(self, user, notification): | |
248 | user = self._get_user(user) |
|
248 | user = self._get_user(user) | |
249 | notification = self.__get_notification(notification) |
|
249 | notification = self.__get_notification(notification) | |
250 |
|
250 | |||
251 | return UserNotification.query()\ |
|
251 | return UserNotification.query()\ | |
252 | .filter(UserNotification.notification == notification)\ |
|
252 | .filter(UserNotification.notification == notification)\ | |
253 | .filter(UserNotification.user == user).scalar() |
|
253 | .filter(UserNotification.user == user).scalar() | |
254 |
|
254 | |||
255 | def make_description(self, notification, translate, show_age=True): |
|
255 | def make_description(self, notification, translate, show_age=True): | |
256 | """ |
|
256 | """ | |
257 | Creates a human readable description based on properties |
|
257 | Creates a human readable description based on properties | |
258 | of notification object |
|
258 | of notification object | |
259 | """ |
|
259 | """ | |
260 | _ = translate |
|
260 | _ = translate | |
261 | _map = { |
|
261 | _map = { | |
262 | notification.TYPE_CHANGESET_COMMENT: [ |
|
262 | notification.TYPE_CHANGESET_COMMENT: [ | |
263 | _('%(user)s commented on commit %(date_or_age)s'), |
|
263 | _('%(user)s commented on commit %(date_or_age)s'), | |
264 | _('%(user)s commented on commit at %(date_or_age)s'), |
|
264 | _('%(user)s commented on commit at %(date_or_age)s'), | |
265 | ], |
|
265 | ], | |
266 | notification.TYPE_MESSAGE: [ |
|
266 | notification.TYPE_MESSAGE: [ | |
267 | _('%(user)s sent message %(date_or_age)s'), |
|
267 | _('%(user)s sent message %(date_or_age)s'), | |
268 | _('%(user)s sent message at %(date_or_age)s'), |
|
268 | _('%(user)s sent message at %(date_or_age)s'), | |
269 | ], |
|
269 | ], | |
270 | notification.TYPE_MENTION: [ |
|
270 | notification.TYPE_MENTION: [ | |
271 | _('%(user)s mentioned you %(date_or_age)s'), |
|
271 | _('%(user)s mentioned you %(date_or_age)s'), | |
272 | _('%(user)s mentioned you at %(date_or_age)s'), |
|
272 | _('%(user)s mentioned you at %(date_or_age)s'), | |
273 | ], |
|
273 | ], | |
274 | notification.TYPE_REGISTRATION: [ |
|
274 | notification.TYPE_REGISTRATION: [ | |
275 | _('%(user)s registered in RhodeCode %(date_or_age)s'), |
|
275 | _('%(user)s registered in RhodeCode %(date_or_age)s'), | |
276 | _('%(user)s registered in RhodeCode at %(date_or_age)s'), |
|
276 | _('%(user)s registered in RhodeCode at %(date_or_age)s'), | |
277 | ], |
|
277 | ], | |
278 | notification.TYPE_PULL_REQUEST: [ |
|
278 | notification.TYPE_PULL_REQUEST: [ | |
279 | _('%(user)s opened new pull request %(date_or_age)s'), |
|
279 | _('%(user)s opened new pull request %(date_or_age)s'), | |
280 | _('%(user)s opened new pull request at %(date_or_age)s'), |
|
280 | _('%(user)s opened new pull request at %(date_or_age)s'), | |
281 | ], |
|
281 | ], | |
282 | notification.TYPE_PULL_REQUEST_UPDATE: [ |
|
282 | notification.TYPE_PULL_REQUEST_UPDATE: [ | |
283 | _('%(user)s updated pull request %(date_or_age)s'), |
|
283 | _('%(user)s updated pull request %(date_or_age)s'), | |
284 | _('%(user)s updated pull request at %(date_or_age)s'), |
|
284 | _('%(user)s updated pull request at %(date_or_age)s'), | |
285 | ], |
|
285 | ], | |
286 | notification.TYPE_PULL_REQUEST_COMMENT: [ |
|
286 | notification.TYPE_PULL_REQUEST_COMMENT: [ | |
287 | _('%(user)s commented on pull request %(date_or_age)s'), |
|
287 | _('%(user)s commented on pull request %(date_or_age)s'), | |
288 | _('%(user)s commented on pull request at %(date_or_age)s'), |
|
288 | _('%(user)s commented on pull request at %(date_or_age)s'), | |
289 | ], |
|
289 | ], | |
290 | } |
|
290 | } | |
291 |
|
291 | |||
292 | templates = _map[notification.type_] |
|
292 | templates = _map[notification.type_] | |
293 |
|
293 | |||
294 | if show_age: |
|
294 | if show_age: | |
295 | template = templates[0] |
|
295 | template = templates[0] | |
296 | date_or_age = h.age(notification.created_on) |
|
296 | date_or_age = h.age(notification.created_on) | |
297 | if translate: |
|
297 | if translate: | |
298 | date_or_age = translate(date_or_age) |
|
298 | date_or_age = translate(date_or_age) | |
299 |
|
299 | |||
300 | if isinstance(date_or_age, TranslationString): |
|
300 | if isinstance(date_or_age, TranslationString): | |
301 | date_or_age = date_or_age.interpolate() |
|
301 | date_or_age = date_or_age.interpolate() | |
302 |
|
302 | |||
303 | else: |
|
303 | else: | |
304 | template = templates[1] |
|
304 | template = templates[1] | |
305 | date_or_age = h.format_date(notification.created_on) |
|
305 | date_or_age = h.format_date(notification.created_on) | |
306 |
|
306 | |||
307 | return template % { |
|
307 | return template % { | |
308 | 'user': notification.created_by_user.username, |
|
308 | 'user': notification.created_by_user.username, | |
309 | 'date_or_age': date_or_age, |
|
309 | 'date_or_age': date_or_age, | |
310 | } |
|
310 | } | |
311 |
|
311 | |||
312 |
|
312 | |||
313 | # Templates for Titles, that could be overwritten by rcextensions |
|
313 | # Templates for Titles, that could be overwritten by rcextensions | |
314 | # Title of email for pull-request update |
|
314 | # Title of email for pull-request update | |
315 | EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = '' |
|
315 | EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = '' | |
316 | # Title of email for request for pull request review |
|
316 | # Title of email for request for pull request review | |
317 | EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = '' |
|
317 | EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = '' | |
318 |
|
318 | |||
319 | # Title of email for general comment on pull request |
|
319 | # Title of email for general comment on pull request | |
320 | EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = '' |
|
320 | EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = '' | |
321 | # Title of email for general comment which includes status change on pull request |
|
321 | # Title of email for general comment which includes status change on pull request | |
322 | EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '' |
|
322 | EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '' | |
323 | # Title of email for inline comment on a file in pull request |
|
323 | # Title of email for inline comment on a file in pull request | |
324 | EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = '' |
|
324 | EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = '' | |
325 |
|
325 | |||
326 | # Title of email for general comment on commit |
|
326 | # Title of email for general comment on commit | |
327 | EMAIL_COMMENT_SUBJECT_TEMPLATE = '' |
|
327 | EMAIL_COMMENT_SUBJECT_TEMPLATE = '' | |
328 | # Title of email for general comment which includes status change on commit |
|
328 | # Title of email for general comment which includes status change on commit | |
329 | EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '' |
|
329 | EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = '' | |
330 | # Title of email for inline comment on a file in commit |
|
330 | # Title of email for inline comment on a file in commit | |
331 | EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = '' |
|
331 | EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = '' | |
332 |
|
332 | |||
333 |
|
333 | |||
334 | class EmailNotificationModel(BaseModel): |
|
334 | class EmailNotificationModel(BaseModel): | |
335 | TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT |
|
335 | TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT | |
336 | TYPE_REGISTRATION = Notification.TYPE_REGISTRATION |
|
336 | TYPE_REGISTRATION = Notification.TYPE_REGISTRATION | |
337 | TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST |
|
337 | TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST | |
338 | TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT |
|
338 | TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT | |
339 | TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE |
|
339 | TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE | |
340 | TYPE_MAIN = Notification.TYPE_MESSAGE |
|
340 | TYPE_MAIN = Notification.TYPE_MESSAGE | |
341 |
|
341 | |||
342 | TYPE_PASSWORD_RESET = 'password_reset' |
|
342 | TYPE_PASSWORD_RESET = 'password_reset' | |
343 | TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation' |
|
343 | TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation' | |
344 | TYPE_EMAIL_TEST = 'email_test' |
|
344 | TYPE_EMAIL_TEST = 'email_test' | |
345 | TYPE_EMAIL_EXCEPTION = 'exception' |
|
345 | TYPE_EMAIL_EXCEPTION = 'exception' | |
|
346 | TYPE_UPDATE_AVAILABLE = 'update_available' | |||
346 | TYPE_TEST = 'test' |
|
347 | TYPE_TEST = 'test' | |
347 |
|
348 | |||
348 | email_types = { |
|
349 | email_types = { | |
349 | TYPE_MAIN: |
|
350 | TYPE_MAIN: | |
350 | 'rhodecode:templates/email_templates/main.mako', |
|
351 | 'rhodecode:templates/email_templates/main.mako', | |
351 | TYPE_TEST: |
|
352 | TYPE_TEST: | |
352 | 'rhodecode:templates/email_templates/test.mako', |
|
353 | 'rhodecode:templates/email_templates/test.mako', | |
353 | TYPE_EMAIL_EXCEPTION: |
|
354 | TYPE_EMAIL_EXCEPTION: | |
354 | 'rhodecode:templates/email_templates/exception_tracker.mako', |
|
355 | 'rhodecode:templates/email_templates/exception_tracker.mako', | |
|
356 | TYPE_UPDATE_AVAILABLE: | |||
|
357 | 'rhodecode:templates/email_templates/update_available.mako', | |||
355 | TYPE_EMAIL_TEST: |
|
358 | TYPE_EMAIL_TEST: | |
356 | 'rhodecode:templates/email_templates/email_test.mako', |
|
359 | 'rhodecode:templates/email_templates/email_test.mako', | |
357 | TYPE_REGISTRATION: |
|
360 | TYPE_REGISTRATION: | |
358 | 'rhodecode:templates/email_templates/user_registration.mako', |
|
361 | 'rhodecode:templates/email_templates/user_registration.mako', | |
359 | TYPE_PASSWORD_RESET: |
|
362 | TYPE_PASSWORD_RESET: | |
360 | 'rhodecode:templates/email_templates/password_reset.mako', |
|
363 | 'rhodecode:templates/email_templates/password_reset.mako', | |
361 | TYPE_PASSWORD_RESET_CONFIRMATION: |
|
364 | TYPE_PASSWORD_RESET_CONFIRMATION: | |
362 | 'rhodecode:templates/email_templates/password_reset_confirmation.mako', |
|
365 | 'rhodecode:templates/email_templates/password_reset_confirmation.mako', | |
363 | TYPE_COMMIT_COMMENT: |
|
366 | TYPE_COMMIT_COMMENT: | |
364 | 'rhodecode:templates/email_templates/commit_comment.mako', |
|
367 | 'rhodecode:templates/email_templates/commit_comment.mako', | |
365 | TYPE_PULL_REQUEST: |
|
368 | TYPE_PULL_REQUEST: | |
366 | 'rhodecode:templates/email_templates/pull_request_review.mako', |
|
369 | 'rhodecode:templates/email_templates/pull_request_review.mako', | |
367 | TYPE_PULL_REQUEST_COMMENT: |
|
370 | TYPE_PULL_REQUEST_COMMENT: | |
368 | 'rhodecode:templates/email_templates/pull_request_comment.mako', |
|
371 | 'rhodecode:templates/email_templates/pull_request_comment.mako', | |
369 | TYPE_PULL_REQUEST_UPDATE: |
|
372 | TYPE_PULL_REQUEST_UPDATE: | |
370 | 'rhodecode:templates/email_templates/pull_request_update.mako', |
|
373 | 'rhodecode:templates/email_templates/pull_request_update.mako', | |
371 | } |
|
374 | } | |
372 |
|
375 | |||
373 | premailer_instance = premailer.Premailer( |
|
376 | premailer_instance = premailer.Premailer( | |
374 | cssutils_logging_level=logging.ERROR, |
|
377 | cssutils_logging_level=logging.ERROR, | |
375 | cssutils_logging_handler=logging.getLogger().handlers[0] |
|
378 | cssutils_logging_handler=logging.getLogger().handlers[0] | |
376 | if logging.getLogger().handlers else None, |
|
379 | if logging.getLogger().handlers else None, | |
377 | ) |
|
380 | ) | |
378 |
|
381 | |||
379 | def __init__(self): |
|
382 | def __init__(self): | |
380 | """ |
|
383 | """ | |
381 | Example usage:: |
|
384 | Example usage:: | |
382 |
|
385 | |||
383 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( |
|
386 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( | |
384 | EmailNotificationModel.TYPE_TEST, **email_kwargs) |
|
387 | EmailNotificationModel.TYPE_TEST, **email_kwargs) | |
385 |
|
388 | |||
386 | """ |
|
389 | """ | |
387 | super(EmailNotificationModel, self).__init__() |
|
390 | super(EmailNotificationModel, self).__init__() | |
388 | self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title') |
|
391 | self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title') | |
389 |
|
392 | |||
390 | def _update_kwargs_for_render(self, kwargs): |
|
393 | def _update_kwargs_for_render(self, kwargs): | |
391 | """ |
|
394 | """ | |
392 | Inject params required for Mako rendering |
|
395 | Inject params required for Mako rendering | |
393 |
|
396 | |||
394 | :param kwargs: |
|
397 | :param kwargs: | |
395 | """ |
|
398 | """ | |
396 |
|
399 | |||
397 | kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name |
|
400 | kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name | |
398 | kwargs['rhodecode_version'] = rhodecode.__version__ |
|
401 | kwargs['rhodecode_version'] = rhodecode.__version__ | |
399 | instance_url = h.route_url('home') |
|
402 | instance_url = h.route_url('home') | |
400 | _kwargs = { |
|
403 | _kwargs = { | |
401 | 'instance_url': instance_url, |
|
404 | 'instance_url': instance_url, | |
402 | 'whitespace_filter': self.whitespace_filter, |
|
405 | 'whitespace_filter': self.whitespace_filter, | |
403 | 'email_pr_update_subject_template': EMAIL_PR_UPDATE_SUBJECT_TEMPLATE, |
|
406 | 'email_pr_update_subject_template': EMAIL_PR_UPDATE_SUBJECT_TEMPLATE, | |
404 | 'email_pr_review_subject_template': EMAIL_PR_REVIEW_SUBJECT_TEMPLATE, |
|
407 | 'email_pr_review_subject_template': EMAIL_PR_REVIEW_SUBJECT_TEMPLATE, | |
405 | 'email_pr_comment_subject_template': EMAIL_PR_COMMENT_SUBJECT_TEMPLATE, |
|
408 | 'email_pr_comment_subject_template': EMAIL_PR_COMMENT_SUBJECT_TEMPLATE, | |
406 | 'email_pr_comment_status_change_subject_template': EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE, |
|
409 | 'email_pr_comment_status_change_subject_template': EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE, | |
407 | 'email_pr_comment_file_subject_template': EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE, |
|
410 | 'email_pr_comment_file_subject_template': EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE, | |
408 | 'email_comment_subject_template': EMAIL_COMMENT_SUBJECT_TEMPLATE, |
|
411 | 'email_comment_subject_template': EMAIL_COMMENT_SUBJECT_TEMPLATE, | |
409 | 'email_comment_status_change_subject_template': EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE, |
|
412 | 'email_comment_status_change_subject_template': EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE, | |
410 | 'email_comment_file_subject_template': EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE, |
|
413 | 'email_comment_file_subject_template': EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE, | |
411 | } |
|
414 | } | |
412 | _kwargs.update(kwargs) |
|
415 | _kwargs.update(kwargs) | |
413 | return _kwargs |
|
416 | return _kwargs | |
414 |
|
417 | |||
415 | def whitespace_filter(self, text): |
|
418 | def whitespace_filter(self, text): | |
416 | return text.replace('\n', '').replace('\t', '') |
|
419 | return text.replace('\n', '').replace('\t', '') | |
417 |
|
420 | |||
418 | def get_renderer(self, type_, request): |
|
421 | def get_renderer(self, type_, request): | |
419 | template_name = self.email_types[type_] |
|
422 | template_name = self.email_types[type_] | |
420 | return request.get_partial_renderer(template_name) |
|
423 | return request.get_partial_renderer(template_name) | |
421 |
|
424 | |||
422 | def render_email(self, type_, **kwargs): |
|
425 | def render_email(self, type_, **kwargs): | |
423 | """ |
|
426 | """ | |
424 | renders template for email, and returns a tuple of |
|
427 | renders template for email, and returns a tuple of | |
425 | (subject, email_headers, email_html_body, email_plaintext_body) |
|
428 | (subject, email_headers, email_html_body, email_plaintext_body) | |
426 | """ |
|
429 | """ | |
427 | # translator and helpers inject |
|
430 | # translator and helpers inject | |
428 | _kwargs = self._update_kwargs_for_render(kwargs) |
|
431 | _kwargs = self._update_kwargs_for_render(kwargs) | |
429 | request = get_current_request() |
|
432 | request = get_current_request() | |
430 | email_template = self.get_renderer(type_, request=request) |
|
433 | email_template = self.get_renderer(type_, request=request) | |
431 |
|
434 | |||
432 | subject = email_template.render('subject', **_kwargs) |
|
435 | subject = email_template.render('subject', **_kwargs) | |
433 |
|
436 | |||
434 | try: |
|
437 | try: | |
435 | body_plaintext = email_template.render('body_plaintext', **_kwargs) |
|
438 | body_plaintext = email_template.render('body_plaintext', **_kwargs) | |
436 | except AttributeError: |
|
439 | except AttributeError: | |
437 | # it's not defined in template, ok we can skip it |
|
440 | # it's not defined in template, ok we can skip it | |
438 | body_plaintext = '' |
|
441 | body_plaintext = '' | |
439 |
|
442 | |||
440 | # render WHOLE template |
|
443 | # render WHOLE template | |
441 | body = email_template.render(None, **_kwargs) |
|
444 | body = email_template.render(None, **_kwargs) | |
442 |
|
445 | |||
443 | try: |
|
446 | try: | |
444 | # Inline CSS styles and conversion |
|
447 | # Inline CSS styles and conversion | |
445 | body = self.premailer_instance.transform(body) |
|
448 | body = self.premailer_instance.transform(body) | |
446 | except Exception: |
|
449 | except Exception: | |
447 | log.exception('Failed to parse body with premailer') |
|
450 | log.exception('Failed to parse body with premailer') | |
448 | pass |
|
451 | pass | |
449 |
|
452 | |||
450 | return subject, body, body_plaintext |
|
453 | return subject, body, body_plaintext |
@@ -1,83 +1,83 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2013-2020 RhodeCode GmbH |
|
3 | # Copyright (C) 2013-2020 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import logging |
|
21 | import logging | |
22 | import urllib2 |
|
22 | import urllib2 | |
23 | from packaging.version import Version |
|
23 | from packaging.version import Version | |
24 |
|
24 | |||
25 | import rhodecode |
|
25 | import rhodecode | |
26 | from rhodecode.lib.ext_json import json |
|
26 | from rhodecode.lib.ext_json import json | |
27 | from rhodecode.model import BaseModel |
|
27 | from rhodecode.model import BaseModel | |
28 | from rhodecode.model.meta import Session |
|
28 | from rhodecode.model.meta import Session | |
29 | from rhodecode.model.settings import SettingsModel |
|
29 | from rhodecode.model.settings import SettingsModel | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | log = logging.getLogger(__name__) |
|
32 | log = logging.getLogger(__name__) | |
33 |
|
33 | |||
34 |
|
34 | |||
35 | class UpdateModel(BaseModel): |
|
35 | class UpdateModel(BaseModel): | |
36 | UPDATE_SETTINGS_KEY = 'update_version' |
|
36 | UPDATE_SETTINGS_KEY = 'update_version' | |
37 | UPDATE_URL_SETTINGS_KEY = 'rhodecode_update_url' |
|
37 | UPDATE_URL_SETTINGS_KEY = 'rhodecode_update_url' | |
38 |
|
38 | |||
39 | @staticmethod |
|
39 | @staticmethod | |
40 | def get_update_data(update_url): |
|
40 | def get_update_data(update_url): | |
41 | """Return the JSON update data.""" |
|
41 | """Return the JSON update data.""" | |
42 | ver = rhodecode.__version__ |
|
42 | ver = rhodecode.__version__ | |
43 | log.debug('Checking for upgrade on `%s` server', update_url) |
|
43 | log.debug('Checking for upgrade on `%s` server', update_url) | |
44 | opener = urllib2.build_opener() |
|
44 | opener = urllib2.build_opener() | |
45 | opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)] |
|
45 | opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)] | |
46 | response = opener.open(update_url) |
|
46 | response = opener.open(update_url) | |
47 | response_data = response.read() |
|
47 | response_data = response.read() | |
48 | data = json.loads(response_data) |
|
48 | data = json.loads(response_data) | |
49 | log.debug('update server returned data') |
|
49 | log.debug('update server returned data') | |
50 | return data |
|
50 | return data | |
51 |
|
51 | |||
52 | def get_update_url(self): |
|
52 | def get_update_url(self): | |
53 | settings = SettingsModel().get_all_settings() |
|
53 | settings = SettingsModel().get_all_settings() | |
54 | return settings.get(self.UPDATE_URL_SETTINGS_KEY) |
|
54 | return settings.get(self.UPDATE_URL_SETTINGS_KEY) | |
55 |
|
55 | |||
56 | def store_version(self, version): |
|
56 | def store_version(self, version): | |
57 | log.debug('Storing version %s into settings', version) |
|
57 | log.debug('Storing version %s into settings', version) | |
58 | setting = SettingsModel().create_or_update_setting( |
|
58 | setting = SettingsModel().create_or_update_setting( | |
59 | self.UPDATE_SETTINGS_KEY, version) |
|
59 | self.UPDATE_SETTINGS_KEY, version) | |
60 | Session().add(setting) |
|
60 | Session().add(setting) | |
61 | Session().commit() |
|
61 | Session().commit() | |
62 |
|
62 | |||
63 | def get_stored_version(self): |
|
63 | def get_stored_version(self, fallback=None): | |
64 | obj = SettingsModel().get_setting_by_name(self.UPDATE_SETTINGS_KEY) |
|
64 | obj = SettingsModel().get_setting_by_name(self.UPDATE_SETTINGS_KEY) | |
65 | if obj: |
|
65 | if obj: | |
66 | return obj.app_settings_value |
|
66 | return obj.app_settings_value | |
67 | return '0.0.0' |
|
67 | return fallback or '0.0.0' | |
68 |
|
68 | |||
69 | def _sanitize_version(self, version): |
|
69 | def _sanitize_version(self, version): | |
70 | """ |
|
70 | """ | |
71 | Cleanup our custom ver. |
|
71 | Cleanup our custom ver. | |
72 | e.g 4.11.0_20171204_204825_CE_default_EE_default to 4.11.0 |
|
72 | e.g 4.11.0_20171204_204825_CE_default_EE_default to 4.11.0 | |
73 | """ |
|
73 | """ | |
74 | return version.split('_')[0] |
|
74 | return version.split('_')[0] | |
75 |
|
75 | |||
76 | def is_outdated(self, cur_version, latest_version=None): |
|
76 | def is_outdated(self, cur_version, latest_version=None): | |
77 | latest_version = latest_version or self.get_stored_version() |
|
77 | latest_version = latest_version or self.get_stored_version() | |
78 | try: |
|
78 | try: | |
79 | cur_version = self._sanitize_version(cur_version) |
|
79 | cur_version = self._sanitize_version(cur_version) | |
80 | return Version(latest_version) > Version(cur_version) |
|
80 | return Version(latest_version) > Version(cur_version) | |
81 | except Exception: |
|
81 | except Exception: | |
82 | # could be invalid version, etc |
|
82 | # could be invalid version, etc | |
83 | return False |
|
83 | return False |
General Comments 0
You need to be logged in to leave comments.
Login now