##// END OF EJS Templates
core: moved channelstream test into pyramid.
marcink -
r1756:278bcf2a default
parent child Browse files
Show More
@@ -1,52 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def includeme(config):
26 26
27 27 config.add_route(
28 28 name='my_account_profile',
29 29 pattern=ADMIN_PREFIX + '/my_account/profile')
30 30
31 31 config.add_route(
32 32 name='my_account_password',
33 33 pattern=ADMIN_PREFIX + '/my_account/password')
34 34
35 35 config.add_route(
36 36 name='my_account_password_update',
37 37 pattern=ADMIN_PREFIX + '/my_account/password')
38 38
39 39 config.add_route(
40 40 name='my_account_auth_tokens',
41 41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
42 42 config.add_route(
43 43 name='my_account_auth_tokens_add',
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
45 )
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
46 45 config.add_route(
47 46 name='my_account_auth_tokens_delete',
48 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
49 )
47 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
48
49 # channelstream test
50 config.add_route(
51 name='my_account_notifications_test_channelstream',
52 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
50 53
51 54 # Scan module for configuration decorators.
52 55 config.scan()
@@ -1,194 +1,231 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 import datetime
22 23
23 24 from pyramid.httpexceptions import HTTPFound
24 25 from pyramid.view import view_config
25 26
26 27 from rhodecode.apps._base import BaseAppView
27 28 from rhodecode import forms
29 from rhodecode.lib import helpers as h
28 30 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29 from rhodecode.lib import helpers as h
31 from rhodecode.lib.channelstream import channelstream_request, \
32 ChannelstreamException
30 33 from rhodecode.lib.utils2 import safe_int, md5
31 34 from rhodecode.model.auth_token import AuthTokenModel
32 35 from rhodecode.model.meta import Session
33 36 from rhodecode.model.user import UserModel
34 37 from rhodecode.model.validation_schema.schemas import user_schema
35 38
36 39 log = logging.getLogger(__name__)
37 40
38 41
39 42 class MyAccountView(BaseAppView):
40 43 ALLOW_SCOPED_TOKENS = False
41 44 """
42 45 This view has alternative version inside EE, if modified please take a look
43 46 in there as well.
44 47 """
45 48
46 49 def load_default_context(self):
47 50 c = self._get_local_tmpl_context()
48 51 c.user = c.auth_user.get_instance()
49 52 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 53 self._register_global_c(c)
51 54 return c
52 55
53 56 @LoginRequired()
54 57 @NotAnonymous()
55 58 @view_config(
56 59 route_name='my_account_profile', request_method='GET',
57 60 renderer='rhodecode:templates/admin/my_account/my_account.mako')
58 61 def my_account_profile(self):
59 62 c = self.load_default_context()
60 63 c.active = 'profile'
61 64 return self._get_template_context(c)
62 65
63 66 @LoginRequired()
64 67 @NotAnonymous()
65 68 @view_config(
66 69 route_name='my_account_password', request_method='GET',
67 70 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 71 def my_account_password(self):
69 72 c = self.load_default_context()
70 73 c.active = 'password'
71 74 c.extern_type = c.user.extern_type
72 75
73 76 schema = user_schema.ChangePasswordSchema().bind(
74 77 username=c.user.username)
75 78
76 79 form = forms.Form(
77 80 schema, buttons=(forms.buttons.save, forms.buttons.reset))
78 81
79 82 c.form = form
80 83 return self._get_template_context(c)
81 84
82 85 @LoginRequired()
83 86 @NotAnonymous()
84 87 @CSRFRequired()
85 88 @view_config(
86 89 route_name='my_account_password', request_method='POST',
87 90 renderer='rhodecode:templates/admin/my_account/my_account.mako')
88 91 def my_account_password_update(self):
89 92 _ = self.request.translate
90 93 c = self.load_default_context()
91 94 c.active = 'password'
92 95 c.extern_type = c.user.extern_type
93 96
94 97 schema = user_schema.ChangePasswordSchema().bind(
95 98 username=c.user.username)
96 99
97 100 form = forms.Form(
98 101 schema, buttons=(forms.buttons.save, forms.buttons.reset))
99 102
100 103 if c.extern_type != 'rhodecode':
101 104 raise HTTPFound(self.request.route_path('my_account_password'))
102 105
103 106 controls = self.request.POST.items()
104 107 try:
105 108 valid_data = form.validate(controls)
106 109 UserModel().update_user(c.user.user_id, **valid_data)
107 110 c.user.update_userdata(force_password_change=False)
108 111 Session().commit()
109 112 except forms.ValidationFailure as e:
110 113 c.form = e
111 114 return self._get_template_context(c)
112 115
113 116 except Exception:
114 117 log.exception("Exception updating password")
115 118 h.flash(_('Error occurred during update of user password'),
116 119 category='error')
117 120 else:
118 121 instance = c.auth_user.get_instance()
119 122 self.session.setdefault('rhodecode_user', {}).update(
120 123 {'password': md5(instance.password)})
121 124 self.session.save()
122 125 h.flash(_("Successfully updated password"), category='success')
123 126
124 127 raise HTTPFound(self.request.route_path('my_account_password'))
125 128
126 129 @LoginRequired()
127 130 @NotAnonymous()
128 131 @view_config(
129 132 route_name='my_account_auth_tokens', request_method='GET',
130 133 renderer='rhodecode:templates/admin/my_account/my_account.mako')
131 134 def my_account_auth_tokens(self):
132 135 _ = self.request.translate
133 136
134 137 c = self.load_default_context()
135 138 c.active = 'auth_tokens'
136 139
137 140 c.lifetime_values = [
138 141 (str(-1), _('forever')),
139 142 (str(5), _('5 minutes')),
140 143 (str(60), _('1 hour')),
141 144 (str(60 * 24), _('1 day')),
142 145 (str(60 * 24 * 30), _('1 month')),
143 146 ]
144 147 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
145 148 c.role_values = [
146 149 (x, AuthTokenModel.cls._get_role_name(x))
147 150 for x in AuthTokenModel.cls.ROLES]
148 151 c.role_options = [(c.role_values, _("Role"))]
149 152 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
150 153 c.user.user_id, show_expired=True)
151 154 return self._get_template_context(c)
152 155
153 156 def maybe_attach_token_scope(self, token):
154 157 # implemented in EE edition
155 158 pass
156 159
157 160 @LoginRequired()
158 161 @NotAnonymous()
159 162 @CSRFRequired()
160 163 @view_config(
161 164 route_name='my_account_auth_tokens_add', request_method='POST')
162 165 def my_account_auth_tokens_add(self):
163 166 _ = self.request.translate
164 167 c = self.load_default_context()
165 168
166 169 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
167 170 description = self.request.POST.get('description')
168 171 role = self.request.POST.get('role')
169 172
170 173 token = AuthTokenModel().create(
171 174 c.user.user_id, description, lifetime, role)
172 175 self.maybe_attach_token_scope(token)
173 176 Session().commit()
174 177
175 178 h.flash(_("Auth token successfully created"), category='success')
176 179 return HTTPFound(h.route_path('my_account_auth_tokens'))
177 180
178 181 @LoginRequired()
179 182 @NotAnonymous()
180 183 @CSRFRequired()
181 184 @view_config(
182 185 route_name='my_account_auth_tokens_delete', request_method='POST')
183 186 def my_account_auth_tokens_delete(self):
184 187 _ = self.request.translate
185 188 c = self.load_default_context()
186 189
187 190 del_auth_token = self.request.POST.get('del_auth_token')
188 191
189 192 if del_auth_token:
190 193 AuthTokenModel().delete(del_auth_token, c.user.user_id)
191 194 Session().commit()
192 195 h.flash(_("Auth token successfully deleted"), category='success')
193 196
194 197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198
199 @LoginRequired()
200 @NotAnonymous()
201 @CSRFRequired()
202 @view_config(
203 route_name='my_account_notifications_test_channelstream',
204 request_method='POST', renderer='json_ext')
205 def my_account_notifications_test_channelstream(self):
206 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
207 self._rhodecode_user.username, datetime.datetime.now())
208 payload = {
209 # 'channel': 'broadcast',
210 'type': 'message',
211 'timestamp': datetime.datetime.utcnow(),
212 'user': 'system',
213 'pm_users': [self._rhodecode_user.username],
214 'message': {
215 'message': message,
216 'level': 'info',
217 'topic': '/notifications'
218 }
219 }
220
221 registry = self.request.registry
222 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
223 channelstream_config = rhodecode_plugins.get('channelstream', {})
224
225 try:
226 channelstream_request(channelstream_config, [payload], '/message')
227 except ChannelstreamException as e:
228 log.exception('Failed to send channelstream data')
229 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
230 return {"response": 'Channelstream data sent. '
231 'You should see a new live message now.'}
@@ -1,1057 +1,1053 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 121 always_scan=config['debug'])
122 122 rmap.minimization = False
123 123 rmap.explicit = False
124 124
125 125 from rhodecode.lib.utils2 import str2bool
126 126 from rhodecode.model import repo, repo_group
127 127
128 128 def check_repo(environ, match_dict):
129 129 """
130 130 check for valid repository for proper 404 handling
131 131
132 132 :param environ:
133 133 :param match_dict:
134 134 """
135 135 repo_name = match_dict.get('repo_name')
136 136
137 137 if match_dict.get('f_path'):
138 138 # fix for multiple initial slashes that causes errors
139 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 140 repo_model = repo.RepoModel()
141 141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 142 # if we match quickly from database, short circuit the operation,
143 143 # and validate repo based on the type.
144 144 if by_name_match:
145 145 return True
146 146
147 147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 148 if by_id_match:
149 149 repo_name = by_id_match.repo_name
150 150 match_dict['repo_name'] = repo_name
151 151 return True
152 152
153 153 return False
154 154
155 155 def check_group(environ, match_dict):
156 156 """
157 157 check for valid repository group path for proper 404 handling
158 158
159 159 :param environ:
160 160 :param match_dict:
161 161 """
162 162 repo_group_name = match_dict.get('group_name')
163 163 repo_group_model = repo_group.RepoGroupModel()
164 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 165 if by_name_match:
166 166 return True
167 167
168 168 return False
169 169
170 170 def check_user_group(environ, match_dict):
171 171 """
172 172 check for valid user group for proper 404 handling
173 173
174 174 :param environ:
175 175 :param match_dict:
176 176 """
177 177 return True
178 178
179 179 def check_int(environ, match_dict):
180 180 return match_dict.get('id').isdigit()
181 181
182 182
183 183 #==========================================================================
184 184 # CUSTOM ROUTES HERE
185 185 #==========================================================================
186 186
187 187 # MAIN PAGE
188 188 rmap.connect('home', '/', controller='home', action='index')
189 189
190 190 # ping and pylons error test
191 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193 193
194 194 # ADMIN REPOSITORY ROUTES
195 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 196 controller='admin/repos') as m:
197 197 m.connect('repos', '/repos',
198 198 action='create', conditions={'method': ['POST']})
199 199 m.connect('repos', '/repos',
200 200 action='index', conditions={'method': ['GET']})
201 201 m.connect('new_repo', '/create_repository', jsroute=True,
202 202 action='create_repository', conditions={'method': ['GET']})
203 203 m.connect('delete_repo', '/repos/{repo_name}',
204 204 action='delete', conditions={'method': ['DELETE']},
205 205 requirements=URL_NAME_REQUIREMENTS)
206 206 m.connect('repo', '/repos/{repo_name}',
207 207 action='show', conditions={'method': ['GET'],
208 208 'function': check_repo},
209 209 requirements=URL_NAME_REQUIREMENTS)
210 210
211 211 # ADMIN REPOSITORY GROUPS ROUTES
212 212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
213 213 controller='admin/repo_groups') as m:
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='create', conditions={'method': ['POST']})
216 216 m.connect('repo_groups', '/repo_groups',
217 217 action='index', conditions={'method': ['GET']})
218 218 m.connect('new_repo_group', '/repo_groups/new',
219 219 action='new', conditions={'method': ['GET']})
220 220 m.connect('update_repo_group', '/repo_groups/{group_name}',
221 221 action='update', conditions={'method': ['PUT'],
222 222 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224
225 225 # EXTRAS REPO GROUP ROUTES
226 226 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
227 227 action='edit',
228 228 conditions={'method': ['GET'], 'function': check_group},
229 229 requirements=URL_NAME_REQUIREMENTS)
230 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 231 action='edit',
232 232 conditions={'method': ['PUT'], 'function': check_group},
233 233 requirements=URL_NAME_REQUIREMENTS)
234 234
235 235 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
236 236 action='edit_repo_group_advanced',
237 237 conditions={'method': ['GET'], 'function': check_group},
238 238 requirements=URL_NAME_REQUIREMENTS)
239 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 240 action='edit_repo_group_advanced',
241 241 conditions={'method': ['PUT'], 'function': check_group},
242 242 requirements=URL_NAME_REQUIREMENTS)
243 243
244 244 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
245 245 action='edit_repo_group_perms',
246 246 conditions={'method': ['GET'], 'function': check_group},
247 247 requirements=URL_NAME_REQUIREMENTS)
248 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 249 action='update_perms',
250 250 conditions={'method': ['PUT'], 'function': check_group},
251 251 requirements=URL_NAME_REQUIREMENTS)
252 252
253 253 m.connect('delete_repo_group', '/repo_groups/{group_name}',
254 254 action='delete', conditions={'method': ['DELETE'],
255 255 'function': check_group},
256 256 requirements=URL_NAME_REQUIREMENTS)
257 257
258 258 # ADMIN USER ROUTES
259 259 with rmap.submapper(path_prefix=ADMIN_PREFIX,
260 260 controller='admin/users') as m:
261 261 m.connect('users', '/users',
262 262 action='create', conditions={'method': ['POST']})
263 263 m.connect('new_user', '/users/new',
264 264 action='new', conditions={'method': ['GET']})
265 265 m.connect('update_user', '/users/{user_id}',
266 266 action='update', conditions={'method': ['PUT']})
267 267 m.connect('delete_user', '/users/{user_id}',
268 268 action='delete', conditions={'method': ['DELETE']})
269 269 m.connect('edit_user', '/users/{user_id}/edit',
270 270 action='edit', conditions={'method': ['GET']}, jsroute=True)
271 271 m.connect('user', '/users/{user_id}',
272 272 action='show', conditions={'method': ['GET']})
273 273 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
274 274 action='reset_password', conditions={'method': ['POST']})
275 275 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
276 276 action='create_personal_repo_group', conditions={'method': ['POST']})
277 277
278 278 # EXTRAS USER ROUTES
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='edit_advanced', conditions={'method': ['GET']})
281 281 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
282 282 action='update_advanced', conditions={'method': ['PUT']})
283 283
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='edit_global_perms', conditions={'method': ['GET']})
286 286 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
287 287 action='update_global_perms', conditions={'method': ['PUT']})
288 288
289 289 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
290 290 action='edit_perms_summary', conditions={'method': ['GET']})
291 291
292 292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 293 action='edit_emails', conditions={'method': ['GET']})
294 294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 295 action='add_email', conditions={'method': ['PUT']})
296 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 297 action='delete_email', conditions={'method': ['DELETE']})
298 298
299 299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 300 action='edit_ips', conditions={'method': ['GET']})
301 301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 302 action='add_ip', conditions={'method': ['PUT']})
303 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 304 action='delete_ip', conditions={'method': ['DELETE']})
305 305
306 306 # ADMIN USER GROUPS REST ROUTES
307 307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
308 308 controller='admin/user_groups') as m:
309 309 m.connect('users_groups', '/user_groups',
310 310 action='create', conditions={'method': ['POST']})
311 311 m.connect('users_groups', '/user_groups',
312 312 action='index', conditions={'method': ['GET']})
313 313 m.connect('new_users_group', '/user_groups/new',
314 314 action='new', conditions={'method': ['GET']})
315 315 m.connect('update_users_group', '/user_groups/{user_group_id}',
316 316 action='update', conditions={'method': ['PUT']})
317 317 m.connect('delete_users_group', '/user_groups/{user_group_id}',
318 318 action='delete', conditions={'method': ['DELETE']})
319 319 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
320 320 action='edit', conditions={'method': ['GET']},
321 321 function=check_user_group)
322 322
323 323 # EXTRAS USER GROUP ROUTES
324 324 m.connect('edit_user_group_global_perms',
325 325 '/user_groups/{user_group_id}/edit/global_permissions',
326 326 action='edit_global_perms', conditions={'method': ['GET']})
327 327 m.connect('edit_user_group_global_perms',
328 328 '/user_groups/{user_group_id}/edit/global_permissions',
329 329 action='update_global_perms', conditions={'method': ['PUT']})
330 330 m.connect('edit_user_group_perms_summary',
331 331 '/user_groups/{user_group_id}/edit/permissions_summary',
332 332 action='edit_perms_summary', conditions={'method': ['GET']})
333 333
334 334 m.connect('edit_user_group_perms',
335 335 '/user_groups/{user_group_id}/edit/permissions',
336 336 action='edit_perms', conditions={'method': ['GET']})
337 337 m.connect('edit_user_group_perms',
338 338 '/user_groups/{user_group_id}/edit/permissions',
339 339 action='update_perms', conditions={'method': ['PUT']})
340 340
341 341 m.connect('edit_user_group_advanced',
342 342 '/user_groups/{user_group_id}/edit/advanced',
343 343 action='edit_advanced', conditions={'method': ['GET']})
344 344
345 345 m.connect('edit_user_group_advanced_sync',
346 346 '/user_groups/{user_group_id}/edit/advanced/sync',
347 347 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
348 348
349 349 m.connect('edit_user_group_members',
350 350 '/user_groups/{user_group_id}/edit/members', jsroute=True,
351 351 action='user_group_members', conditions={'method': ['GET']})
352 352
353 353 # ADMIN PERMISSIONS ROUTES
354 354 with rmap.submapper(path_prefix=ADMIN_PREFIX,
355 355 controller='admin/permissions') as m:
356 356 m.connect('admin_permissions_application', '/permissions/application',
357 357 action='permission_application_update', conditions={'method': ['POST']})
358 358 m.connect('admin_permissions_application', '/permissions/application',
359 359 action='permission_application', conditions={'method': ['GET']})
360 360
361 361 m.connect('admin_permissions_global', '/permissions/global',
362 362 action='permission_global_update', conditions={'method': ['POST']})
363 363 m.connect('admin_permissions_global', '/permissions/global',
364 364 action='permission_global', conditions={'method': ['GET']})
365 365
366 366 m.connect('admin_permissions_object', '/permissions/object',
367 367 action='permission_objects_update', conditions={'method': ['POST']})
368 368 m.connect('admin_permissions_object', '/permissions/object',
369 369 action='permission_objects', conditions={'method': ['GET']})
370 370
371 371 m.connect('admin_permissions_ips', '/permissions/ips',
372 372 action='permission_ips', conditions={'method': ['POST']})
373 373 m.connect('admin_permissions_ips', '/permissions/ips',
374 374 action='permission_ips', conditions={'method': ['GET']})
375 375
376 376 m.connect('admin_permissions_overview', '/permissions/overview',
377 377 action='permission_perms', conditions={'method': ['GET']})
378 378
379 379 # ADMIN DEFAULTS REST ROUTES
380 380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
381 381 controller='admin/defaults') as m:
382 382 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 383 action='update_repository_defaults', conditions={'method': ['POST']})
384 384 m.connect('admin_defaults_repositories', '/defaults/repositories',
385 385 action='index', conditions={'method': ['GET']})
386 386
387 387 # ADMIN DEBUG STYLE ROUTES
388 388 if str2bool(config.get('debug_style')):
389 389 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
390 390 controller='debug_style') as m:
391 391 m.connect('debug_style_home', '',
392 392 action='index', conditions={'method': ['GET']})
393 393 m.connect('debug_style_template', '/t/{t_path}',
394 394 action='template', conditions={'method': ['GET']})
395 395
396 396 # ADMIN SETTINGS ROUTES
397 397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 398 controller='admin/settings') as m:
399 399
400 400 # default
401 401 m.connect('admin_settings', '/settings',
402 402 action='settings_global_update',
403 403 conditions={'method': ['POST']})
404 404 m.connect('admin_settings', '/settings',
405 405 action='settings_global', conditions={'method': ['GET']})
406 406
407 407 m.connect('admin_settings_vcs', '/settings/vcs',
408 408 action='settings_vcs_update',
409 409 conditions={'method': ['POST']})
410 410 m.connect('admin_settings_vcs', '/settings/vcs',
411 411 action='settings_vcs',
412 412 conditions={'method': ['GET']})
413 413 m.connect('admin_settings_vcs', '/settings/vcs',
414 414 action='delete_svn_pattern',
415 415 conditions={'method': ['DELETE']})
416 416
417 417 m.connect('admin_settings_mapping', '/settings/mapping',
418 418 action='settings_mapping_update',
419 419 conditions={'method': ['POST']})
420 420 m.connect('admin_settings_mapping', '/settings/mapping',
421 421 action='settings_mapping', conditions={'method': ['GET']})
422 422
423 423 m.connect('admin_settings_global', '/settings/global',
424 424 action='settings_global_update',
425 425 conditions={'method': ['POST']})
426 426 m.connect('admin_settings_global', '/settings/global',
427 427 action='settings_global', conditions={'method': ['GET']})
428 428
429 429 m.connect('admin_settings_visual', '/settings/visual',
430 430 action='settings_visual_update',
431 431 conditions={'method': ['POST']})
432 432 m.connect('admin_settings_visual', '/settings/visual',
433 433 action='settings_visual', conditions={'method': ['GET']})
434 434
435 435 m.connect('admin_settings_issuetracker',
436 436 '/settings/issue-tracker', action='settings_issuetracker',
437 437 conditions={'method': ['GET']})
438 438 m.connect('admin_settings_issuetracker_save',
439 439 '/settings/issue-tracker/save',
440 440 action='settings_issuetracker_save',
441 441 conditions={'method': ['POST']})
442 442 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
443 443 action='settings_issuetracker_test',
444 444 conditions={'method': ['POST']})
445 445 m.connect('admin_issuetracker_delete',
446 446 '/settings/issue-tracker/delete',
447 447 action='settings_issuetracker_delete',
448 448 conditions={'method': ['DELETE']})
449 449
450 450 m.connect('admin_settings_email', '/settings/email',
451 451 action='settings_email_update',
452 452 conditions={'method': ['POST']})
453 453 m.connect('admin_settings_email', '/settings/email',
454 454 action='settings_email', conditions={'method': ['GET']})
455 455
456 456 m.connect('admin_settings_hooks', '/settings/hooks',
457 457 action='settings_hooks_update',
458 458 conditions={'method': ['POST', 'DELETE']})
459 459 m.connect('admin_settings_hooks', '/settings/hooks',
460 460 action='settings_hooks', conditions={'method': ['GET']})
461 461
462 462 m.connect('admin_settings_search', '/settings/search',
463 463 action='settings_search', conditions={'method': ['GET']})
464 464
465 465 m.connect('admin_settings_supervisor', '/settings/supervisor',
466 466 action='settings_supervisor', conditions={'method': ['GET']})
467 467 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
468 468 action='settings_supervisor_log', conditions={'method': ['GET']})
469 469
470 470 m.connect('admin_settings_labs', '/settings/labs',
471 471 action='settings_labs_update',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_settings_labs', '/settings/labs',
474 474 action='settings_labs', conditions={'method': ['GET']})
475 475
476 476 # ADMIN MY ACCOUNT
477 477 with rmap.submapper(path_prefix=ADMIN_PREFIX,
478 478 controller='admin/my_account') as m:
479 479
480 480 m.connect('my_account_edit', '/my_account/edit',
481 481 action='my_account_edit', conditions={'method': ['GET']})
482 482 m.connect('my_account', '/my_account/update',
483 483 action='my_account_update', conditions={'method': ['POST']})
484 484
485 485 # NOTE(marcink): this needs to be kept for password force flag to be
486 486 # handler, remove after migration to pyramid
487 487 m.connect('my_account_password', '/my_account/password',
488 488 action='my_account_password', conditions={'method': ['GET']})
489 489
490 490 m.connect('my_account_repos', '/my_account/repos',
491 491 action='my_account_repos', conditions={'method': ['GET']})
492 492
493 493 m.connect('my_account_watched', '/my_account/watched',
494 494 action='my_account_watched', conditions={'method': ['GET']})
495 495
496 496 m.connect('my_account_pullrequests', '/my_account/pull_requests',
497 497 action='my_account_pullrequests', conditions={'method': ['GET']})
498 498
499 499 m.connect('my_account_perms', '/my_account/perms',
500 500 action='my_account_perms', conditions={'method': ['GET']})
501 501
502 502 m.connect('my_account_emails', '/my_account/emails',
503 503 action='my_account_emails', conditions={'method': ['GET']})
504 504 m.connect('my_account_emails', '/my_account/emails',
505 505 action='my_account_emails_add', conditions={'method': ['POST']})
506 506 m.connect('my_account_emails', '/my_account/emails',
507 507 action='my_account_emails_delete', conditions={'method': ['DELETE']})
508 508
509 509 m.connect('my_account_notifications', '/my_account/notifications',
510 510 action='my_notifications',
511 511 conditions={'method': ['GET']})
512 512 m.connect('my_account_notifications_toggle_visibility',
513 513 '/my_account/toggle_visibility',
514 514 action='my_notifications_toggle_visibility',
515 515 conditions={'method': ['POST']})
516 m.connect('my_account_notifications_test_channelstream',
517 '/my_account/test_channelstream',
518 action='my_account_notifications_test_channelstream',
519 conditions={'method': ['POST']})
520 516
521 517 # NOTIFICATION REST ROUTES
522 518 with rmap.submapper(path_prefix=ADMIN_PREFIX,
523 519 controller='admin/notifications') as m:
524 520 m.connect('notifications', '/notifications',
525 521 action='index', conditions={'method': ['GET']})
526 522 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
527 523 action='mark_all_read', conditions={'method': ['POST']})
528 524 m.connect('/notifications/{notification_id}',
529 525 action='update', conditions={'method': ['PUT']})
530 526 m.connect('/notifications/{notification_id}',
531 527 action='delete', conditions={'method': ['DELETE']})
532 528 m.connect('notification', '/notifications/{notification_id}',
533 529 action='show', conditions={'method': ['GET']})
534 530
535 531 # ADMIN GIST
536 532 with rmap.submapper(path_prefix=ADMIN_PREFIX,
537 533 controller='admin/gists') as m:
538 534 m.connect('gists', '/gists',
539 535 action='create', conditions={'method': ['POST']})
540 536 m.connect('gists', '/gists', jsroute=True,
541 537 action='index', conditions={'method': ['GET']})
542 538 m.connect('new_gist', '/gists/new', jsroute=True,
543 539 action='new', conditions={'method': ['GET']})
544 540
545 541 m.connect('/gists/{gist_id}',
546 542 action='delete', conditions={'method': ['DELETE']})
547 543 m.connect('edit_gist', '/gists/{gist_id}/edit',
548 544 action='edit_form', conditions={'method': ['GET']})
549 545 m.connect('edit_gist', '/gists/{gist_id}/edit',
550 546 action='edit', conditions={'method': ['POST']})
551 547 m.connect(
552 548 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
553 549 action='check_revision', conditions={'method': ['GET']})
554 550
555 551 m.connect('gist', '/gists/{gist_id}',
556 552 action='show', conditions={'method': ['GET']})
557 553 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
558 554 revision='tip',
559 555 action='show', conditions={'method': ['GET']})
560 556 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
561 557 revision='tip',
562 558 action='show', conditions={'method': ['GET']})
563 559 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
564 560 revision='tip',
565 561 action='show', conditions={'method': ['GET']},
566 562 requirements=URL_NAME_REQUIREMENTS)
567 563
568 564 # ADMIN MAIN PAGES
569 565 with rmap.submapper(path_prefix=ADMIN_PREFIX,
570 566 controller='admin/admin') as m:
571 567 m.connect('admin_home', '', action='index')
572 568 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
573 569 action='add_repo')
574 570 m.connect(
575 571 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
576 572 action='pull_requests')
577 573 m.connect(
578 574 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
579 575 action='pull_requests')
580 576 m.connect(
581 577 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
582 578 action='pull_requests')
583 579
584 580 # USER JOURNAL
585 581 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
586 582 controller='journal', action='index')
587 583 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
588 584 controller='journal', action='journal_rss')
589 585 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
590 586 controller='journal', action='journal_atom')
591 587
592 588 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
593 589 controller='journal', action='public_journal')
594 590
595 591 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
596 592 controller='journal', action='public_journal_rss')
597 593
598 594 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
599 595 controller='journal', action='public_journal_rss')
600 596
601 597 rmap.connect('public_journal_atom',
602 598 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
603 599 action='public_journal_atom')
604 600
605 601 rmap.connect('public_journal_atom_old',
606 602 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
607 603 action='public_journal_atom')
608 604
609 605 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
610 606 controller='journal', action='toggle_following', jsroute=True,
611 607 conditions={'method': ['POST']})
612 608
613 609 # FEEDS
614 610 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
615 611 controller='feed', action='rss',
616 612 conditions={'function': check_repo},
617 613 requirements=URL_NAME_REQUIREMENTS)
618 614
619 615 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
620 616 controller='feed', action='atom',
621 617 conditions={'function': check_repo},
622 618 requirements=URL_NAME_REQUIREMENTS)
623 619
624 620 #==========================================================================
625 621 # REPOSITORY ROUTES
626 622 #==========================================================================
627 623
628 624 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
629 625 controller='admin/repos', action='repo_creating',
630 626 requirements=URL_NAME_REQUIREMENTS)
631 627 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
632 628 controller='admin/repos', action='repo_check',
633 629 requirements=URL_NAME_REQUIREMENTS)
634 630
635 631 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
636 632 controller='summary', action='repo_stats',
637 633 conditions={'function': check_repo},
638 634 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
639 635
640 636 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
641 637 controller='summary', action='repo_refs_data',
642 638 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 639 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
644 640 controller='summary', action='repo_refs_changelog_data',
645 641 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
646 642 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
647 643 controller='summary', action='repo_default_reviewers_data',
648 644 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
649 645
650 646 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
651 647 controller='changeset', revision='tip',
652 648 conditions={'function': check_repo},
653 649 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
654 650 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
655 651 controller='changeset', revision='tip', action='changeset_children',
656 652 conditions={'function': check_repo},
657 653 requirements=URL_NAME_REQUIREMENTS)
658 654 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
659 655 controller='changeset', revision='tip', action='changeset_parents',
660 656 conditions={'function': check_repo},
661 657 requirements=URL_NAME_REQUIREMENTS)
662 658
663 659 # repo edit options
664 660 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
665 661 controller='admin/repos', action='edit_fields',
666 662 conditions={'method': ['GET'], 'function': check_repo},
667 663 requirements=URL_NAME_REQUIREMENTS)
668 664 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
669 665 controller='admin/repos', action='create_repo_field',
670 666 conditions={'method': ['PUT'], 'function': check_repo},
671 667 requirements=URL_NAME_REQUIREMENTS)
672 668 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
673 669 controller='admin/repos', action='delete_repo_field',
674 670 conditions={'method': ['DELETE'], 'function': check_repo},
675 671 requirements=URL_NAME_REQUIREMENTS)
676 672
677 673 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
678 674 controller='admin/repos', action='toggle_locking',
679 675 conditions={'method': ['GET'], 'function': check_repo},
680 676 requirements=URL_NAME_REQUIREMENTS)
681 677
682 678 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
683 679 controller='admin/repos', action='edit_remote_form',
684 680 conditions={'method': ['GET'], 'function': check_repo},
685 681 requirements=URL_NAME_REQUIREMENTS)
686 682 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
687 683 controller='admin/repos', action='edit_remote',
688 684 conditions={'method': ['PUT'], 'function': check_repo},
689 685 requirements=URL_NAME_REQUIREMENTS)
690 686
691 687 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
692 688 controller='admin/repos', action='edit_statistics_form',
693 689 conditions={'method': ['GET'], 'function': check_repo},
694 690 requirements=URL_NAME_REQUIREMENTS)
695 691 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
696 692 controller='admin/repos', action='edit_statistics',
697 693 conditions={'method': ['PUT'], 'function': check_repo},
698 694 requirements=URL_NAME_REQUIREMENTS)
699 695 rmap.connect('repo_settings_issuetracker',
700 696 '/{repo_name}/settings/issue-tracker',
701 697 controller='admin/repos', action='repo_issuetracker',
702 698 conditions={'method': ['GET'], 'function': check_repo},
703 699 requirements=URL_NAME_REQUIREMENTS)
704 700 rmap.connect('repo_issuetracker_test',
705 701 '/{repo_name}/settings/issue-tracker/test',
706 702 controller='admin/repos', action='repo_issuetracker_test',
707 703 conditions={'method': ['POST'], 'function': check_repo},
708 704 requirements=URL_NAME_REQUIREMENTS)
709 705 rmap.connect('repo_issuetracker_delete',
710 706 '/{repo_name}/settings/issue-tracker/delete',
711 707 controller='admin/repos', action='repo_issuetracker_delete',
712 708 conditions={'method': ['DELETE'], 'function': check_repo},
713 709 requirements=URL_NAME_REQUIREMENTS)
714 710 rmap.connect('repo_issuetracker_save',
715 711 '/{repo_name}/settings/issue-tracker/save',
716 712 controller='admin/repos', action='repo_issuetracker_save',
717 713 conditions={'method': ['POST'], 'function': check_repo},
718 714 requirements=URL_NAME_REQUIREMENTS)
719 715 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
720 716 controller='admin/repos', action='repo_settings_vcs_update',
721 717 conditions={'method': ['POST'], 'function': check_repo},
722 718 requirements=URL_NAME_REQUIREMENTS)
723 719 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
724 720 controller='admin/repos', action='repo_settings_vcs',
725 721 conditions={'method': ['GET'], 'function': check_repo},
726 722 requirements=URL_NAME_REQUIREMENTS)
727 723 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
728 724 controller='admin/repos', action='repo_delete_svn_pattern',
729 725 conditions={'method': ['DELETE'], 'function': check_repo},
730 726 requirements=URL_NAME_REQUIREMENTS)
731 727 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
732 728 controller='admin/repos', action='repo_settings_pullrequest',
733 729 conditions={'method': ['GET', 'POST'], 'function': check_repo},
734 730 requirements=URL_NAME_REQUIREMENTS)
735 731
736 732 # still working url for backward compat.
737 733 rmap.connect('raw_changeset_home_depraced',
738 734 '/{repo_name}/raw-changeset/{revision}',
739 735 controller='changeset', action='changeset_raw',
740 736 revision='tip', conditions={'function': check_repo},
741 737 requirements=URL_NAME_REQUIREMENTS)
742 738
743 739 # new URLs
744 740 rmap.connect('changeset_raw_home',
745 741 '/{repo_name}/changeset-diff/{revision}',
746 742 controller='changeset', action='changeset_raw',
747 743 revision='tip', conditions={'function': check_repo},
748 744 requirements=URL_NAME_REQUIREMENTS)
749 745
750 746 rmap.connect('changeset_patch_home',
751 747 '/{repo_name}/changeset-patch/{revision}',
752 748 controller='changeset', action='changeset_patch',
753 749 revision='tip', conditions={'function': check_repo},
754 750 requirements=URL_NAME_REQUIREMENTS)
755 751
756 752 rmap.connect('changeset_download_home',
757 753 '/{repo_name}/changeset-download/{revision}',
758 754 controller='changeset', action='changeset_download',
759 755 revision='tip', conditions={'function': check_repo},
760 756 requirements=URL_NAME_REQUIREMENTS)
761 757
762 758 rmap.connect('changeset_comment',
763 759 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
764 760 controller='changeset', revision='tip', action='comment',
765 761 conditions={'function': check_repo},
766 762 requirements=URL_NAME_REQUIREMENTS)
767 763
768 764 rmap.connect('changeset_comment_preview',
769 765 '/{repo_name}/changeset/comment/preview', jsroute=True,
770 766 controller='changeset', action='preview_comment',
771 767 conditions={'function': check_repo, 'method': ['POST']},
772 768 requirements=URL_NAME_REQUIREMENTS)
773 769
774 770 rmap.connect('changeset_comment_delete',
775 771 '/{repo_name}/changeset/comment/{comment_id}/delete',
776 772 controller='changeset', action='delete_comment',
777 773 conditions={'function': check_repo, 'method': ['DELETE']},
778 774 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
779 775
780 776 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
781 777 controller='changeset', action='changeset_info',
782 778 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
783 779
784 780 rmap.connect('compare_home',
785 781 '/{repo_name}/compare',
786 782 controller='compare', action='index',
787 783 conditions={'function': check_repo},
788 784 requirements=URL_NAME_REQUIREMENTS)
789 785
790 786 rmap.connect('compare_url',
791 787 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
792 788 controller='compare', action='compare',
793 789 conditions={'function': check_repo},
794 790 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
795 791
796 792 rmap.connect('pullrequest_home',
797 793 '/{repo_name}/pull-request/new', controller='pullrequests',
798 794 action='index', conditions={'function': check_repo,
799 795 'method': ['GET']},
800 796 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
801 797
802 798 rmap.connect('pullrequest',
803 799 '/{repo_name}/pull-request/new', controller='pullrequests',
804 800 action='create', conditions={'function': check_repo,
805 801 'method': ['POST']},
806 802 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
807 803
808 804 rmap.connect('pullrequest_repo_refs',
809 805 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
810 806 controller='pullrequests',
811 807 action='get_repo_refs',
812 808 conditions={'function': check_repo, 'method': ['GET']},
813 809 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
814 810
815 811 rmap.connect('pullrequest_repo_destinations',
816 812 '/{repo_name}/pull-request/repo-destinations',
817 813 controller='pullrequests',
818 814 action='get_repo_destinations',
819 815 conditions={'function': check_repo, 'method': ['GET']},
820 816 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
821 817
822 818 rmap.connect('pullrequest_show',
823 819 '/{repo_name}/pull-request/{pull_request_id}',
824 820 controller='pullrequests',
825 821 action='show', conditions={'function': check_repo,
826 822 'method': ['GET']},
827 823 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
828 824
829 825 rmap.connect('pullrequest_update',
830 826 '/{repo_name}/pull-request/{pull_request_id}',
831 827 controller='pullrequests',
832 828 action='update', conditions={'function': check_repo,
833 829 'method': ['PUT']},
834 830 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
835 831
836 832 rmap.connect('pullrequest_merge',
837 833 '/{repo_name}/pull-request/{pull_request_id}',
838 834 controller='pullrequests',
839 835 action='merge', conditions={'function': check_repo,
840 836 'method': ['POST']},
841 837 requirements=URL_NAME_REQUIREMENTS)
842 838
843 839 rmap.connect('pullrequest_delete',
844 840 '/{repo_name}/pull-request/{pull_request_id}',
845 841 controller='pullrequests',
846 842 action='delete', conditions={'function': check_repo,
847 843 'method': ['DELETE']},
848 844 requirements=URL_NAME_REQUIREMENTS)
849 845
850 846 rmap.connect('pullrequest_show_all',
851 847 '/{repo_name}/pull-request',
852 848 controller='pullrequests',
853 849 action='show_all', conditions={'function': check_repo,
854 850 'method': ['GET']},
855 851 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
856 852
857 853 rmap.connect('pullrequest_comment',
858 854 '/{repo_name}/pull-request-comment/{pull_request_id}',
859 855 controller='pullrequests',
860 856 action='comment', conditions={'function': check_repo,
861 857 'method': ['POST']},
862 858 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
863 859
864 860 rmap.connect('pullrequest_comment_delete',
865 861 '/{repo_name}/pull-request-comment/{comment_id}/delete',
866 862 controller='pullrequests', action='delete_comment',
867 863 conditions={'function': check_repo, 'method': ['DELETE']},
868 864 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
869 865
870 866 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
871 867 controller='summary', conditions={'function': check_repo},
872 868 requirements=URL_NAME_REQUIREMENTS)
873 869
874 870 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
875 871 controller='changelog', conditions={'function': check_repo},
876 872 requirements=URL_NAME_REQUIREMENTS)
877 873
878 874 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
879 875 controller='changelog', action='changelog_summary',
880 876 conditions={'function': check_repo},
881 877 requirements=URL_NAME_REQUIREMENTS)
882 878
883 879 rmap.connect('changelog_file_home',
884 880 '/{repo_name}/changelog/{revision}/{f_path}',
885 881 controller='changelog', f_path=None,
886 882 conditions={'function': check_repo},
887 883 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888 884
889 885 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
890 886 controller='changelog', action='changelog_elements',
891 887 conditions={'function': check_repo},
892 888 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 889
894 890 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
895 891 controller='files', revision='tip', f_path='',
896 892 conditions={'function': check_repo},
897 893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
898 894
899 895 rmap.connect('files_home_simple_catchrev',
900 896 '/{repo_name}/files/{revision}',
901 897 controller='files', revision='tip', f_path='',
902 898 conditions={'function': check_repo},
903 899 requirements=URL_NAME_REQUIREMENTS)
904 900
905 901 rmap.connect('files_home_simple_catchall',
906 902 '/{repo_name}/files',
907 903 controller='files', revision='tip', f_path='',
908 904 conditions={'function': check_repo},
909 905 requirements=URL_NAME_REQUIREMENTS)
910 906
911 907 rmap.connect('files_history_home',
912 908 '/{repo_name}/history/{revision}/{f_path}',
913 909 controller='files', action='history', revision='tip', f_path='',
914 910 conditions={'function': check_repo},
915 911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
916 912
917 913 rmap.connect('files_authors_home',
918 914 '/{repo_name}/authors/{revision}/{f_path}',
919 915 controller='files', action='authors', revision='tip', f_path='',
920 916 conditions={'function': check_repo},
921 917 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
922 918
923 919 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
924 920 controller='files', action='diff', f_path='',
925 921 conditions={'function': check_repo},
926 922 requirements=URL_NAME_REQUIREMENTS)
927 923
928 924 rmap.connect('files_diff_2way_home',
929 925 '/{repo_name}/diff-2way/{f_path}',
930 926 controller='files', action='diff_2way', f_path='',
931 927 conditions={'function': check_repo},
932 928 requirements=URL_NAME_REQUIREMENTS)
933 929
934 930 rmap.connect('files_rawfile_home',
935 931 '/{repo_name}/rawfile/{revision}/{f_path}',
936 932 controller='files', action='rawfile', revision='tip',
937 933 f_path='', conditions={'function': check_repo},
938 934 requirements=URL_NAME_REQUIREMENTS)
939 935
940 936 rmap.connect('files_raw_home',
941 937 '/{repo_name}/raw/{revision}/{f_path}',
942 938 controller='files', action='raw', revision='tip', f_path='',
943 939 conditions={'function': check_repo},
944 940 requirements=URL_NAME_REQUIREMENTS)
945 941
946 942 rmap.connect('files_render_home',
947 943 '/{repo_name}/render/{revision}/{f_path}',
948 944 controller='files', action='index', revision='tip', f_path='',
949 945 rendered=True, conditions={'function': check_repo},
950 946 requirements=URL_NAME_REQUIREMENTS)
951 947
952 948 rmap.connect('files_annotate_home',
953 949 '/{repo_name}/annotate/{revision}/{f_path}',
954 950 controller='files', action='index', revision='tip',
955 951 f_path='', annotate=True, conditions={'function': check_repo},
956 952 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
957 953
958 954 rmap.connect('files_annotate_previous',
959 955 '/{repo_name}/annotate-previous/{revision}/{f_path}',
960 956 controller='files', action='annotate_previous', revision='tip',
961 957 f_path='', annotate=True, conditions={'function': check_repo},
962 958 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
963 959
964 960 rmap.connect('files_edit',
965 961 '/{repo_name}/edit/{revision}/{f_path}',
966 962 controller='files', action='edit', revision='tip',
967 963 f_path='',
968 964 conditions={'function': check_repo, 'method': ['POST']},
969 965 requirements=URL_NAME_REQUIREMENTS)
970 966
971 967 rmap.connect('files_edit_home',
972 968 '/{repo_name}/edit/{revision}/{f_path}',
973 969 controller='files', action='edit_home', revision='tip',
974 970 f_path='', conditions={'function': check_repo},
975 971 requirements=URL_NAME_REQUIREMENTS)
976 972
977 973 rmap.connect('files_add',
978 974 '/{repo_name}/add/{revision}/{f_path}',
979 975 controller='files', action='add', revision='tip',
980 976 f_path='',
981 977 conditions={'function': check_repo, 'method': ['POST']},
982 978 requirements=URL_NAME_REQUIREMENTS)
983 979
984 980 rmap.connect('files_add_home',
985 981 '/{repo_name}/add/{revision}/{f_path}',
986 982 controller='files', action='add_home', revision='tip',
987 983 f_path='', conditions={'function': check_repo},
988 984 requirements=URL_NAME_REQUIREMENTS)
989 985
990 986 rmap.connect('files_delete',
991 987 '/{repo_name}/delete/{revision}/{f_path}',
992 988 controller='files', action='delete', revision='tip',
993 989 f_path='',
994 990 conditions={'function': check_repo, 'method': ['POST']},
995 991 requirements=URL_NAME_REQUIREMENTS)
996 992
997 993 rmap.connect('files_delete_home',
998 994 '/{repo_name}/delete/{revision}/{f_path}',
999 995 controller='files', action='delete_home', revision='tip',
1000 996 f_path='', conditions={'function': check_repo},
1001 997 requirements=URL_NAME_REQUIREMENTS)
1002 998
1003 999 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1004 1000 controller='files', action='archivefile',
1005 1001 conditions={'function': check_repo},
1006 1002 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1007 1003
1008 1004 rmap.connect('files_nodelist_home',
1009 1005 '/{repo_name}/nodelist/{revision}/{f_path}',
1010 1006 controller='files', action='nodelist',
1011 1007 conditions={'function': check_repo},
1012 1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1013 1009
1014 1010 rmap.connect('files_nodetree_full',
1015 1011 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1016 1012 controller='files', action='nodetree_full',
1017 1013 conditions={'function': check_repo},
1018 1014 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1019 1015
1020 1016 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1021 1017 controller='forks', action='fork_create',
1022 1018 conditions={'function': check_repo, 'method': ['POST']},
1023 1019 requirements=URL_NAME_REQUIREMENTS)
1024 1020
1025 1021 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1026 1022 controller='forks', action='fork',
1027 1023 conditions={'function': check_repo},
1028 1024 requirements=URL_NAME_REQUIREMENTS)
1029 1025
1030 1026 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1031 1027 controller='forks', action='forks',
1032 1028 conditions={'function': check_repo},
1033 1029 requirements=URL_NAME_REQUIREMENTS)
1034 1030
1035 1031 # must be here for proper group/repo catching pattern
1036 1032 _connect_with_slash(
1037 1033 rmap, 'repo_group_home', '/{group_name}',
1038 1034 controller='home', action='index_repo_group',
1039 1035 conditions={'function': check_group},
1040 1036 requirements=URL_NAME_REQUIREMENTS)
1041 1037
1042 1038 # catch all, at the end
1043 1039 _connect_with_slash(
1044 1040 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1045 1041 controller='summary', action='index',
1046 1042 conditions={'function': check_repo},
1047 1043 requirements=URL_NAME_REQUIREMENTS)
1048 1044
1049 1045 return rmap
1050 1046
1051 1047
1052 1048 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1053 1049 """
1054 1050 Connect a route with an optional trailing slash in `path`.
1055 1051 """
1056 1052 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1057 1053 mapper.connect(name, path, *args, **kwargs)
@@ -1,364 +1,330 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 my account controller for RhodeCode admin
24 24 """
25 25
26 26 import logging
27 import datetime
28 27
29 28 import formencode
30 29 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
32 30 from pyramid.httpexceptions import HTTPFound
33 31
34 32 from pylons import request, tmpl_context as c, url
35 33 from pylons.controllers.util import redirect
36 34 from pylons.i18n.translation import _
37 35 from sqlalchemy.orm import joinedload
38 36
39 37 from rhodecode.lib import helpers as h
40 38 from rhodecode.lib import auth
41 39 from rhodecode.lib.auth import (
42 40 LoginRequired, NotAnonymous, AuthUser)
43 41 from rhodecode.lib.base import BaseController, render
44 42 from rhodecode.lib.utils import jsonify
45 43 from rhodecode.lib.utils2 import safe_int, str2bool
46 44 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
49 45
50 46 from rhodecode.model.db import (
51 47 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 48 from rhodecode.model.forms import UserForm
53 49 from rhodecode.model.scm import RepoList
54 50 from rhodecode.model.user import UserModel
55 51 from rhodecode.model.repo import RepoModel
56 52 from rhodecode.model.meta import Session
57 53 from rhodecode.model.pull_request import PullRequestModel
58 54 from rhodecode.model.comment import CommentsModel
59 55
60 56 log = logging.getLogger(__name__)
61 57
62 58
63 59 class MyAccountController(BaseController):
64 60 """REST Controller styled on the Atom Publishing Protocol"""
65 61 # To properly map this controller, ensure your config/routing.py
66 62 # file has a resource setup:
67 63 # map.resource('setting', 'settings', controller='admin/settings',
68 64 # path_prefix='/admin', name_prefix='admin_')
69 65
70 66 @LoginRequired()
71 67 @NotAnonymous()
72 68 def __before__(self):
73 69 super(MyAccountController, self).__before__()
74 70
75 71 def __load_data(self):
76 72 c.user = User.get(c.rhodecode_user.user_id)
77 73 if c.user.username == User.DEFAULT_USER:
78 74 h.flash(_("You can't edit this user since it's"
79 75 " crucial for entire application"), category='warning')
80 76 return redirect(h.route_path('users'))
81 77
82 78 c.auth_user = AuthUser(
83 79 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
84 80
85 81 def _load_my_repos_data(self, watched=False):
86 82 if watched:
87 83 admin = False
88 84 follows_repos = Session().query(UserFollowing)\
89 85 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
90 86 .options(joinedload(UserFollowing.follows_repository))\
91 87 .all()
92 88 repo_list = [x.follows_repository for x in follows_repos]
93 89 else:
94 90 admin = True
95 91 repo_list = Repository.get_all_repos(
96 92 user_id=c.rhodecode_user.user_id)
97 93 repo_list = RepoList(repo_list, perm_set=[
98 94 'repository.read', 'repository.write', 'repository.admin'])
99 95
100 96 repos_data = RepoModel().get_repos_as_dict(
101 97 repo_list=repo_list, admin=admin)
102 98 # json used to render the grid
103 99 return json.dumps(repos_data)
104 100
105 101 @auth.CSRFRequired()
106 102 def my_account_update(self):
107 103 """
108 104 POST /_admin/my_account Updates info of my account
109 105 """
110 106 # url('my_account')
111 107 c.active = 'profile_edit'
112 108 self.__load_data()
113 109 c.perm_user = c.auth_user
114 110 c.extern_type = c.user.extern_type
115 111 c.extern_name = c.user.extern_name
116 112
117 113 defaults = c.user.get_dict()
118 114 update = False
119 115 _form = UserForm(edit=True,
120 116 old_data={'user_id': c.rhodecode_user.user_id,
121 117 'email': c.rhodecode_user.email})()
122 118 form_result = {}
123 119 try:
124 120 post_data = dict(request.POST)
125 121 post_data['new_password'] = ''
126 122 post_data['password_confirmation'] = ''
127 123 form_result = _form.to_python(post_data)
128 124 # skip updating those attrs for my account
129 125 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
130 126 'new_password', 'password_confirmation']
131 127 # TODO: plugin should define if username can be updated
132 128 if c.extern_type != "rhodecode":
133 129 # forbid updating username for external accounts
134 130 skip_attrs.append('username')
135 131
136 132 UserModel().update_user(
137 133 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
138 134 h.flash(_('Your account was updated successfully'),
139 135 category='success')
140 136 Session().commit()
141 137 update = True
142 138
143 139 except formencode.Invalid as errors:
144 140 return htmlfill.render(
145 141 render('admin/my_account/my_account.mako'),
146 142 defaults=errors.value,
147 143 errors=errors.error_dict or {},
148 144 prefix_error=False,
149 145 encoding="UTF-8",
150 146 force_defaults=False)
151 147 except Exception:
152 148 log.exception("Exception updating user")
153 149 h.flash(_('Error occurred during update of user %s')
154 150 % form_result.get('username'), category='error')
155 151
156 152 if update:
157 153 raise HTTPFound(h.route_path('my_account_profile'))
158 154
159 155 return htmlfill.render(
160 156 render('admin/my_account/my_account.mako'),
161 157 defaults=defaults,
162 158 encoding="UTF-8",
163 159 force_defaults=False
164 160 )
165 161
166 162 def my_account_edit(self):
167 163 """
168 164 GET /_admin/my_account/edit Displays edit form of my account
169 165 """
170 166 c.active = 'profile_edit'
171 167 self.__load_data()
172 168 c.perm_user = c.auth_user
173 169 c.extern_type = c.user.extern_type
174 170 c.extern_name = c.user.extern_name
175 171
176 172 defaults = c.user.get_dict()
177 173 return htmlfill.render(
178 174 render('admin/my_account/my_account.mako'),
179 175 defaults=defaults,
180 176 encoding="UTF-8",
181 177 force_defaults=False
182 178 )
183 179
184 180 def my_account_repos(self):
185 181 c.active = 'repos'
186 182 self.__load_data()
187 183
188 184 # json used to render the grid
189 185 c.data = self._load_my_repos_data()
190 186 return render('admin/my_account/my_account.mako')
191 187
192 188 def my_account_watched(self):
193 189 c.active = 'watched'
194 190 self.__load_data()
195 191
196 192 # json used to render the grid
197 193 c.data = self._load_my_repos_data(watched=True)
198 194 return render('admin/my_account/my_account.mako')
199 195
200 196 def my_account_perms(self):
201 197 c.active = 'perms'
202 198 self.__load_data()
203 199 c.perm_user = c.auth_user
204 200
205 201 return render('admin/my_account/my_account.mako')
206 202
207 203 def my_account_emails(self):
208 204 c.active = 'emails'
209 205 self.__load_data()
210 206
211 207 c.user_email_map = UserEmailMap.query()\
212 208 .filter(UserEmailMap.user == c.user).all()
213 209 return render('admin/my_account/my_account.mako')
214 210
215 211 @auth.CSRFRequired()
216 212 def my_account_emails_add(self):
217 213 email = request.POST.get('new_email')
218 214
219 215 try:
220 216 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
221 217 Session().commit()
222 218 h.flash(_("Added new email address `%s` for user account") % email,
223 219 category='success')
224 220 except formencode.Invalid as error:
225 221 msg = error.error_dict['email']
226 222 h.flash(msg, category='error')
227 223 except Exception:
228 224 log.exception("Exception in my_account_emails")
229 225 h.flash(_('An error occurred during email saving'),
230 226 category='error')
231 227 return redirect(url('my_account_emails'))
232 228
233 229 @auth.CSRFRequired()
234 230 def my_account_emails_delete(self):
235 231 email_id = request.POST.get('del_email_id')
236 232 user_model = UserModel()
237 233 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
238 234 Session().commit()
239 235 h.flash(_("Removed email address from user account"),
240 236 category='success')
241 237 return redirect(url('my_account_emails'))
242 238
243 239 def _extract_ordering(self, request):
244 240 column_index = safe_int(request.GET.get('order[0][column]'))
245 241 order_dir = request.GET.get('order[0][dir]', 'desc')
246 242 order_by = request.GET.get(
247 243 'columns[%s][data][sort]' % column_index, 'name_raw')
248 244 return order_by, order_dir
249 245
250 246 def _get_pull_requests_list(self, statuses):
251 247 start = safe_int(request.GET.get('start'), 0)
252 248 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
253 249 order_by, order_dir = self._extract_ordering(request)
254 250
255 251 pull_requests = PullRequestModel().get_im_participating_in(
256 252 user_id=c.rhodecode_user.user_id,
257 253 statuses=statuses,
258 254 offset=start, length=length, order_by=order_by,
259 255 order_dir=order_dir)
260 256
261 257 pull_requests_total_count = PullRequestModel().count_im_participating_in(
262 258 user_id=c.rhodecode_user.user_id, statuses=statuses)
263 259
264 260 from rhodecode.lib.utils import PartialRenderer
265 261 _render = PartialRenderer('data_table/_dt_elements.mako')
266 262 data = []
267 263 for pr in pull_requests:
268 264 repo_id = pr.target_repo_id
269 265 comments = CommentsModel().get_all_comments(
270 266 repo_id, pull_request=pr)
271 267 owned = pr.user_id == c.rhodecode_user.user_id
272 268 status = pr.calculated_review_status()
273 269
274 270 data.append({
275 271 'target_repo': _render('pullrequest_target_repo',
276 272 pr.target_repo.repo_name),
277 273 'name': _render('pullrequest_name',
278 274 pr.pull_request_id, pr.target_repo.repo_name,
279 275 short=True),
280 276 'name_raw': pr.pull_request_id,
281 277 'status': _render('pullrequest_status', status),
282 278 'title': _render(
283 279 'pullrequest_title', pr.title, pr.description),
284 280 'description': h.escape(pr.description),
285 281 'updated_on': _render('pullrequest_updated_on',
286 282 h.datetime_to_time(pr.updated_on)),
287 283 'updated_on_raw': h.datetime_to_time(pr.updated_on),
288 284 'created_on': _render('pullrequest_updated_on',
289 285 h.datetime_to_time(pr.created_on)),
290 286 'created_on_raw': h.datetime_to_time(pr.created_on),
291 287 'author': _render('pullrequest_author',
292 288 pr.author.full_contact, ),
293 289 'author_raw': pr.author.full_name,
294 290 'comments': _render('pullrequest_comments', len(comments)),
295 291 'comments_raw': len(comments),
296 292 'closed': pr.is_closed(),
297 293 'owned': owned
298 294 })
299 295 # json used to render the grid
300 296 data = ({
301 297 'data': data,
302 298 'recordsTotal': pull_requests_total_count,
303 299 'recordsFiltered': pull_requests_total_count,
304 300 })
305 301 return data
306 302
307 303 def my_account_pullrequests(self):
308 304 c.active = 'pullrequests'
309 305 self.__load_data()
310 306 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
311 307
312 308 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
313 309 if c.show_closed:
314 310 statuses += [PullRequest.STATUS_CLOSED]
315 311 data = self._get_pull_requests_list(statuses)
316 312 if not request.is_xhr:
317 313 c.data_participate = json.dumps(data['data'])
318 314 c.records_total_participate = data['recordsTotal']
319 315 return render('admin/my_account/my_account.mako')
320 316 else:
321 317 return json.dumps(data)
322 318
323 319 def my_notifications(self):
324 320 c.active = 'notifications'
325 321 return render('admin/my_account/my_account.mako')
326 322
327 323 @auth.CSRFRequired()
328 324 @jsonify
329 325 def my_notifications_toggle_visibility(self):
330 326 user = c.rhodecode_user.get_instance()
331 327 new_status = not user.user_data.get('notification_status', True)
332 328 user.update_userdata(notification_status=new_status)
333 329 Session().commit()
334 330 return user.user_data['notification_status']
335
336 @auth.CSRFRequired()
337 @jsonify
338 def my_account_notifications_test_channelstream(self):
339 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
340 c.rhodecode_user.username, datetime.datetime.now())
341 payload = {
342 'type': 'message',
343 'timestamp': datetime.datetime.utcnow(),
344 'user': 'system',
345 #'channel': 'broadcast',
346 'pm_users': [c.rhodecode_user.username],
347 'message': {
348 'message': message,
349 'level': 'info',
350 'topic': '/notifications'
351 }
352 }
353
354 registry = get_current_registry()
355 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
356 channelstream_config = rhodecode_plugins.get('channelstream', {})
357
358 try:
359 channelstream_request(channelstream_config, [payload], '/message')
360 except ChannelstreamException as e:
361 log.exception('Failed to send channelstream data')
362 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
363 return {"response": 'Channelstream data sent. '
364 'You should see a new live message now.'}
@@ -1,127 +1,128 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 18 pyroutes.register('gists', '/_admin/gists', []);
19 19 pyroutes.register('new_gist', '/_admin/gists/new', []);
20 20 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
21 21 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
22 22 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
23 23 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
24 24 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
25 25 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
26 26 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
27 27 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
28 28 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
29 29 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
30 30 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
31 31 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
32 32 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
33 33 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
34 34 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
35 35 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
36 36 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
37 37 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
38 38 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 39 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
40 40 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
41 41 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
42 42 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
43 43 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 44 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 46 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 47 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 48 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
49 49 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 50 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
51 51 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
52 52 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
53 53 pyroutes.register('favicon', '/favicon.ico', []);
54 54 pyroutes.register('robots', '/robots.txt', []);
55 55 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
56 56 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
57 57 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
58 58 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
59 59 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
60 60 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
61 61 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
62 62 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
63 63 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
64 64 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
65 65 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
66 66 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
67 67 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
68 68 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
69 69 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
70 70 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
71 71 pyroutes.register('ops_ping', '_admin/ops/ping', []);
72 72 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
73 73 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
74 74 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
75 75 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
76 76 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
77 77 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
78 78 pyroutes.register('users', '_admin/users', []);
79 79 pyroutes.register('users_data', '_admin/users_data', []);
80 80 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
81 81 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
82 82 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
83 83 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
84 84 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
85 85 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
86 86 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
87 87 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
88 88 pyroutes.register('channelstream_proxy', '/_channelstream', []);
89 89 pyroutes.register('login', '/_admin/login', []);
90 90 pyroutes.register('logout', '/_admin/logout', []);
91 91 pyroutes.register('register', '/_admin/register', []);
92 92 pyroutes.register('reset_password', '/_admin/password_reset', []);
93 93 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
94 94 pyroutes.register('home', '/', []);
95 95 pyroutes.register('user_autocomplete_data', '/_users', []);
96 96 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
97 97 pyroutes.register('repo_list_data', '/_repos', []);
98 98 pyroutes.register('goto_switcher_data', '/_goto_data', []);
99 99 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
100 100 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
101 101 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
102 102 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
103 103 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
104 104 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
105 105 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
106 106 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
107 107 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
108 108 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
109 109 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
110 110 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
111 111 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
112 112 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
113 113 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
114 114 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
115 115 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
116 116 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
117 117 pyroutes.register('search', '/_admin/search', []);
118 118 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
119 119 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
120 120 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
121 121 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
122 122 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
123 123 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
124 124 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
125 125 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
126 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
126 127 pyroutes.register('apiv2', '/_admin/api', []);
127 128 }
@@ -1,130 +1,130 b''
1 1 <template is="dom-bind" id="notificationsPage">
2 2 <iron-ajax id="toggleNotifications"
3 3 method="post"
4 4 url="${url('my_account_notifications_toggle_visibility')}"
5 5 content-type="application/json"
6 6 loading="{{changeNotificationsLoading}}"
7 7 on-response="handleNotifications"
8 8 handle-as="json">
9 9 </iron-ajax>
10 10
11 11 <iron-ajax id="sendTestNotification"
12 12 method="post"
13 url="${url('my_account_notifications_test_channelstream')}"
13 url="${h.route_path('my_account_notifications_test_channelstream')}"
14 14 content-type="application/json"
15 15 on-response="handleTestNotification"
16 16 handle-as="json">
17 17 </iron-ajax>
18 18
19 19 <div class="panel panel-default">
20 20 <div class="panel-heading">
21 21 <h3 class="panel-title">${_('Your Live Notification Settings')}</h3>
22 22 </div>
23 23 <div class="panel-body">
24 24
25 25 <p><strong>IMPORTANT:</strong> This feature requires enabled channelstream websocket server to function correctly.</p>
26 26
27 27 <p class="hidden">Status of browser notifications permission: <strong id="browser-notification-status"></strong></p>
28 28
29 29 <div class="form">
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="new_email">${_('Notifications Status')}:</label>
34 34 </div>
35 35 <div class="checkboxes">
36 36 <rhodecode-toggle id="live-notifications" active="[[changeNotificationsLoading]]" on-change="toggleNotifications" ${'checked' if c.rhodecode_user.get_instance().user_data.get('notification_status') else ''}></rhodecode-toggle>
37 37 </div>
38 38 </div>
39 39 </div>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="panel panel-default">
45 45 <div class="panel-heading">
46 46 <h3 class="panel-title">${_('Test Notifications')}</h3>
47 47 </div>
48 48 <div class="panel-body">
49 49
50 50
51 51 <div style="padding: 0px 0px 20px 0px">
52 52 <button class="btn" id="test-notification" on-tap="testNotifications">Test flash message</button>
53 53 <button class="btn" id="test-notification-live" on-tap="testNotificationsLive">Test live notification</button>
54 54 </div>
55 55 <h4 id="test-response"></h4>
56 56
57 57 </div>
58 58
59 59
60 60
61 61 </div>
62 62
63 63 <script type="text/javascript">
64 64 /** because im not creating a custom element for this page
65 65 * we need to push the function onto the dom-template
66 66 * ideally we turn this into notification-settings elements
67 67 * then it will be cleaner
68 68 */
69 69 var ctrlr = $('#notificationsPage')[0];
70 70 ctrlr.toggleNotifications = function(event){
71 71 var ajax = $('#toggleNotifications')[0];
72 72 ajax.headers = {"X-CSRF-Token": CSRF_TOKEN};
73 73 ajax.body = {notification_status:event.target.active};
74 74 ajax.generateRequest();
75 75 };
76 76 ctrlr.handleNotifications = function(event){
77 77 $('#live-notifications')[0].checked = event.detail.response;
78 78 };
79 79
80 80 ctrlr.testNotifications = function(event){
81 81 var levels = ['info', 'error', 'warning', 'success'];
82 82 var level = levels[Math.floor(Math.random()*levels.length)];
83 83 function getRandomArbitrary(min, max) {
84 84 return parseInt(Math.random() * (max - min) + min);
85 85 }
86 86 function shuffle(a) {
87 87 var j, x, i;
88 88 for (i = a.length; i; i--) {
89 89 j = Math.floor(Math.random() * i);
90 90 x = a[i - 1];
91 91 a[i - 1] = a[j];
92 92 a[j] = x;
93 93 }
94 94 }
95 95 var wordDb = [
96 96 "Leela,", "Bender,", "we are", "going", "grave", "robbing.",
97 97 "Oh,", "I", "think", "we", "should", "just", "stay", "friends.",
98 98 "got", "to", "find", "a", "way", "to", "escape", "the", "horrible",
99 99 "ravages", "of", "youth.", "Suddenly,", "going", "to",
100 100 "the", "bathroom", "like", "clockwork,", "every", "three",
101 101 "hours.", "And", "those", "jerks", "at", "Social", "Security",
102 102 "stopped", "sending", "me", "checks.", "Now", "have", "to", "pay"
103 103 ];
104 104 shuffle(wordDb);
105 105 wordDb = wordDb.slice(0, getRandomArbitrary(3, wordDb.length));
106 106 var randomMessage = wordDb.join(" ");
107 107 var payload = {
108 108 message: {
109 109 message: randomMessage + " " + new Date(),
110 110 level: level,
111 111 force: true
112 112 }
113 113 };
114 114 $.Topic('/notifications').publish(payload);
115 115 };
116 116 ctrlr.testNotificationsLive = function(event){
117 117 var ajax = $('#sendTestNotification')[0];
118 118 ajax.headers = {"X-CSRF-Token": CSRF_TOKEN};
119 119 ajax.body = {test_msg: 'Hello !'};
120 120 ajax.generateRequest();
121 121 };
122 122 ctrlr.handleTestNotification = function(event){
123 123 var reply = event.detail.response.response;
124 124 reply = reply || 'no reply form server';
125 125 $('#test-response').html(reply);
126 126 };
127 127
128 128 </script>
129 129
130 130 </template>
General Comments 0
You need to be logged in to leave comments. Login now