##// END OF EJS Templates
my-account: moved few my account views into pyramid.
marcink -
r1819:956c5cda default
parent child Browse files
Show More
@@ -0,0 +1,76 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User, UserEmailMap, Repository, UserFollowing
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
27 assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29
30 fixture = Fixture()
31
32
33 def route_path(name, **kwargs):
34 return {
35 'my_account_repos':
36 ADMIN_PREFIX + '/my_account/repos',
37 'my_account_watched':
38 ADMIN_PREFIX + '/my_account/watched',
39 'my_account_perms':
40 ADMIN_PREFIX + '/my_account/perms',
41 'my_account_notifications':
42 ADMIN_PREFIX + '/my_account/notifications',
43 }[name].format(**kwargs)
44
45
46 class TestMyAccountSimpleViews(TestController):
47
48 def test_my_account_my_repos(self, autologin_user):
49 response = self.app.get(route_path('my_account_repos'))
50 repos = Repository.query().filter(
51 Repository.user == User.get_by_username(
52 TEST_USER_ADMIN_LOGIN)).all()
53 for repo in repos:
54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
55
56 def test_my_account_my_watched(self, autologin_user):
57 response = self.app.get(route_path('my_account_watched'))
58
59 repos = UserFollowing.query().filter(
60 UserFollowing.user == User.get_by_username(
61 TEST_USER_ADMIN_LOGIN)).all()
62 for repo in repos:
63 response.mustcontain(
64 '"name_raw": "%s"' % repo.follows_repository.repo_name)
65
66 def test_my_account_perms(self, autologin_user):
67 response = self.app.get(route_path('my_account_perms'))
68 assert_response = response.assert_response()
69 assert assert_response.get_elements('.perm_tag.none')
70 assert assert_response.get_elements('.perm_tag.read')
71 assert assert_response.get_elements('.perm_tag.write')
72 assert assert_response.get_elements('.perm_tag.admin')
73
74 def test_my_account_notifications(self, autologin_user):
75 response = self.app.get(route_path('my_account_notifications'))
76 response.mustcontain('Test flash message')
@@ -1,65 +1,85 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 44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
45 45 config.add_route(
46 46 name='my_account_auth_tokens_delete',
47 47 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
48 48
49 49 config.add_route(
50 50 name='my_account_emails',
51 51 pattern=ADMIN_PREFIX + '/my_account/emails')
52 52 config.add_route(
53 53 name='my_account_emails_add',
54 54 pattern=ADMIN_PREFIX + '/my_account/emails/new')
55 55 config.add_route(
56 56 name='my_account_emails_delete',
57 57 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
58 58
59 config.add_route(
60 name='my_account_repos',
61 pattern=ADMIN_PREFIX + '/my_account/repos')
62
63 config.add_route(
64 name='my_account_watched',
65 pattern=ADMIN_PREFIX + '/my_account/watched')
66
67 config.add_route(
68 name='my_account_perms',
69 pattern=ADMIN_PREFIX + '/my_account/perms')
70
71 config.add_route(
72 name='my_account_notifications',
73 pattern=ADMIN_PREFIX + '/my_account/notifications')
74
75 config.add_route(
76 name='my_account_notifications_toggle_visibility',
77 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
78
59 79 # channelstream test
60 80 config.add_route(
61 81 name='my_account_notifications_test_channelstream',
62 82 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
63 83
64 84 # Scan module for configuration decorators.
65 85 config.scan()
@@ -1,292 +1,378 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 22 import datetime
23 23
24 24 import formencode
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.view import view_config
27 27
28 28 from rhodecode.apps._base import BaseAppView
29 29 from rhodecode import forms
30 30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.ext_json import json
31 32 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
32 33 from rhodecode.lib.channelstream import channelstream_request, \
33 34 ChannelstreamException
34 35 from rhodecode.lib.utils2 import safe_int, md5
35 36 from rhodecode.model.auth_token import AuthTokenModel
36 from rhodecode.model.db import UserEmailMap
37 from rhodecode.model.db import (
38 Repository, PullRequest, UserEmailMap, User, UserFollowing, joinedload)
37 39 from rhodecode.model.meta import Session
40 from rhodecode.model.scm import RepoList
38 41 from rhodecode.model.user import UserModel
42 from rhodecode.model.repo import RepoModel
39 43 from rhodecode.model.validation_schema.schemas import user_schema
40 44
41 45 log = logging.getLogger(__name__)
42 46
43 47
44 48 class MyAccountView(BaseAppView):
45 49 ALLOW_SCOPED_TOKENS = False
46 50 """
47 51 This view has alternative version inside EE, if modified please take a look
48 52 in there as well.
49 53 """
50 54
51 55 def load_default_context(self):
52 56 c = self._get_local_tmpl_context()
53 57 c.user = c.auth_user.get_instance()
54 58 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
55 59 self._register_global_c(c)
56 60 return c
57 61
58 62 @LoginRequired()
59 63 @NotAnonymous()
60 64 @view_config(
61 65 route_name='my_account_profile', request_method='GET',
62 66 renderer='rhodecode:templates/admin/my_account/my_account.mako')
63 67 def my_account_profile(self):
64 68 c = self.load_default_context()
65 69 c.active = 'profile'
66 70 return self._get_template_context(c)
67 71
68 72 @LoginRequired()
69 73 @NotAnonymous()
70 74 @view_config(
71 75 route_name='my_account_password', request_method='GET',
72 76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
73 77 def my_account_password(self):
74 78 c = self.load_default_context()
75 79 c.active = 'password'
76 80 c.extern_type = c.user.extern_type
77 81
78 82 schema = user_schema.ChangePasswordSchema().bind(
79 83 username=c.user.username)
80 84
81 85 form = forms.Form(
82 86 schema, buttons=(forms.buttons.save, forms.buttons.reset))
83 87
84 88 c.form = form
85 89 return self._get_template_context(c)
86 90
87 91 @LoginRequired()
88 92 @NotAnonymous()
89 93 @CSRFRequired()
90 94 @view_config(
91 95 route_name='my_account_password', request_method='POST',
92 96 renderer='rhodecode:templates/admin/my_account/my_account.mako')
93 97 def my_account_password_update(self):
94 98 _ = self.request.translate
95 99 c = self.load_default_context()
96 100 c.active = 'password'
97 101 c.extern_type = c.user.extern_type
98 102
99 103 schema = user_schema.ChangePasswordSchema().bind(
100 104 username=c.user.username)
101 105
102 106 form = forms.Form(
103 107 schema, buttons=(forms.buttons.save, forms.buttons.reset))
104 108
105 109 if c.extern_type != 'rhodecode':
106 110 raise HTTPFound(self.request.route_path('my_account_password'))
107 111
108 112 controls = self.request.POST.items()
109 113 try:
110 114 valid_data = form.validate(controls)
111 115 UserModel().update_user(c.user.user_id, **valid_data)
112 116 c.user.update_userdata(force_password_change=False)
113 117 Session().commit()
114 118 except forms.ValidationFailure as e:
115 119 c.form = e
116 120 return self._get_template_context(c)
117 121
118 122 except Exception:
119 123 log.exception("Exception updating password")
120 124 h.flash(_('Error occurred during update of user password'),
121 125 category='error')
122 126 else:
123 127 instance = c.auth_user.get_instance()
124 128 self.session.setdefault('rhodecode_user', {}).update(
125 129 {'password': md5(instance.password)})
126 130 self.session.save()
127 131 h.flash(_("Successfully updated password"), category='success')
128 132
129 133 raise HTTPFound(self.request.route_path('my_account_password'))
130 134
131 135 @LoginRequired()
132 136 @NotAnonymous()
133 137 @view_config(
134 138 route_name='my_account_auth_tokens', request_method='GET',
135 139 renderer='rhodecode:templates/admin/my_account/my_account.mako')
136 140 def my_account_auth_tokens(self):
137 141 _ = self.request.translate
138 142
139 143 c = self.load_default_context()
140 144 c.active = 'auth_tokens'
141 145
142 146 c.lifetime_values = [
143 147 (str(-1), _('forever')),
144 148 (str(5), _('5 minutes')),
145 149 (str(60), _('1 hour')),
146 150 (str(60 * 24), _('1 day')),
147 151 (str(60 * 24 * 30), _('1 month')),
148 152 ]
149 153 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
150 154 c.role_values = [
151 155 (x, AuthTokenModel.cls._get_role_name(x))
152 156 for x in AuthTokenModel.cls.ROLES]
153 157 c.role_options = [(c.role_values, _("Role"))]
154 158 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
155 159 c.user.user_id, show_expired=True)
156 160 return self._get_template_context(c)
157 161
158 162 def maybe_attach_token_scope(self, token):
159 163 # implemented in EE edition
160 164 pass
161 165
162 166 @LoginRequired()
163 167 @NotAnonymous()
164 168 @CSRFRequired()
165 169 @view_config(
166 170 route_name='my_account_auth_tokens_add', request_method='POST',)
167 171 def my_account_auth_tokens_add(self):
168 172 _ = self.request.translate
169 173 c = self.load_default_context()
170 174
171 175 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
172 176 description = self.request.POST.get('description')
173 177 role = self.request.POST.get('role')
174 178
175 179 token = AuthTokenModel().create(
176 180 c.user.user_id, description, lifetime, role)
177 181 self.maybe_attach_token_scope(token)
178 182 Session().commit()
179 183
180 184 h.flash(_("Auth token successfully created"), category='success')
181 185 return HTTPFound(h.route_path('my_account_auth_tokens'))
182 186
183 187 @LoginRequired()
184 188 @NotAnonymous()
185 189 @CSRFRequired()
186 190 @view_config(
187 191 route_name='my_account_auth_tokens_delete', request_method='POST')
188 192 def my_account_auth_tokens_delete(self):
189 193 _ = self.request.translate
190 194 c = self.load_default_context()
191 195
192 196 del_auth_token = self.request.POST.get('del_auth_token')
193 197
194 198 if del_auth_token:
195 199 AuthTokenModel().delete(del_auth_token, c.user.user_id)
196 200 Session().commit()
197 201 h.flash(_("Auth token successfully deleted"), category='success')
198 202
199 203 return HTTPFound(h.route_path('my_account_auth_tokens'))
200 204
201 205 @LoginRequired()
202 206 @NotAnonymous()
203 207 @view_config(
204 208 route_name='my_account_emails', request_method='GET',
205 209 renderer='rhodecode:templates/admin/my_account/my_account.mako')
206 210 def my_account_emails(self):
207 211 _ = self.request.translate
208 212
209 213 c = self.load_default_context()
210 214 c.active = 'emails'
211 215
212 216 c.user_email_map = UserEmailMap.query()\
213 217 .filter(UserEmailMap.user == c.user).all()
214 218 return self._get_template_context(c)
215 219
216 220 @LoginRequired()
217 221 @NotAnonymous()
218 222 @CSRFRequired()
219 223 @view_config(
220 224 route_name='my_account_emails_add', request_method='POST')
221 225 def my_account_emails_add(self):
222 226 _ = self.request.translate
223 227 c = self.load_default_context()
224 228
225 229 email = self.request.POST.get('new_email')
226 230
227 231 try:
228 232 UserModel().add_extra_email(c.user.user_id, email)
229 233 Session().commit()
230 234 h.flash(_("Added new email address `%s` for user account") % email,
231 235 category='success')
232 236 except formencode.Invalid as error:
233 237 msg = error.error_dict['email']
234 238 h.flash(msg, category='error')
235 239 except Exception:
236 240 log.exception("Exception in my_account_emails")
237 241 h.flash(_('An error occurred during email saving'),
238 242 category='error')
239 243 return HTTPFound(h.route_path('my_account_emails'))
240 244
241 245 @LoginRequired()
242 246 @NotAnonymous()
243 247 @CSRFRequired()
244 248 @view_config(
245 249 route_name='my_account_emails_delete', request_method='POST')
246 250 def my_account_emails_delete(self):
247 251 _ = self.request.translate
248 252 c = self.load_default_context()
249 253
250 254 del_email_id = self.request.POST.get('del_email_id')
251 255 if del_email_id:
252 256
253 257 UserModel().delete_extra_email(
254 258 c.user.user_id, del_email_id)
255 259 Session().commit()
256 260 h.flash(_("Email successfully deleted"),
257 261 category='success')
258 262 return HTTPFound(h.route_path('my_account_emails'))
259 263
260 264 @LoginRequired()
261 265 @NotAnonymous()
262 266 @CSRFRequired()
263 267 @view_config(
264 268 route_name='my_account_notifications_test_channelstream',
265 269 request_method='POST', renderer='json_ext')
266 270 def my_account_notifications_test_channelstream(self):
267 271 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
268 272 self._rhodecode_user.username, datetime.datetime.now())
269 273 payload = {
270 274 # 'channel': 'broadcast',
271 275 'type': 'message',
272 276 'timestamp': datetime.datetime.utcnow(),
273 277 'user': 'system',
274 278 'pm_users': [self._rhodecode_user.username],
275 279 'message': {
276 280 'message': message,
277 281 'level': 'info',
278 282 'topic': '/notifications'
279 283 }
280 284 }
281 285
282 286 registry = self.request.registry
283 287 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
284 288 channelstream_config = rhodecode_plugins.get('channelstream', {})
285 289
286 290 try:
287 291 channelstream_request(channelstream_config, [payload], '/message')
288 292 except ChannelstreamException as e:
289 293 log.exception('Failed to send channelstream data')
290 294 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
291 295 return {"response": 'Channelstream data sent. '
292 296 'You should see a new live message now.'}
297
298 def _load_my_repos_data(self, watched=False):
299 if watched:
300 admin = False
301 follows_repos = Session().query(UserFollowing)\
302 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
303 .options(joinedload(UserFollowing.follows_repository))\
304 .all()
305 repo_list = [x.follows_repository for x in follows_repos]
306 else:
307 admin = True
308 repo_list = Repository.get_all_repos(
309 user_id=self._rhodecode_user.user_id)
310 repo_list = RepoList(repo_list, perm_set=[
311 'repository.read', 'repository.write', 'repository.admin'])
312
313 repos_data = RepoModel().get_repos_as_dict(
314 repo_list=repo_list, admin=admin)
315 # json used to render the grid
316 return json.dumps(repos_data)
317
318 @LoginRequired()
319 @NotAnonymous()
320 @view_config(
321 route_name='my_account_repos', request_method='GET',
322 renderer='rhodecode:templates/admin/my_account/my_account.mako')
323 def my_account_repos(self):
324 c = self.load_default_context()
325 c.active = 'repos'
326
327 # json used to render the grid
328 c.data = self._load_my_repos_data()
329 return self._get_template_context(c)
330
331 @LoginRequired()
332 @NotAnonymous()
333 @view_config(
334 route_name='my_account_watched', request_method='GET',
335 renderer='rhodecode:templates/admin/my_account/my_account.mako')
336 def my_account_watched(self):
337 c = self.load_default_context()
338 c.active = 'watched'
339
340 # json used to render the grid
341 c.data = self._load_my_repos_data(watched=True)
342 return self._get_template_context(c)
343
344 @LoginRequired()
345 @NotAnonymous()
346 @view_config(
347 route_name='my_account_perms', request_method='GET',
348 renderer='rhodecode:templates/admin/my_account/my_account.mako')
349 def my_account_perms(self):
350 c = self.load_default_context()
351 c.active = 'perms'
352
353 c.perm_user = c.auth_user
354 return self._get_template_context(c)
355
356 @LoginRequired()
357 @NotAnonymous()
358 @view_config(
359 route_name='my_account_notifications', request_method='GET',
360 renderer='rhodecode:templates/admin/my_account/my_account.mako')
361 def my_notifications(self):
362 c = self.load_default_context()
363 c.active = 'notifications'
364
365 return self._get_template_context(c)
366
367 @LoginRequired()
368 @NotAnonymous()
369 @CSRFRequired()
370 @view_config(
371 route_name='my_account_notifications_toggle_visibility',
372 request_method='POST', renderer='json_ext')
373 def my_notifications_toggle_visibility(self):
374 user = self._rhodecode_db_user
375 new_status = not user.user_data.get('notification_status', True)
376 user.update_userdata(notification_status=new_status)
377 Session().commit()
378 return user.user_data['notification_status'] No newline at end of file
@@ -1,975 +1,958 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(
121 121 directory=config['pylons.paths']['controllers'],
122 122 always_scan=config['debug'])
123 123 rmap.minimization = False
124 124 rmap.explicit = False
125 125
126 126 from rhodecode.lib.utils2 import str2bool
127 127 from rhodecode.model import repo, repo_group
128 128
129 129 def check_repo(environ, match_dict):
130 130 """
131 131 check for valid repository for proper 404 handling
132 132
133 133 :param environ:
134 134 :param match_dict:
135 135 """
136 136 repo_name = match_dict.get('repo_name')
137 137
138 138 if match_dict.get('f_path'):
139 139 # fix for multiple initial slashes that causes errors
140 140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 141 repo_model = repo.RepoModel()
142 142 by_name_match = repo_model.get_by_repo_name(repo_name)
143 143 # if we match quickly from database, short circuit the operation,
144 144 # and validate repo based on the type.
145 145 if by_name_match:
146 146 return True
147 147
148 148 by_id_match = repo_model.get_repo_by_id(repo_name)
149 149 if by_id_match:
150 150 repo_name = by_id_match.repo_name
151 151 match_dict['repo_name'] = repo_name
152 152 return True
153 153
154 154 return False
155 155
156 156 def check_group(environ, match_dict):
157 157 """
158 158 check for valid repository group path for proper 404 handling
159 159
160 160 :param environ:
161 161 :param match_dict:
162 162 """
163 163 repo_group_name = match_dict.get('group_name')
164 164 repo_group_model = repo_group.RepoGroupModel()
165 165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 166 if by_name_match:
167 167 return True
168 168
169 169 return False
170 170
171 171 def check_user_group(environ, match_dict):
172 172 """
173 173 check for valid user group for proper 404 handling
174 174
175 175 :param environ:
176 176 :param match_dict:
177 177 """
178 178 return True
179 179
180 180 def check_int(environ, match_dict):
181 181 return match_dict.get('id').isdigit()
182 182
183 183
184 184 #==========================================================================
185 185 # CUSTOM ROUTES HERE
186 186 #==========================================================================
187 187
188 188 # ping and pylons error test
189 189 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
190 190 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
191 191
192 192 # ADMIN REPOSITORY ROUTES
193 193 with rmap.submapper(path_prefix=ADMIN_PREFIX,
194 194 controller='admin/repos') as m:
195 195 m.connect('repos', '/repos',
196 196 action='create', conditions={'method': ['POST']})
197 197 m.connect('repos', '/repos',
198 198 action='index', conditions={'method': ['GET']})
199 199 m.connect('new_repo', '/create_repository', jsroute=True,
200 200 action='create_repository', conditions={'method': ['GET']})
201 201 m.connect('delete_repo', '/repos/{repo_name}',
202 202 action='delete', conditions={'method': ['DELETE']},
203 203 requirements=URL_NAME_REQUIREMENTS)
204 204 m.connect('repo', '/repos/{repo_name}',
205 205 action='show', conditions={'method': ['GET'],
206 206 'function': check_repo},
207 207 requirements=URL_NAME_REQUIREMENTS)
208 208
209 209 # ADMIN REPOSITORY GROUPS ROUTES
210 210 with rmap.submapper(path_prefix=ADMIN_PREFIX,
211 211 controller='admin/repo_groups') as m:
212 212 m.connect('repo_groups', '/repo_groups',
213 213 action='create', conditions={'method': ['POST']})
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='index', conditions={'method': ['GET']})
216 216 m.connect('new_repo_group', '/repo_groups/new',
217 217 action='new', conditions={'method': ['GET']})
218 218 m.connect('update_repo_group', '/repo_groups/{group_name}',
219 219 action='update', conditions={'method': ['PUT'],
220 220 'function': check_group},
221 221 requirements=URL_NAME_REQUIREMENTS)
222 222
223 223 # EXTRAS REPO GROUP ROUTES
224 224 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
225 225 action='edit',
226 226 conditions={'method': ['GET'], 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
229 229 action='edit',
230 230 conditions={'method': ['PUT'], 'function': check_group},
231 231 requirements=URL_NAME_REQUIREMENTS)
232 232
233 233 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
234 234 action='edit_repo_group_advanced',
235 235 conditions={'method': ['GET'], 'function': check_group},
236 236 requirements=URL_NAME_REQUIREMENTS)
237 237 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
238 238 action='edit_repo_group_advanced',
239 239 conditions={'method': ['PUT'], 'function': check_group},
240 240 requirements=URL_NAME_REQUIREMENTS)
241 241
242 242 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
243 243 action='edit_repo_group_perms',
244 244 conditions={'method': ['GET'], 'function': check_group},
245 245 requirements=URL_NAME_REQUIREMENTS)
246 246 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
247 247 action='update_perms',
248 248 conditions={'method': ['PUT'], 'function': check_group},
249 249 requirements=URL_NAME_REQUIREMENTS)
250 250
251 251 m.connect('delete_repo_group', '/repo_groups/{group_name}',
252 252 action='delete', conditions={'method': ['DELETE'],
253 253 'function': check_group},
254 254 requirements=URL_NAME_REQUIREMENTS)
255 255
256 256 # ADMIN USER ROUTES
257 257 with rmap.submapper(path_prefix=ADMIN_PREFIX,
258 258 controller='admin/users') as m:
259 259 m.connect('users', '/users',
260 260 action='create', conditions={'method': ['POST']})
261 261 m.connect('new_user', '/users/new',
262 262 action='new', conditions={'method': ['GET']})
263 263 m.connect('update_user', '/users/{user_id}',
264 264 action='update', conditions={'method': ['PUT']})
265 265 m.connect('delete_user', '/users/{user_id}',
266 266 action='delete', conditions={'method': ['DELETE']})
267 267 m.connect('edit_user', '/users/{user_id}/edit',
268 268 action='edit', conditions={'method': ['GET']}, jsroute=True)
269 269 m.connect('user', '/users/{user_id}',
270 270 action='show', conditions={'method': ['GET']})
271 271 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
272 272 action='reset_password', conditions={'method': ['POST']})
273 273 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
274 274 action='create_personal_repo_group', conditions={'method': ['POST']})
275 275
276 276 # EXTRAS USER ROUTES
277 277 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
278 278 action='edit_advanced', conditions={'method': ['GET']})
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='update_advanced', conditions={'method': ['PUT']})
281 281
282 282 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
283 283 action='edit_global_perms', conditions={'method': ['GET']})
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='update_global_perms', conditions={'method': ['PUT']})
286 286
287 287 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
288 288 action='edit_perms_summary', conditions={'method': ['GET']})
289 289
290 290 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
291 291 action='edit_emails', conditions={'method': ['GET']})
292 292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 293 action='add_email', conditions={'method': ['PUT']})
294 294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 295 action='delete_email', conditions={'method': ['DELETE']})
296 296
297 297 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
298 298 action='edit_ips', conditions={'method': ['GET']})
299 299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 300 action='add_ip', conditions={'method': ['PUT']})
301 301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 302 action='delete_ip', conditions={'method': ['DELETE']})
303 303
304 304 # ADMIN USER GROUPS REST ROUTES
305 305 with rmap.submapper(path_prefix=ADMIN_PREFIX,
306 306 controller='admin/user_groups') as m:
307 307 m.connect('users_groups', '/user_groups',
308 308 action='create', conditions={'method': ['POST']})
309 309 m.connect('users_groups', '/user_groups',
310 310 action='index', conditions={'method': ['GET']})
311 311 m.connect('new_users_group', '/user_groups/new',
312 312 action='new', conditions={'method': ['GET']})
313 313 m.connect('update_users_group', '/user_groups/{user_group_id}',
314 314 action='update', conditions={'method': ['PUT']})
315 315 m.connect('delete_users_group', '/user_groups/{user_group_id}',
316 316 action='delete', conditions={'method': ['DELETE']})
317 317 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
318 318 action='edit', conditions={'method': ['GET']},
319 319 function=check_user_group)
320 320
321 321 # EXTRAS USER GROUP ROUTES
322 322 m.connect('edit_user_group_global_perms',
323 323 '/user_groups/{user_group_id}/edit/global_permissions',
324 324 action='edit_global_perms', conditions={'method': ['GET']})
325 325 m.connect('edit_user_group_global_perms',
326 326 '/user_groups/{user_group_id}/edit/global_permissions',
327 327 action='update_global_perms', conditions={'method': ['PUT']})
328 328 m.connect('edit_user_group_perms_summary',
329 329 '/user_groups/{user_group_id}/edit/permissions_summary',
330 330 action='edit_perms_summary', conditions={'method': ['GET']})
331 331
332 332 m.connect('edit_user_group_perms',
333 333 '/user_groups/{user_group_id}/edit/permissions',
334 334 action='edit_perms', conditions={'method': ['GET']})
335 335 m.connect('edit_user_group_perms',
336 336 '/user_groups/{user_group_id}/edit/permissions',
337 337 action='update_perms', conditions={'method': ['PUT']})
338 338
339 339 m.connect('edit_user_group_advanced',
340 340 '/user_groups/{user_group_id}/edit/advanced',
341 341 action='edit_advanced', conditions={'method': ['GET']})
342 342
343 343 m.connect('edit_user_group_advanced_sync',
344 344 '/user_groups/{user_group_id}/edit/advanced/sync',
345 345 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
346 346
347 347 m.connect('edit_user_group_members',
348 348 '/user_groups/{user_group_id}/edit/members', jsroute=True,
349 349 action='user_group_members', conditions={'method': ['GET']})
350 350
351 351 # ADMIN PERMISSIONS ROUTES
352 352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 353 controller='admin/permissions') as m:
354 354 m.connect('admin_permissions_application', '/permissions/application',
355 355 action='permission_application_update', conditions={'method': ['POST']})
356 356 m.connect('admin_permissions_application', '/permissions/application',
357 357 action='permission_application', conditions={'method': ['GET']})
358 358
359 359 m.connect('admin_permissions_global', '/permissions/global',
360 360 action='permission_global_update', conditions={'method': ['POST']})
361 361 m.connect('admin_permissions_global', '/permissions/global',
362 362 action='permission_global', conditions={'method': ['GET']})
363 363
364 364 m.connect('admin_permissions_object', '/permissions/object',
365 365 action='permission_objects_update', conditions={'method': ['POST']})
366 366 m.connect('admin_permissions_object', '/permissions/object',
367 367 action='permission_objects', conditions={'method': ['GET']})
368 368
369 369 m.connect('admin_permissions_ips', '/permissions/ips',
370 370 action='permission_ips', conditions={'method': ['POST']})
371 371 m.connect('admin_permissions_ips', '/permissions/ips',
372 372 action='permission_ips', conditions={'method': ['GET']})
373 373
374 374 m.connect('admin_permissions_overview', '/permissions/overview',
375 375 action='permission_perms', conditions={'method': ['GET']})
376 376
377 377 # ADMIN DEFAULTS REST ROUTES
378 378 with rmap.submapper(path_prefix=ADMIN_PREFIX,
379 379 controller='admin/defaults') as m:
380 380 m.connect('admin_defaults_repositories', '/defaults/repositories',
381 381 action='update_repository_defaults', conditions={'method': ['POST']})
382 382 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 383 action='index', conditions={'method': ['GET']})
384 384
385 385 # ADMIN DEBUG STYLE ROUTES
386 386 if str2bool(config.get('debug_style')):
387 387 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
388 388 controller='debug_style') as m:
389 389 m.connect('debug_style_home', '',
390 390 action='index', conditions={'method': ['GET']})
391 391 m.connect('debug_style_template', '/t/{t_path}',
392 392 action='template', conditions={'method': ['GET']})
393 393
394 394 # ADMIN SETTINGS ROUTES
395 395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 396 controller='admin/settings') as m:
397 397
398 398 # default
399 399 m.connect('admin_settings', '/settings',
400 400 action='settings_global_update',
401 401 conditions={'method': ['POST']})
402 402 m.connect('admin_settings', '/settings',
403 403 action='settings_global', conditions={'method': ['GET']})
404 404
405 405 m.connect('admin_settings_vcs', '/settings/vcs',
406 406 action='settings_vcs_update',
407 407 conditions={'method': ['POST']})
408 408 m.connect('admin_settings_vcs', '/settings/vcs',
409 409 action='settings_vcs',
410 410 conditions={'method': ['GET']})
411 411 m.connect('admin_settings_vcs', '/settings/vcs',
412 412 action='delete_svn_pattern',
413 413 conditions={'method': ['DELETE']})
414 414
415 415 m.connect('admin_settings_mapping', '/settings/mapping',
416 416 action='settings_mapping_update',
417 417 conditions={'method': ['POST']})
418 418 m.connect('admin_settings_mapping', '/settings/mapping',
419 419 action='settings_mapping', conditions={'method': ['GET']})
420 420
421 421 m.connect('admin_settings_global', '/settings/global',
422 422 action='settings_global_update',
423 423 conditions={'method': ['POST']})
424 424 m.connect('admin_settings_global', '/settings/global',
425 425 action='settings_global', conditions={'method': ['GET']})
426 426
427 427 m.connect('admin_settings_visual', '/settings/visual',
428 428 action='settings_visual_update',
429 429 conditions={'method': ['POST']})
430 430 m.connect('admin_settings_visual', '/settings/visual',
431 431 action='settings_visual', conditions={'method': ['GET']})
432 432
433 433 m.connect('admin_settings_issuetracker',
434 434 '/settings/issue-tracker', action='settings_issuetracker',
435 435 conditions={'method': ['GET']})
436 436 m.connect('admin_settings_issuetracker_save',
437 437 '/settings/issue-tracker/save',
438 438 action='settings_issuetracker_save',
439 439 conditions={'method': ['POST']})
440 440 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
441 441 action='settings_issuetracker_test',
442 442 conditions={'method': ['POST']})
443 443 m.connect('admin_issuetracker_delete',
444 444 '/settings/issue-tracker/delete',
445 445 action='settings_issuetracker_delete',
446 446 conditions={'method': ['DELETE']})
447 447
448 448 m.connect('admin_settings_email', '/settings/email',
449 449 action='settings_email_update',
450 450 conditions={'method': ['POST']})
451 451 m.connect('admin_settings_email', '/settings/email',
452 452 action='settings_email', conditions={'method': ['GET']})
453 453
454 454 m.connect('admin_settings_hooks', '/settings/hooks',
455 455 action='settings_hooks_update',
456 456 conditions={'method': ['POST', 'DELETE']})
457 457 m.connect('admin_settings_hooks', '/settings/hooks',
458 458 action='settings_hooks', conditions={'method': ['GET']})
459 459
460 460 m.connect('admin_settings_search', '/settings/search',
461 461 action='settings_search', conditions={'method': ['GET']})
462 462
463 463 m.connect('admin_settings_supervisor', '/settings/supervisor',
464 464 action='settings_supervisor', conditions={'method': ['GET']})
465 465 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
466 466 action='settings_supervisor_log', conditions={'method': ['GET']})
467 467
468 468 m.connect('admin_settings_labs', '/settings/labs',
469 469 action='settings_labs_update',
470 470 conditions={'method': ['POST']})
471 471 m.connect('admin_settings_labs', '/settings/labs',
472 472 action='settings_labs', conditions={'method': ['GET']})
473 473
474 474 # ADMIN MY ACCOUNT
475 475 with rmap.submapper(path_prefix=ADMIN_PREFIX,
476 476 controller='admin/my_account') as m:
477 477
478 478 m.connect('my_account_edit', '/my_account/edit',
479 479 action='my_account_edit', conditions={'method': ['GET']})
480 480 m.connect('my_account', '/my_account/update',
481 481 action='my_account_update', conditions={'method': ['POST']})
482 482
483 483 # NOTE(marcink): this needs to be kept for password force flag to be
484 484 # handler, remove after migration to pyramid
485 485 m.connect('my_account_password', '/my_account/password',
486 486 action='my_account_password', conditions={'method': ['GET']})
487 487
488 m.connect('my_account_repos', '/my_account/repos',
489 action='my_account_repos', conditions={'method': ['GET']})
490
491 m.connect('my_account_watched', '/my_account/watched',
492 action='my_account_watched', conditions={'method': ['GET']})
493
494 488 m.connect('my_account_pullrequests', '/my_account/pull_requests',
495 489 action='my_account_pullrequests', conditions={'method': ['GET']})
496 490
497 m.connect('my_account_perms', '/my_account/perms',
498 action='my_account_perms', conditions={'method': ['GET']})
499
500 m.connect('my_account_notifications', '/my_account/notifications',
501 action='my_notifications',
502 conditions={'method': ['GET']})
503 m.connect('my_account_notifications_toggle_visibility',
504 '/my_account/toggle_visibility',
505 action='my_notifications_toggle_visibility',
506 conditions={'method': ['POST']})
507
508 491 # NOTIFICATION REST ROUTES
509 492 with rmap.submapper(path_prefix=ADMIN_PREFIX,
510 493 controller='admin/notifications') as m:
511 494 m.connect('notifications', '/notifications',
512 495 action='index', conditions={'method': ['GET']})
513 496 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
514 497 action='mark_all_read', conditions={'method': ['POST']})
515 498 m.connect('/notifications/{notification_id}',
516 499 action='update', conditions={'method': ['PUT']})
517 500 m.connect('/notifications/{notification_id}',
518 501 action='delete', conditions={'method': ['DELETE']})
519 502 m.connect('notification', '/notifications/{notification_id}',
520 503 action='show', conditions={'method': ['GET']})
521 504
522 505 # ADMIN GIST
523 506 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 507 controller='admin/gists') as m:
525 508 m.connect('gists', '/gists',
526 509 action='create', conditions={'method': ['POST']})
527 510 m.connect('gists', '/gists', jsroute=True,
528 511 action='index', conditions={'method': ['GET']})
529 512 m.connect('new_gist', '/gists/new', jsroute=True,
530 513 action='new', conditions={'method': ['GET']})
531 514
532 515 m.connect('/gists/{gist_id}',
533 516 action='delete', conditions={'method': ['DELETE']})
534 517 m.connect('edit_gist', '/gists/{gist_id}/edit',
535 518 action='edit_form', conditions={'method': ['GET']})
536 519 m.connect('edit_gist', '/gists/{gist_id}/edit',
537 520 action='edit', conditions={'method': ['POST']})
538 521 m.connect(
539 522 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
540 523 action='check_revision', conditions={'method': ['GET']})
541 524
542 525 m.connect('gist', '/gists/{gist_id}',
543 526 action='show', conditions={'method': ['GET']})
544 527 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
545 528 revision='tip',
546 529 action='show', conditions={'method': ['GET']})
547 530 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
548 531 revision='tip',
549 532 action='show', conditions={'method': ['GET']})
550 533 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
551 534 revision='tip',
552 535 action='show', conditions={'method': ['GET']},
553 536 requirements=URL_NAME_REQUIREMENTS)
554 537
555 538 # USER JOURNAL
556 539 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
557 540 controller='journal', action='index')
558 541 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
559 542 controller='journal', action='journal_rss')
560 543 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
561 544 controller='journal', action='journal_atom')
562 545
563 546 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
564 547 controller='journal', action='public_journal')
565 548
566 549 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
567 550 controller='journal', action='public_journal_rss')
568 551
569 552 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
570 553 controller='journal', action='public_journal_rss')
571 554
572 555 rmap.connect('public_journal_atom',
573 556 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
574 557 action='public_journal_atom')
575 558
576 559 rmap.connect('public_journal_atom_old',
577 560 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
578 561 action='public_journal_atom')
579 562
580 563 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
581 564 controller='journal', action='toggle_following', jsroute=True,
582 565 conditions={'method': ['POST']})
583 566
584 567 # FEEDS
585 568 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
586 569 controller='feed', action='rss',
587 570 conditions={'function': check_repo},
588 571 requirements=URL_NAME_REQUIREMENTS)
589 572
590 573 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
591 574 controller='feed', action='atom',
592 575 conditions={'function': check_repo},
593 576 requirements=URL_NAME_REQUIREMENTS)
594 577
595 578 #==========================================================================
596 579 # REPOSITORY ROUTES
597 580 #==========================================================================
598 581
599 582 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
600 583 controller='admin/repos', action='repo_creating',
601 584 requirements=URL_NAME_REQUIREMENTS)
602 585 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
603 586 controller='admin/repos', action='repo_check',
604 587 requirements=URL_NAME_REQUIREMENTS)
605 588
606 589 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
607 590 controller='changeset', revision='tip',
608 591 conditions={'function': check_repo},
609 592 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
610 593 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
611 594 controller='changeset', revision='tip', action='changeset_children',
612 595 conditions={'function': check_repo},
613 596 requirements=URL_NAME_REQUIREMENTS)
614 597 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
615 598 controller='changeset', revision='tip', action='changeset_parents',
616 599 conditions={'function': check_repo},
617 600 requirements=URL_NAME_REQUIREMENTS)
618 601
619 602 # repo edit options
620 603 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
621 604 controller='admin/repos', action='edit_fields',
622 605 conditions={'method': ['GET'], 'function': check_repo},
623 606 requirements=URL_NAME_REQUIREMENTS)
624 607 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
625 608 controller='admin/repos', action='create_repo_field',
626 609 conditions={'method': ['PUT'], 'function': check_repo},
627 610 requirements=URL_NAME_REQUIREMENTS)
628 611 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
629 612 controller='admin/repos', action='delete_repo_field',
630 613 conditions={'method': ['DELETE'], 'function': check_repo},
631 614 requirements=URL_NAME_REQUIREMENTS)
632 615
633 616 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
634 617 controller='admin/repos', action='toggle_locking',
635 618 conditions={'method': ['GET'], 'function': check_repo},
636 619 requirements=URL_NAME_REQUIREMENTS)
637 620
638 621 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
639 622 controller='admin/repos', action='edit_remote_form',
640 623 conditions={'method': ['GET'], 'function': check_repo},
641 624 requirements=URL_NAME_REQUIREMENTS)
642 625 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
643 626 controller='admin/repos', action='edit_remote',
644 627 conditions={'method': ['PUT'], 'function': check_repo},
645 628 requirements=URL_NAME_REQUIREMENTS)
646 629
647 630 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
648 631 controller='admin/repos', action='edit_statistics_form',
649 632 conditions={'method': ['GET'], 'function': check_repo},
650 633 requirements=URL_NAME_REQUIREMENTS)
651 634 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
652 635 controller='admin/repos', action='edit_statistics',
653 636 conditions={'method': ['PUT'], 'function': check_repo},
654 637 requirements=URL_NAME_REQUIREMENTS)
655 638 rmap.connect('repo_settings_issuetracker',
656 639 '/{repo_name}/settings/issue-tracker',
657 640 controller='admin/repos', action='repo_issuetracker',
658 641 conditions={'method': ['GET'], 'function': check_repo},
659 642 requirements=URL_NAME_REQUIREMENTS)
660 643 rmap.connect('repo_issuetracker_test',
661 644 '/{repo_name}/settings/issue-tracker/test',
662 645 controller='admin/repos', action='repo_issuetracker_test',
663 646 conditions={'method': ['POST'], 'function': check_repo},
664 647 requirements=URL_NAME_REQUIREMENTS)
665 648 rmap.connect('repo_issuetracker_delete',
666 649 '/{repo_name}/settings/issue-tracker/delete',
667 650 controller='admin/repos', action='repo_issuetracker_delete',
668 651 conditions={'method': ['DELETE'], 'function': check_repo},
669 652 requirements=URL_NAME_REQUIREMENTS)
670 653 rmap.connect('repo_issuetracker_save',
671 654 '/{repo_name}/settings/issue-tracker/save',
672 655 controller='admin/repos', action='repo_issuetracker_save',
673 656 conditions={'method': ['POST'], 'function': check_repo},
674 657 requirements=URL_NAME_REQUIREMENTS)
675 658 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
676 659 controller='admin/repos', action='repo_settings_vcs_update',
677 660 conditions={'method': ['POST'], 'function': check_repo},
678 661 requirements=URL_NAME_REQUIREMENTS)
679 662 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
680 663 controller='admin/repos', action='repo_settings_vcs',
681 664 conditions={'method': ['GET'], 'function': check_repo},
682 665 requirements=URL_NAME_REQUIREMENTS)
683 666 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
684 667 controller='admin/repos', action='repo_delete_svn_pattern',
685 668 conditions={'method': ['DELETE'], 'function': check_repo},
686 669 requirements=URL_NAME_REQUIREMENTS)
687 670 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
688 671 controller='admin/repos', action='repo_settings_pullrequest',
689 672 conditions={'method': ['GET', 'POST'], 'function': check_repo},
690 673 requirements=URL_NAME_REQUIREMENTS)
691 674
692 675 # still working url for backward compat.
693 676 rmap.connect('raw_changeset_home_depraced',
694 677 '/{repo_name}/raw-changeset/{revision}',
695 678 controller='changeset', action='changeset_raw',
696 679 revision='tip', conditions={'function': check_repo},
697 680 requirements=URL_NAME_REQUIREMENTS)
698 681
699 682 # new URLs
700 683 rmap.connect('changeset_raw_home',
701 684 '/{repo_name}/changeset-diff/{revision}',
702 685 controller='changeset', action='changeset_raw',
703 686 revision='tip', conditions={'function': check_repo},
704 687 requirements=URL_NAME_REQUIREMENTS)
705 688
706 689 rmap.connect('changeset_patch_home',
707 690 '/{repo_name}/changeset-patch/{revision}',
708 691 controller='changeset', action='changeset_patch',
709 692 revision='tip', conditions={'function': check_repo},
710 693 requirements=URL_NAME_REQUIREMENTS)
711 694
712 695 rmap.connect('changeset_download_home',
713 696 '/{repo_name}/changeset-download/{revision}',
714 697 controller='changeset', action='changeset_download',
715 698 revision='tip', conditions={'function': check_repo},
716 699 requirements=URL_NAME_REQUIREMENTS)
717 700
718 701 rmap.connect('changeset_comment',
719 702 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
720 703 controller='changeset', revision='tip', action='comment',
721 704 conditions={'function': check_repo},
722 705 requirements=URL_NAME_REQUIREMENTS)
723 706
724 707 rmap.connect('changeset_comment_preview',
725 708 '/{repo_name}/changeset/comment/preview', jsroute=True,
726 709 controller='changeset', action='preview_comment',
727 710 conditions={'function': check_repo, 'method': ['POST']},
728 711 requirements=URL_NAME_REQUIREMENTS)
729 712
730 713 rmap.connect('changeset_comment_delete',
731 714 '/{repo_name}/changeset/comment/{comment_id}/delete',
732 715 controller='changeset', action='delete_comment',
733 716 conditions={'function': check_repo, 'method': ['DELETE']},
734 717 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
735 718
736 719 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
737 720 controller='changeset', action='changeset_info',
738 721 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
739 722
740 723 rmap.connect('compare_home',
741 724 '/{repo_name}/compare',
742 725 controller='compare', action='index',
743 726 conditions={'function': check_repo},
744 727 requirements=URL_NAME_REQUIREMENTS)
745 728
746 729 rmap.connect('compare_url',
747 730 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
748 731 controller='compare', action='compare',
749 732 conditions={'function': check_repo},
750 733 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
751 734
752 735 rmap.connect('pullrequest_home',
753 736 '/{repo_name}/pull-request/new', controller='pullrequests',
754 737 action='index', conditions={'function': check_repo,
755 738 'method': ['GET']},
756 739 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
757 740
758 741 rmap.connect('pullrequest',
759 742 '/{repo_name}/pull-request/new', controller='pullrequests',
760 743 action='create', conditions={'function': check_repo,
761 744 'method': ['POST']},
762 745 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
763 746
764 747 rmap.connect('pullrequest_repo_refs',
765 748 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
766 749 controller='pullrequests',
767 750 action='get_repo_refs',
768 751 conditions={'function': check_repo, 'method': ['GET']},
769 752 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
770 753
771 754 rmap.connect('pullrequest_repo_destinations',
772 755 '/{repo_name}/pull-request/repo-destinations',
773 756 controller='pullrequests',
774 757 action='get_repo_destinations',
775 758 conditions={'function': check_repo, 'method': ['GET']},
776 759 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
777 760
778 761 rmap.connect('pullrequest_show',
779 762 '/{repo_name}/pull-request/{pull_request_id}',
780 763 controller='pullrequests',
781 764 action='show', conditions={'function': check_repo,
782 765 'method': ['GET']},
783 766 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
784 767
785 768 rmap.connect('pullrequest_update',
786 769 '/{repo_name}/pull-request/{pull_request_id}',
787 770 controller='pullrequests',
788 771 action='update', conditions={'function': check_repo,
789 772 'method': ['PUT']},
790 773 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
791 774
792 775 rmap.connect('pullrequest_merge',
793 776 '/{repo_name}/pull-request/{pull_request_id}',
794 777 controller='pullrequests',
795 778 action='merge', conditions={'function': check_repo,
796 779 'method': ['POST']},
797 780 requirements=URL_NAME_REQUIREMENTS)
798 781
799 782 rmap.connect('pullrequest_delete',
800 783 '/{repo_name}/pull-request/{pull_request_id}',
801 784 controller='pullrequests',
802 785 action='delete', conditions={'function': check_repo,
803 786 'method': ['DELETE']},
804 787 requirements=URL_NAME_REQUIREMENTS)
805 788
806 789 rmap.connect('pullrequest_comment',
807 790 '/{repo_name}/pull-request-comment/{pull_request_id}',
808 791 controller='pullrequests',
809 792 action='comment', conditions={'function': check_repo,
810 793 'method': ['POST']},
811 794 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
812 795
813 796 rmap.connect('pullrequest_comment_delete',
814 797 '/{repo_name}/pull-request-comment/{comment_id}/delete',
815 798 controller='pullrequests', action='delete_comment',
816 799 conditions={'function': check_repo, 'method': ['DELETE']},
817 800 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
818 801
819 802 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
820 803 controller='changelog', conditions={'function': check_repo},
821 804 requirements=URL_NAME_REQUIREMENTS)
822 805
823 806 rmap.connect('changelog_file_home',
824 807 '/{repo_name}/changelog/{revision}/{f_path}',
825 808 controller='changelog', f_path=None,
826 809 conditions={'function': check_repo},
827 810 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
828 811
829 812 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
830 813 controller='changelog', action='changelog_elements',
831 814 conditions={'function': check_repo},
832 815 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
833 816
834 817 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
835 818 controller='files', revision='tip', f_path='',
836 819 conditions={'function': check_repo},
837 820 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
838 821
839 822 rmap.connect('files_home_simple_catchrev',
840 823 '/{repo_name}/files/{revision}',
841 824 controller='files', revision='tip', f_path='',
842 825 conditions={'function': check_repo},
843 826 requirements=URL_NAME_REQUIREMENTS)
844 827
845 828 rmap.connect('files_home_simple_catchall',
846 829 '/{repo_name}/files',
847 830 controller='files', revision='tip', f_path='',
848 831 conditions={'function': check_repo},
849 832 requirements=URL_NAME_REQUIREMENTS)
850 833
851 834 rmap.connect('files_history_home',
852 835 '/{repo_name}/history/{revision}/{f_path}',
853 836 controller='files', action='history', revision='tip', f_path='',
854 837 conditions={'function': check_repo},
855 838 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
856 839
857 840 rmap.connect('files_authors_home',
858 841 '/{repo_name}/authors/{revision}/{f_path}',
859 842 controller='files', action='authors', revision='tip', f_path='',
860 843 conditions={'function': check_repo},
861 844 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
862 845
863 846 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
864 847 controller='files', action='diff', f_path='',
865 848 conditions={'function': check_repo},
866 849 requirements=URL_NAME_REQUIREMENTS)
867 850
868 851 rmap.connect('files_diff_2way_home',
869 852 '/{repo_name}/diff-2way/{f_path}',
870 853 controller='files', action='diff_2way', f_path='',
871 854 conditions={'function': check_repo},
872 855 requirements=URL_NAME_REQUIREMENTS)
873 856
874 857 rmap.connect('files_rawfile_home',
875 858 '/{repo_name}/rawfile/{revision}/{f_path}',
876 859 controller='files', action='rawfile', revision='tip',
877 860 f_path='', conditions={'function': check_repo},
878 861 requirements=URL_NAME_REQUIREMENTS)
879 862
880 863 rmap.connect('files_raw_home',
881 864 '/{repo_name}/raw/{revision}/{f_path}',
882 865 controller='files', action='raw', revision='tip', f_path='',
883 866 conditions={'function': check_repo},
884 867 requirements=URL_NAME_REQUIREMENTS)
885 868
886 869 rmap.connect('files_render_home',
887 870 '/{repo_name}/render/{revision}/{f_path}',
888 871 controller='files', action='index', revision='tip', f_path='',
889 872 rendered=True, conditions={'function': check_repo},
890 873 requirements=URL_NAME_REQUIREMENTS)
891 874
892 875 rmap.connect('files_annotate_home',
893 876 '/{repo_name}/annotate/{revision}/{f_path}',
894 877 controller='files', action='index', revision='tip',
895 878 f_path='', annotate=True, conditions={'function': check_repo},
896 879 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
897 880
898 881 rmap.connect('files_annotate_previous',
899 882 '/{repo_name}/annotate-previous/{revision}/{f_path}',
900 883 controller='files', action='annotate_previous', revision='tip',
901 884 f_path='', annotate=True, conditions={'function': check_repo},
902 885 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903 886
904 887 rmap.connect('files_edit',
905 888 '/{repo_name}/edit/{revision}/{f_path}',
906 889 controller='files', action='edit', revision='tip',
907 890 f_path='',
908 891 conditions={'function': check_repo, 'method': ['POST']},
909 892 requirements=URL_NAME_REQUIREMENTS)
910 893
911 894 rmap.connect('files_edit_home',
912 895 '/{repo_name}/edit/{revision}/{f_path}',
913 896 controller='files', action='edit_home', revision='tip',
914 897 f_path='', conditions={'function': check_repo},
915 898 requirements=URL_NAME_REQUIREMENTS)
916 899
917 900 rmap.connect('files_add',
918 901 '/{repo_name}/add/{revision}/{f_path}',
919 902 controller='files', action='add', revision='tip',
920 903 f_path='',
921 904 conditions={'function': check_repo, 'method': ['POST']},
922 905 requirements=URL_NAME_REQUIREMENTS)
923 906
924 907 rmap.connect('files_add_home',
925 908 '/{repo_name}/add/{revision}/{f_path}',
926 909 controller='files', action='add_home', revision='tip',
927 910 f_path='', conditions={'function': check_repo},
928 911 requirements=URL_NAME_REQUIREMENTS)
929 912
930 913 rmap.connect('files_delete',
931 914 '/{repo_name}/delete/{revision}/{f_path}',
932 915 controller='files', action='delete', revision='tip',
933 916 f_path='',
934 917 conditions={'function': check_repo, 'method': ['POST']},
935 918 requirements=URL_NAME_REQUIREMENTS)
936 919
937 920 rmap.connect('files_delete_home',
938 921 '/{repo_name}/delete/{revision}/{f_path}',
939 922 controller='files', action='delete_home', revision='tip',
940 923 f_path='', conditions={'function': check_repo},
941 924 requirements=URL_NAME_REQUIREMENTS)
942 925
943 926 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
944 927 controller='files', action='archivefile',
945 928 conditions={'function': check_repo},
946 929 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
947 930
948 931 rmap.connect('files_nodelist_home',
949 932 '/{repo_name}/nodelist/{revision}/{f_path}',
950 933 controller='files', action='nodelist',
951 934 conditions={'function': check_repo},
952 935 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
953 936
954 937 rmap.connect('files_nodetree_full',
955 938 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
956 939 controller='files', action='nodetree_full',
957 940 conditions={'function': check_repo},
958 941 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
959 942
960 943 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
961 944 controller='forks', action='fork_create',
962 945 conditions={'function': check_repo, 'method': ['POST']},
963 946 requirements=URL_NAME_REQUIREMENTS)
964 947
965 948 rmap.connect('repo_fork_home', '/{repo_name}/fork',
966 949 controller='forks', action='fork',
967 950 conditions={'function': check_repo},
968 951 requirements=URL_NAME_REQUIREMENTS)
969 952
970 953 rmap.connect('repo_forks_home', '/{repo_name}/forks',
971 954 controller='forks', action='forks',
972 955 conditions={'function': check_repo},
973 956 requirements=URL_NAME_REQUIREMENTS)
974 957
975 958 return rmap
@@ -1,294 +1,236 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 27
28 28 import formencode
29 29 from formencode import htmlfill
30 30 from pyramid.httpexceptions import HTTPFound
31 31
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
36 35
37 36 from rhodecode.lib import helpers as h
38 37 from rhodecode.lib import auth
39 38 from rhodecode.lib.auth import (
40 39 LoginRequired, NotAnonymous, AuthUser)
41 40 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.utils import jsonify
43 41 from rhodecode.lib.utils2 import safe_int, str2bool
44 42 from rhodecode.lib.ext_json import json
45 43
46 44 from rhodecode.model.db import (
47 45 Repository, PullRequest, UserEmailMap, User, UserFollowing)
48 46 from rhodecode.model.forms import UserForm
49 from rhodecode.model.scm import RepoList
50 47 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
52 48 from rhodecode.model.meta import Session
53 49 from rhodecode.model.pull_request import PullRequestModel
54 50 from rhodecode.model.comment import CommentsModel
55 51
56 52 log = logging.getLogger(__name__)
57 53
58 54
59 55 class MyAccountController(BaseController):
60 56 """REST Controller styled on the Atom Publishing Protocol"""
61 57 # To properly map this controller, ensure your config/routing.py
62 58 # file has a resource setup:
63 59 # map.resource('setting', 'settings', controller='admin/settings',
64 60 # path_prefix='/admin', name_prefix='admin_')
65 61
66 62 @LoginRequired()
67 63 @NotAnonymous()
68 64 def __before__(self):
69 65 super(MyAccountController, self).__before__()
70 66
71 67 def __load_data(self):
72 68 c.user = User.get(c.rhodecode_user.user_id)
73 69 if c.user.username == User.DEFAULT_USER:
74 70 h.flash(_("You can't edit this user since it's"
75 71 " crucial for entire application"), category='warning')
76 72 return redirect(h.route_path('users'))
77 73
78 74 c.auth_user = AuthUser(
79 75 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
80 76
81 def _load_my_repos_data(self, watched=False):
82 if watched:
83 admin = False
84 follows_repos = Session().query(UserFollowing)\
85 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
86 .options(joinedload(UserFollowing.follows_repository))\
87 .all()
88 repo_list = [x.follows_repository for x in follows_repos]
89 else:
90 admin = True
91 repo_list = Repository.get_all_repos(
92 user_id=c.rhodecode_user.user_id)
93 repo_list = RepoList(repo_list, perm_set=[
94 'repository.read', 'repository.write', 'repository.admin'])
95
96 repos_data = RepoModel().get_repos_as_dict(
97 repo_list=repo_list, admin=admin)
98 # json used to render the grid
99 return json.dumps(repos_data)
100
101 77 @auth.CSRFRequired()
102 78 def my_account_update(self):
103 79 """
104 80 POST /_admin/my_account Updates info of my account
105 81 """
106 82 # url('my_account')
107 83 c.active = 'profile_edit'
108 84 self.__load_data()
109 85 c.perm_user = c.auth_user
110 86 c.extern_type = c.user.extern_type
111 87 c.extern_name = c.user.extern_name
112 88
113 89 defaults = c.user.get_dict()
114 90 update = False
115 91 _form = UserForm(edit=True,
116 92 old_data={'user_id': c.rhodecode_user.user_id,
117 93 'email': c.rhodecode_user.email})()
118 94 form_result = {}
119 95 try:
120 96 post_data = dict(request.POST)
121 97 post_data['new_password'] = ''
122 98 post_data['password_confirmation'] = ''
123 99 form_result = _form.to_python(post_data)
124 100 # skip updating those attrs for my account
125 101 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
126 102 'new_password', 'password_confirmation']
127 103 # TODO: plugin should define if username can be updated
128 104 if c.extern_type != "rhodecode":
129 105 # forbid updating username for external accounts
130 106 skip_attrs.append('username')
131 107
132 108 UserModel().update_user(
133 109 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
134 110 h.flash(_('Your account was updated successfully'),
135 111 category='success')
136 112 Session().commit()
137 113 update = True
138 114
139 115 except formencode.Invalid as errors:
140 116 return htmlfill.render(
141 117 render('admin/my_account/my_account.mako'),
142 118 defaults=errors.value,
143 119 errors=errors.error_dict or {},
144 120 prefix_error=False,
145 121 encoding="UTF-8",
146 122 force_defaults=False)
147 123 except Exception:
148 124 log.exception("Exception updating user")
149 125 h.flash(_('Error occurred during update of user %s')
150 126 % form_result.get('username'), category='error')
151 127
152 128 if update:
153 129 raise HTTPFound(h.route_path('my_account_profile'))
154 130
155 131 return htmlfill.render(
156 132 render('admin/my_account/my_account.mako'),
157 133 defaults=defaults,
158 134 encoding="UTF-8",
159 135 force_defaults=False
160 136 )
161 137
162 138 def my_account_edit(self):
163 139 """
164 140 GET /_admin/my_account/edit Displays edit form of my account
165 141 """
166 142 c.active = 'profile_edit'
167 143 self.__load_data()
168 144 c.perm_user = c.auth_user
169 145 c.extern_type = c.user.extern_type
170 146 c.extern_name = c.user.extern_name
171 147
172 148 defaults = c.user.get_dict()
173 149 return htmlfill.render(
174 150 render('admin/my_account/my_account.mako'),
175 151 defaults=defaults,
176 152 encoding="UTF-8",
177 153 force_defaults=False
178 154 )
179 155
180 def my_account_repos(self):
181 c.active = 'repos'
182 self.__load_data()
183
184 # json used to render the grid
185 c.data = self._load_my_repos_data()
186 return render('admin/my_account/my_account.mako')
187
188 def my_account_watched(self):
189 c.active = 'watched'
190 self.__load_data()
191
192 # json used to render the grid
193 c.data = self._load_my_repos_data(watched=True)
194 return render('admin/my_account/my_account.mako')
195
196 def my_account_perms(self):
197 c.active = 'perms'
198 self.__load_data()
199 c.perm_user = c.auth_user
200
201 return render('admin/my_account/my_account.mako')
202
203 156 def _extract_ordering(self, request):
204 157 column_index = safe_int(request.GET.get('order[0][column]'))
205 158 order_dir = request.GET.get('order[0][dir]', 'desc')
206 159 order_by = request.GET.get(
207 160 'columns[%s][data][sort]' % column_index, 'name_raw')
208 161 return order_by, order_dir
209 162
210 163 def _get_pull_requests_list(self, statuses):
211 164 start = safe_int(request.GET.get('start'), 0)
212 165 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
213 166 order_by, order_dir = self._extract_ordering(request)
214 167
215 168 pull_requests = PullRequestModel().get_im_participating_in(
216 169 user_id=c.rhodecode_user.user_id,
217 170 statuses=statuses,
218 171 offset=start, length=length, order_by=order_by,
219 172 order_dir=order_dir)
220 173
221 174 pull_requests_total_count = PullRequestModel().count_im_participating_in(
222 175 user_id=c.rhodecode_user.user_id, statuses=statuses)
223 176
224 177 from rhodecode.lib.utils import PartialRenderer
225 178 _render = PartialRenderer('data_table/_dt_elements.mako')
226 179 data = []
227 180 for pr in pull_requests:
228 181 repo_id = pr.target_repo_id
229 182 comments = CommentsModel().get_all_comments(
230 183 repo_id, pull_request=pr)
231 184 owned = pr.user_id == c.rhodecode_user.user_id
232 185 status = pr.calculated_review_status()
233 186
234 187 data.append({
235 188 'target_repo': _render('pullrequest_target_repo',
236 189 pr.target_repo.repo_name),
237 190 'name': _render('pullrequest_name',
238 191 pr.pull_request_id, pr.target_repo.repo_name,
239 192 short=True),
240 193 'name_raw': pr.pull_request_id,
241 194 'status': _render('pullrequest_status', status),
242 195 'title': _render(
243 196 'pullrequest_title', pr.title, pr.description),
244 197 'description': h.escape(pr.description),
245 198 'updated_on': _render('pullrequest_updated_on',
246 199 h.datetime_to_time(pr.updated_on)),
247 200 'updated_on_raw': h.datetime_to_time(pr.updated_on),
248 201 'created_on': _render('pullrequest_updated_on',
249 202 h.datetime_to_time(pr.created_on)),
250 203 'created_on_raw': h.datetime_to_time(pr.created_on),
251 204 'author': _render('pullrequest_author',
252 205 pr.author.full_contact, ),
253 206 'author_raw': pr.author.full_name,
254 207 'comments': _render('pullrequest_comments', len(comments)),
255 208 'comments_raw': len(comments),
256 209 'closed': pr.is_closed(),
257 210 'owned': owned
258 211 })
259 212 # json used to render the grid
260 213 data = ({
261 214 'data': data,
262 215 'recordsTotal': pull_requests_total_count,
263 216 'recordsFiltered': pull_requests_total_count,
264 217 })
265 218 return data
266 219
267 220 def my_account_pullrequests(self):
268 221 c.active = 'pullrequests'
269 222 self.__load_data()
270 223 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
271 224
272 225 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
273 226 if c.show_closed:
274 227 statuses += [PullRequest.STATUS_CLOSED]
275 228 data = self._get_pull_requests_list(statuses)
276 229 if not request.is_xhr:
277 230 c.data_participate = json.dumps(data['data'])
278 231 c.records_total_participate = data['recordsTotal']
279 232 return render('admin/my_account/my_account.mako')
280 233 else:
281 234 return json.dumps(data)
282 235
283 def my_notifications(self):
284 c.active = 'notifications'
285 return render('admin/my_account/my_account.mako')
286 236
287 @auth.CSRFRequired()
288 @jsonify
289 def my_notifications_toggle_visibility(self):
290 user = c.rhodecode_user.get_instance()
291 new_status = not user.user_data.get('notification_status', True)
292 user.update_userdata(notification_status=new_status)
293 Session().commit()
294 return user.user_data['notification_status']
@@ -1,139 +1,147 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('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
22 22 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
23 23 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
24 24 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
25 25 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
26 26 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']);
27 27 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
28 28 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
29 29 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
30 30 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
31 31 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
32 32 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
33 33 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
34 34 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
35 35 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
36 36 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
37 37 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
38 38 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
39 39 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
40 40 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
41 41 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
42 42 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 43 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
44 44 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
46 46 pyroutes.register('favicon', '/favicon.ico', []);
47 47 pyroutes.register('robots', '/robots.txt', []);
48 48 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
49 49 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
50 50 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
51 51 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
52 52 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
53 53 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
54 54 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
55 55 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
56 56 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
57 57 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
58 58 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
59 59 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
60 60 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
61 61 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
62 62 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
63 63 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
64 64 pyroutes.register('ops_ping', '_admin/ops/ping', []);
65 65 pyroutes.register('admin_home', '/_admin', []);
66 66 pyroutes.register('admin_audit_logs', '_admin/audit_logs', []);
67 67 pyroutes.register('pull_requests_global_0', '_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
68 68 pyroutes.register('pull_requests_global_1', '_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
69 69 pyroutes.register('pull_requests_global', '_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
70 70 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
71 71 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
72 72 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
73 73 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
74 74 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
75 75 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
76 76 pyroutes.register('users', '_admin/users', []);
77 77 pyroutes.register('users_data', '_admin/users_data', []);
78 78 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 79 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 80 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
81 81 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
82 82 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
83 83 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
84 84 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
85 85 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
86 86 pyroutes.register('channelstream_proxy', '/_channelstream', []);
87 87 pyroutes.register('login', '/_admin/login', []);
88 88 pyroutes.register('logout', '/_admin/logout', []);
89 89 pyroutes.register('register', '/_admin/register', []);
90 90 pyroutes.register('reset_password', '/_admin/password_reset', []);
91 91 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
92 92 pyroutes.register('home', '/', []);
93 93 pyroutes.register('user_autocomplete_data', '/_users', []);
94 94 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
95 95 pyroutes.register('repo_list_data', '/_repos', []);
96 96 pyroutes.register('goto_switcher_data', '/_goto_data', []);
97 97 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
98 98 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
99 99 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
100 100 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
101 101 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
102 102 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
103 103 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
104 104 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
105 105 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
106 106 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
107 107 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
108 108 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
109 109 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
110 110 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
111 111 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
112 112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 113 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
114 114 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
115 115 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
116 116 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
117 117 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
118 118 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
119 119 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
120 120 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
121 121 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
122 122 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
123 123 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
124 124 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
125 125 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
126 126 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
127 127 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
128 128 pyroutes.register('search', '/_admin/search', []);
129 129 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
130 130 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
131 131 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
132 132 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
133 133 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
134 134 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
135 135 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
136 136 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
137 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
138 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
139 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
140 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
141 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
142 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
143 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
144 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
137 145 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
138 146 pyroutes.register('apiv2', '/_admin/api', []);
139 147 }
@@ -1,52 +1,52 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('My Account')}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='my_account')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24
25 25 <div class="sidebar-col-wrapper scw-small">
26 26 ##main
27 27 <div class="sidebar">
28 28 <ul class="nav nav-pills nav-stacked">
29 29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
30 30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
31 31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 32 ## TODO: Find a better integration of oauth views into navigation.
33 33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 34 % if my_account_oauth_url:
35 35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 36 % endif
37 37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
40 40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('Live Notifications')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 43 </ul>
44 44 </div>
45 45
46 46 <div class="main-content-full-width">
47 47 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
48 48 </div>
49 49 </div>
50 50 </div>
51 51
52 52 </%def>
@@ -1,130 +1,130 b''
1 1 <template is="dom-bind" id="notificationsPage">
2 2 <iron-ajax id="toggleNotifications"
3 3 method="post"
4 url="${url('my_account_notifications_toggle_visibility')}"
4 url="${h.route_path('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 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>
@@ -1,203 +1,181 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 import pytest
22 22
23 23 from rhodecode.lib import helpers as h
24 24 from rhodecode.model.db import User, UserFollowing, Repository
25 25 from rhodecode.tests import (
26 26 TestController, url, TEST_USER_ADMIN_LOGIN, assert_session_flash)
27 27 from rhodecode.tests.fixture import Fixture
28 28 from rhodecode.tests.utils import AssertResponse
29 29
30 30 fixture = Fixture()
31 31
32 32
33 33 def route_path(name, **kwargs):
34 34 return {
35 35 'home': '/',
36 36 }[name].format(**kwargs)
37 37
38 38
39 39 class TestMyAccountController(TestController):
40 40 test_user_1 = 'testme'
41 41 test_user_1_password = '0jd83nHNS/d23n'
42 42 destroy_users = set()
43 43
44 44 @classmethod
45 45 def teardown_class(cls):
46 46 fixture.destroy_users(cls.destroy_users)
47 47
48 48 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
49 49 response = self.app.get(route_path('home'))
50 50 assert_response = AssertResponse(response)
51 51 element = assert_response.get_element('.logout #csrf_token')
52 52 assert element.value == csrf_token
53 53
54 54 def test_my_account_edit(self):
55 55 self.log_user()
56 56 response = self.app.get(url('my_account_edit'))
57 57
58 58 response.mustcontain('value="test_admin')
59 59
60 def test_my_account_my_repos(self):
61 self.log_user()
62 response = self.app.get(url('my_account_repos'))
63 repos = Repository.query().filter(
64 Repository.user == User.get_by_username(
65 TEST_USER_ADMIN_LOGIN)).all()
66 for repo in repos:
67 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
68
69 def test_my_account_my_watched(self):
70 self.log_user()
71 response = self.app.get(url('my_account_watched'))
72
73 repos = UserFollowing.query().filter(
74 UserFollowing.user == User.get_by_username(
75 TEST_USER_ADMIN_LOGIN)).all()
76 for repo in repos:
77 response.mustcontain(
78 '"name_raw": "%s"' % repo.follows_repository.repo_name)
79
80 60 @pytest.mark.backends("git", "hg")
81 61 def test_my_account_my_pullrequests(self, pr_util):
82 62 self.log_user()
83 63 response = self.app.get(url('my_account_pullrequests'))
84 64 response.mustcontain('There are currently no open pull '
85 65 'requests requiring your participation.')
86 66
87 67 pr = pr_util.create_pull_request(title='TestMyAccountPR')
88 68 response = self.app.get(url('my_account_pullrequests'))
89 69 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
90 70 response.mustcontain('TestMyAccountPR')
91 71
92
93
94 72 @pytest.mark.parametrize(
95 73 "name, attrs", [
96 74 ('firstname', {'firstname': 'new_username'}),
97 75 ('lastname', {'lastname': 'new_username'}),
98 76 ('admin', {'admin': True}),
99 77 ('admin', {'admin': False}),
100 78 ('extern_type', {'extern_type': 'ldap'}),
101 79 ('extern_type', {'extern_type': None}),
102 80 # ('extern_name', {'extern_name': 'test'}),
103 81 # ('extern_name', {'extern_name': None}),
104 82 ('active', {'active': False}),
105 83 ('active', {'active': True}),
106 84 ('email', {'email': 'some@email.com'}),
107 85 ])
108 86 def test_my_account_update(self, name, attrs):
109 87 usr = fixture.create_user(self.test_user_1,
110 88 password=self.test_user_1_password,
111 89 email='testme@rhodecode.org',
112 90 extern_type='rhodecode',
113 91 extern_name=self.test_user_1,
114 92 skip_if_exists=True)
115 93 self.destroy_users.add(self.test_user_1)
116 94
117 95 params = usr.get_api_data() # current user data
118 96 user_id = usr.user_id
119 97 self.log_user(
120 98 username=self.test_user_1, password=self.test_user_1_password)
121 99
122 100 params.update({'password_confirmation': ''})
123 101 params.update({'new_password': ''})
124 102 params.update({'extern_type': 'rhodecode'})
125 103 params.update({'extern_name': self.test_user_1})
126 104 params.update({'csrf_token': self.csrf_token})
127 105
128 106 params.update(attrs)
129 107 # my account page cannot set language param yet, only for admins
130 108 del params['language']
131 109 response = self.app.post(url('my_account'), params)
132 110
133 111 assert_session_flash(
134 112 response, 'Your account was updated successfully')
135 113
136 114 del params['csrf_token']
137 115
138 116 updated_user = User.get_by_username(self.test_user_1)
139 117 updated_params = updated_user.get_api_data()
140 118 updated_params.update({'password_confirmation': ''})
141 119 updated_params.update({'new_password': ''})
142 120
143 121 params['last_login'] = updated_params['last_login']
144 122 params['last_activity'] = updated_params['last_activity']
145 123 # my account page cannot set language param yet, only for admins
146 124 # but we get this info from API anyway
147 125 params['language'] = updated_params['language']
148 126
149 127 if name == 'email':
150 128 params['emails'] = [attrs['email']]
151 129 if name == 'extern_type':
152 130 # cannot update this via form, expected value is original one
153 131 params['extern_type'] = "rhodecode"
154 132 if name == 'extern_name':
155 133 # cannot update this via form, expected value is original one
156 134 params['extern_name'] = str(user_id)
157 135 if name == 'active':
158 136 # my account cannot deactivate account
159 137 params['active'] = True
160 138 if name == 'admin':
161 139 # my account cannot make you an admin !
162 140 params['admin'] = False
163 141
164 142 assert params == updated_params
165 143
166 144 def test_my_account_update_err_email_exists(self):
167 145 self.log_user()
168 146
169 147 new_email = 'test_regular@mail.com' # already exisitn email
170 148 response = self.app.post(url('my_account'),
171 149 params={
172 150 'username': 'test_admin',
173 151 'new_password': 'test12',
174 152 'password_confirmation': 'test122',
175 153 'firstname': 'NewName',
176 154 'lastname': 'NewLastname',
177 155 'email': new_email,
178 156 'csrf_token': self.csrf_token,
179 157 })
180 158
181 159 response.mustcontain('This e-mail address is already taken')
182 160
183 161 def test_my_account_update_err(self):
184 162 self.log_user('test_regular2', 'test12')
185 163
186 164 new_email = 'newmail.pl'
187 165 response = self.app.post(url('my_account'),
188 166 params={
189 167 'username': 'test_admin',
190 168 'new_password': 'test12',
191 169 'password_confirmation': 'test122',
192 170 'firstname': 'NewName',
193 171 'lastname': 'NewLastname',
194 172 'email': new_email,
195 173 'csrf_token': self.csrf_token,
196 174 })
197 175
198 176 response.mustcontain('An email address must contain a single @')
199 177 from rhodecode.model import validators
200 178 msg = validators.ValidUsername(
201 179 edit=False, old_data={})._messages['username_exists']
202 180 msg = h.html_escape(msg % {'username': 'test_admin'})
203 181 response.mustcontain(u"%s" % msg)
General Comments 0
You need to be logged in to leave comments. Login now