##// END OF EJS Templates
my-account: moved profile page into pyramid.
marcink -
r1540:e835784f default
parent child Browse files
Show More
@@ -0,0 +1,55 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.tests import (
25 TestController, TEST_USER_ADMIN_LOGIN,
26 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
27 from rhodecode.tests.fixture import Fixture
28
29 fixture = Fixture()
30
31
32 def route_path(name, **kwargs):
33 return {
34 'my_account':
35 ADMIN_PREFIX + '/my_account/profile',
36 }[name].format(**kwargs)
37
38
39 class TestMyAccountProfile(TestController):
40
41 def test_my_account(self):
42 self.log_user()
43 response = self.app.get(route_path('my_account'))
44
45 response.mustcontain(TEST_USER_ADMIN_LOGIN)
46 response.mustcontain('href="/_admin/my_account/edit"')
47 response.mustcontain('Photo')
48
49 def test_my_account_regular_user(self):
50 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
51 response = self.app.get(route_path('my_account'))
52
53 response.mustcontain(TEST_USER_REGULAR_LOGIN)
54 response.mustcontain('href="/_admin/my_account/edit"')
55 response.mustcontain('Photo')
@@ -1,48 +1,52 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 name='my_account_profile',
29 pattern=ADMIN_PREFIX + '/my_account/profile')
30
31 config.add_route(
28 32 name='my_account_password',
29 33 pattern=ADMIN_PREFIX + '/my_account/password')
30 34
31 35 config.add_route(
32 36 name='my_account_password_update',
33 37 pattern=ADMIN_PREFIX + '/my_account/password')
34 38
35 39 config.add_route(
36 40 name='my_account_auth_tokens',
37 41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
38 42 config.add_route(
39 43 name='my_account_auth_tokens_add',
40 44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
41 45 )
42 46 config.add_route(
43 47 name='my_account_auth_tokens_delete',
44 48 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
45 49 )
46 50
47 51 # Scan module for configuration decorators.
48 52 config.scan()
@@ -1,184 +1,194 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
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode import forms
28 28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.utils2 import safe_int, md5
31 31 from rhodecode.model.auth_token import AuthTokenModel
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.user import UserModel
34 34 from rhodecode.model.validation_schema.schemas import user_schema
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class MyAccountView(BaseAppView):
40 40 ALLOW_SCOPED_TOKENS = False
41 41 """
42 42 This view has alternative version inside EE, if modified please take a look
43 43 in there as well.
44 44 """
45 45
46 46 def load_default_context(self):
47 47 c = self._get_local_tmpl_context()
48 48 c.user = c.auth_user.get_instance()
49 49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 50 self._register_global_c(c)
51 51 return c
52 52
53 53 @LoginRequired()
54 54 @NotAnonymous()
55 55 @view_config(
56 route_name='my_account_profile', request_method='GET',
57 renderer='rhodecode:templates/admin/my_account/my_account.mako')
58 def my_account_profile(self):
59 c = self.load_default_context()
60 c.active = 'profile'
61 return self._get_template_context(c)
62
63 @LoginRequired()
64 @NotAnonymous()
65 @view_config(
56 66 route_name='my_account_password', request_method='GET',
57 67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
58 68 def my_account_password(self):
59 69 c = self.load_default_context()
60 70 c.active = 'password'
61 71 c.extern_type = c.user.extern_type
62 72
63 73 schema = user_schema.ChangePasswordSchema().bind(
64 74 username=c.user.username)
65 75
66 76 form = forms.Form(
67 77 schema, buttons=(forms.buttons.save, forms.buttons.reset))
68 78
69 79 c.form = form
70 80 return self._get_template_context(c)
71 81
72 82 @LoginRequired()
73 83 @NotAnonymous()
74 84 @CSRFRequired()
75 85 @view_config(
76 86 route_name='my_account_password', request_method='POST',
77 87 renderer='rhodecode:templates/admin/my_account/my_account.mako')
78 88 def my_account_password_update(self):
79 89 _ = self.request.translate
80 90 c = self.load_default_context()
81 91 c.active = 'password'
82 92 c.extern_type = c.user.extern_type
83 93
84 94 schema = user_schema.ChangePasswordSchema().bind(
85 95 username=c.user.username)
86 96
87 97 form = forms.Form(
88 98 schema, buttons=(forms.buttons.save, forms.buttons.reset))
89 99
90 100 if c.extern_type != 'rhodecode':
91 101 raise HTTPFound(self.request.route_path('my_account_password'))
92 102
93 103 controls = self.request.POST.items()
94 104 try:
95 105 valid_data = form.validate(controls)
96 106 UserModel().update_user(c.user.user_id, **valid_data)
97 107 c.user.update_userdata(force_password_change=False)
98 108 Session().commit()
99 109 except forms.ValidationFailure as e:
100 110 c.form = e
101 111 return self._get_template_context(c)
102 112
103 113 except Exception:
104 114 log.exception("Exception updating password")
105 115 h.flash(_('Error occurred during update of user password'),
106 116 category='error')
107 117 else:
108 118 instance = c.auth_user.get_instance()
109 119 self.session.setdefault('rhodecode_user', {}).update(
110 120 {'password': md5(instance.password)})
111 121 self.session.save()
112 122 h.flash(_("Successfully updated password"), category='success')
113 123
114 124 raise HTTPFound(self.request.route_path('my_account_password'))
115 125
116 126 @LoginRequired()
117 127 @NotAnonymous()
118 128 @view_config(
119 129 route_name='my_account_auth_tokens', request_method='GET',
120 130 renderer='rhodecode:templates/admin/my_account/my_account.mako')
121 131 def my_account_auth_tokens(self):
122 132 _ = self.request.translate
123 133
124 134 c = self.load_default_context()
125 135 c.active = 'auth_tokens'
126 136
127 137 c.lifetime_values = [
128 138 (str(-1), _('forever')),
129 139 (str(5), _('5 minutes')),
130 140 (str(60), _('1 hour')),
131 141 (str(60 * 24), _('1 day')),
132 142 (str(60 * 24 * 30), _('1 month')),
133 143 ]
134 144 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
135 145 c.role_values = [
136 146 (x, AuthTokenModel.cls._get_role_name(x))
137 147 for x in AuthTokenModel.cls.ROLES]
138 148 c.role_options = [(c.role_values, _("Role"))]
139 149 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
140 150 c.user.user_id, show_expired=True)
141 151 return self._get_template_context(c)
142 152
143 153 def maybe_attach_token_scope(self, token):
144 154 # implemented in EE edition
145 155 pass
146 156
147 157 @LoginRequired()
148 158 @NotAnonymous()
149 159 @CSRFRequired()
150 160 @view_config(
151 161 route_name='my_account_auth_tokens_add', request_method='POST')
152 162 def my_account_auth_tokens_add(self):
153 163 _ = self.request.translate
154 164 c = self.load_default_context()
155 165
156 166 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
157 167 description = self.request.POST.get('description')
158 168 role = self.request.POST.get('role')
159 169
160 170 token = AuthTokenModel().create(
161 171 c.user.user_id, description, lifetime, role)
162 172 self.maybe_attach_token_scope(token)
163 173 Session().commit()
164 174
165 175 h.flash(_("Auth token successfully created"), category='success')
166 176 return HTTPFound(h.route_path('my_account_auth_tokens'))
167 177
168 178 @LoginRequired()
169 179 @NotAnonymous()
170 180 @CSRFRequired()
171 181 @view_config(
172 182 route_name='my_account_auth_tokens_delete', request_method='POST')
173 183 def my_account_auth_tokens_delete(self):
174 184 _ = self.request.translate
175 185 c = self.load_default_context()
176 186
177 187 del_auth_token = self.request.POST.get('del_auth_token')
178 188
179 189 if del_auth_token:
180 190 AuthTokenModel().delete(del_auth_token, c.user.user_id)
181 191 Session().commit()
182 192 h.flash(_("Auth token successfully deleted"), category='success')
183 193
184 194 return HTTPFound(h.route_path('my_account_auth_tokens'))
@@ -1,1151 +1,1149 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 from rhodecode.config import routing_links
36 36
37 37 # prefix for non repository related links needs to be prefixed with `/`
38 38 ADMIN_PREFIX = '/_admin'
39 39 STATIC_FILE_PREFIX = '/_static'
40 40
41 41 # Default requirements for URL parts
42 42 URL_NAME_REQUIREMENTS = {
43 43 # group name can have a slash in them, but they must not end with a slash
44 44 'group_name': r'.*?[^/]',
45 45 'repo_group_name': r'.*?[^/]',
46 46 # repo names can have a slash in them, but they must not end with a slash
47 47 'repo_name': r'.*?[^/]',
48 48 # file path eats up everything at the end
49 49 'f_path': r'.*',
50 50 # reference types
51 51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 53 }
54 54
55 55
56 56 def add_route_requirements(route_path, requirements):
57 57 """
58 58 Adds regex requirements to pyramid routes using a mapping dict
59 59
60 60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 61 '/{action}/{id:\d+}'
62 62
63 63 """
64 64 for key, regex in requirements.items():
65 65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 66 return route_path
67 67
68 68
69 69 class JSRoutesMapper(Mapper):
70 70 """
71 71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 72 """
73 73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 75 def __init__(self, *args, **kw):
76 76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 77 self._jsroutes = []
78 78
79 79 def connect(self, *args, **kw):
80 80 """
81 81 Wrapper for connect to take an extra argument jsroute=True
82 82
83 83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 84 """
85 85 if kw.pop('jsroute', False):
86 86 if not self._named_route_regex.match(args[0]):
87 87 raise Exception('only named routes can be added to pyroutes')
88 88 self._jsroutes.append(args[0])
89 89
90 90 super(JSRoutesMapper, self).connect(*args, **kw)
91 91
92 92 def _extract_route_information(self, route):
93 93 """
94 94 Convert a route into tuple(name, path, args), eg:
95 95 ('show_user', '/profile/%(username)s', ['username'])
96 96 """
97 97 routepath = route.routepath
98 98 def replace(matchobj):
99 99 if matchobj.group(1):
100 100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 101 else:
102 102 return "%%(%s)s" % matchobj.group(2)
103 103
104 104 routepath = self._argument_prog.sub(replace, routepath)
105 105 return (
106 106 route.name,
107 107 routepath,
108 108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 109 for arg in self._argument_prog.findall(route.routepath)]
110 110 )
111 111
112 112 def jsroutes(self):
113 113 """
114 114 Return a list of pyroutes.js compatible routes
115 115 """
116 116 for route_name in self._jsroutes:
117 117 yield self._extract_route_information(self._routenames[route_name])
118 118
119 119
120 120 def make_map(config):
121 121 """Create, configure and return the routes Mapper"""
122 122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 123 always_scan=config['debug'])
124 124 rmap.minimization = False
125 125 rmap.explicit = False
126 126
127 127 from rhodecode.lib.utils2 import str2bool
128 128 from rhodecode.model import repo, repo_group
129 129
130 130 def check_repo(environ, match_dict):
131 131 """
132 132 check for valid repository for proper 404 handling
133 133
134 134 :param environ:
135 135 :param match_dict:
136 136 """
137 137 repo_name = match_dict.get('repo_name')
138 138
139 139 if match_dict.get('f_path'):
140 140 # fix for multiple initial slashes that causes errors
141 141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 142 repo_model = repo.RepoModel()
143 143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 144 # if we match quickly from database, short circuit the operation,
145 145 # and validate repo based on the type.
146 146 if by_name_match:
147 147 return True
148 148
149 149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 150 if by_id_match:
151 151 repo_name = by_id_match.repo_name
152 152 match_dict['repo_name'] = repo_name
153 153 return True
154 154
155 155 return False
156 156
157 157 def check_group(environ, match_dict):
158 158 """
159 159 check for valid repository group path for proper 404 handling
160 160
161 161 :param environ:
162 162 :param match_dict:
163 163 """
164 164 repo_group_name = match_dict.get('group_name')
165 165 repo_group_model = repo_group.RepoGroupModel()
166 166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 167 if by_name_match:
168 168 return True
169 169
170 170 return False
171 171
172 172 def check_user_group(environ, match_dict):
173 173 """
174 174 check for valid user group for proper 404 handling
175 175
176 176 :param environ:
177 177 :param match_dict:
178 178 """
179 179 return True
180 180
181 181 def check_int(environ, match_dict):
182 182 return match_dict.get('id').isdigit()
183 183
184 184
185 185 #==========================================================================
186 186 # CUSTOM ROUTES HERE
187 187 #==========================================================================
188 188
189 189 # MAIN PAGE
190 190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 192 action='goto_switcher_data')
193 193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 194 action='repo_list_data')
195 195
196 196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 197 action='user_autocomplete_data', jsroute=True)
198 198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 199 action='user_group_autocomplete_data', jsroute=True)
200 200
201 201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
202 202 rmap.connect('rst_help',
203 203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
204 204 _static=True)
205 205 rmap.connect('markdown_help',
206 206 'http://daringfireball.net/projects/markdown/syntax',
207 207 _static=True)
208 208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
209 209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
210 210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
211 211 # TODO: anderson - making this a static link since redirect won't play
212 212 # nice with POST requests
213 213 rmap.connect('enterprise_license_convert_from_old',
214 214 'https://rhodecode.com/u/license-upgrade',
215 215 _static=True)
216 216
217 217 routing_links.connect_redirection_links(rmap)
218 218
219 219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
221 221
222 222 # ADMIN REPOSITORY ROUTES
223 223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 224 controller='admin/repos') as m:
225 225 m.connect('repos', '/repos',
226 226 action='create', conditions={'method': ['POST']})
227 227 m.connect('repos', '/repos',
228 228 action='index', conditions={'method': ['GET']})
229 229 m.connect('new_repo', '/create_repository', jsroute=True,
230 230 action='create_repository', conditions={'method': ['GET']})
231 231 m.connect('/repos/{repo_name}',
232 232 action='update', conditions={'method': ['PUT'],
233 233 'function': check_repo},
234 234 requirements=URL_NAME_REQUIREMENTS)
235 235 m.connect('delete_repo', '/repos/{repo_name}',
236 236 action='delete', conditions={'method': ['DELETE']},
237 237 requirements=URL_NAME_REQUIREMENTS)
238 238 m.connect('repo', '/repos/{repo_name}',
239 239 action='show', conditions={'method': ['GET'],
240 240 'function': check_repo},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242
243 243 # ADMIN REPOSITORY GROUPS ROUTES
244 244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 245 controller='admin/repo_groups') as m:
246 246 m.connect('repo_groups', '/repo_groups',
247 247 action='create', conditions={'method': ['POST']})
248 248 m.connect('repo_groups', '/repo_groups',
249 249 action='index', conditions={'method': ['GET']})
250 250 m.connect('new_repo_group', '/repo_groups/new',
251 251 action='new', conditions={'method': ['GET']})
252 252 m.connect('update_repo_group', '/repo_groups/{group_name}',
253 253 action='update', conditions={'method': ['PUT'],
254 254 'function': check_group},
255 255 requirements=URL_NAME_REQUIREMENTS)
256 256
257 257 # EXTRAS REPO GROUP ROUTES
258 258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
259 259 action='edit',
260 260 conditions={'method': ['GET'], 'function': check_group},
261 261 requirements=URL_NAME_REQUIREMENTS)
262 262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 263 action='edit',
264 264 conditions={'method': ['PUT'], 'function': check_group},
265 265 requirements=URL_NAME_REQUIREMENTS)
266 266
267 267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
268 268 action='edit_repo_group_advanced',
269 269 conditions={'method': ['GET'], 'function': check_group},
270 270 requirements=URL_NAME_REQUIREMENTS)
271 271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 272 action='edit_repo_group_advanced',
273 273 conditions={'method': ['PUT'], 'function': check_group},
274 274 requirements=URL_NAME_REQUIREMENTS)
275 275
276 276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
277 277 action='edit_repo_group_perms',
278 278 conditions={'method': ['GET'], 'function': check_group},
279 279 requirements=URL_NAME_REQUIREMENTS)
280 280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 281 action='update_perms',
282 282 conditions={'method': ['PUT'], 'function': check_group},
283 283 requirements=URL_NAME_REQUIREMENTS)
284 284
285 285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
286 286 action='delete', conditions={'method': ['DELETE'],
287 287 'function': check_group},
288 288 requirements=URL_NAME_REQUIREMENTS)
289 289
290 290 # ADMIN USER ROUTES
291 291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
292 292 controller='admin/users') as m:
293 293 m.connect('users', '/users',
294 294 action='create', conditions={'method': ['POST']})
295 295 m.connect('new_user', '/users/new',
296 296 action='new', conditions={'method': ['GET']})
297 297 m.connect('update_user', '/users/{user_id}',
298 298 action='update', conditions={'method': ['PUT']})
299 299 m.connect('delete_user', '/users/{user_id}',
300 300 action='delete', conditions={'method': ['DELETE']})
301 301 m.connect('edit_user', '/users/{user_id}/edit',
302 302 action='edit', conditions={'method': ['GET']}, jsroute=True)
303 303 m.connect('user', '/users/{user_id}',
304 304 action='show', conditions={'method': ['GET']})
305 305 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
306 306 action='reset_password', conditions={'method': ['POST']})
307 307 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
308 308 action='create_personal_repo_group', conditions={'method': ['POST']})
309 309
310 310 # EXTRAS USER ROUTES
311 311 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
312 312 action='edit_advanced', conditions={'method': ['GET']})
313 313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
314 314 action='update_advanced', conditions={'method': ['PUT']})
315 315
316 316 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
317 317 action='edit_global_perms', conditions={'method': ['GET']})
318 318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
319 319 action='update_global_perms', conditions={'method': ['PUT']})
320 320
321 321 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
322 322 action='edit_perms_summary', conditions={'method': ['GET']})
323 323
324 324 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
325 325 action='edit_emails', conditions={'method': ['GET']})
326 326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
327 327 action='add_email', conditions={'method': ['PUT']})
328 328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
329 329 action='delete_email', conditions={'method': ['DELETE']})
330 330
331 331 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
332 332 action='edit_ips', conditions={'method': ['GET']})
333 333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
334 334 action='add_ip', conditions={'method': ['PUT']})
335 335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
336 336 action='delete_ip', conditions={'method': ['DELETE']})
337 337
338 338 # ADMIN USER GROUPS REST ROUTES
339 339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
340 340 controller='admin/user_groups') as m:
341 341 m.connect('users_groups', '/user_groups',
342 342 action='create', conditions={'method': ['POST']})
343 343 m.connect('users_groups', '/user_groups',
344 344 action='index', conditions={'method': ['GET']})
345 345 m.connect('new_users_group', '/user_groups/new',
346 346 action='new', conditions={'method': ['GET']})
347 347 m.connect('update_users_group', '/user_groups/{user_group_id}',
348 348 action='update', conditions={'method': ['PUT']})
349 349 m.connect('delete_users_group', '/user_groups/{user_group_id}',
350 350 action='delete', conditions={'method': ['DELETE']})
351 351 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
352 352 action='edit', conditions={'method': ['GET']},
353 353 function=check_user_group)
354 354
355 355 # EXTRAS USER GROUP ROUTES
356 356 m.connect('edit_user_group_global_perms',
357 357 '/user_groups/{user_group_id}/edit/global_permissions',
358 358 action='edit_global_perms', conditions={'method': ['GET']})
359 359 m.connect('edit_user_group_global_perms',
360 360 '/user_groups/{user_group_id}/edit/global_permissions',
361 361 action='update_global_perms', conditions={'method': ['PUT']})
362 362 m.connect('edit_user_group_perms_summary',
363 363 '/user_groups/{user_group_id}/edit/permissions_summary',
364 364 action='edit_perms_summary', conditions={'method': ['GET']})
365 365
366 366 m.connect('edit_user_group_perms',
367 367 '/user_groups/{user_group_id}/edit/permissions',
368 368 action='edit_perms', conditions={'method': ['GET']})
369 369 m.connect('edit_user_group_perms',
370 370 '/user_groups/{user_group_id}/edit/permissions',
371 371 action='update_perms', conditions={'method': ['PUT']})
372 372
373 373 m.connect('edit_user_group_advanced',
374 374 '/user_groups/{user_group_id}/edit/advanced',
375 375 action='edit_advanced', conditions={'method': ['GET']})
376 376
377 377 m.connect('edit_user_group_members',
378 378 '/user_groups/{user_group_id}/edit/members', jsroute=True,
379 379 action='user_group_members', conditions={'method': ['GET']})
380 380
381 381 # ADMIN PERMISSIONS ROUTES
382 382 with rmap.submapper(path_prefix=ADMIN_PREFIX,
383 383 controller='admin/permissions') as m:
384 384 m.connect('admin_permissions_application', '/permissions/application',
385 385 action='permission_application_update', conditions={'method': ['POST']})
386 386 m.connect('admin_permissions_application', '/permissions/application',
387 387 action='permission_application', conditions={'method': ['GET']})
388 388
389 389 m.connect('admin_permissions_global', '/permissions/global',
390 390 action='permission_global_update', conditions={'method': ['POST']})
391 391 m.connect('admin_permissions_global', '/permissions/global',
392 392 action='permission_global', conditions={'method': ['GET']})
393 393
394 394 m.connect('admin_permissions_object', '/permissions/object',
395 395 action='permission_objects_update', conditions={'method': ['POST']})
396 396 m.connect('admin_permissions_object', '/permissions/object',
397 397 action='permission_objects', conditions={'method': ['GET']})
398 398
399 399 m.connect('admin_permissions_ips', '/permissions/ips',
400 400 action='permission_ips', conditions={'method': ['POST']})
401 401 m.connect('admin_permissions_ips', '/permissions/ips',
402 402 action='permission_ips', conditions={'method': ['GET']})
403 403
404 404 m.connect('admin_permissions_overview', '/permissions/overview',
405 405 action='permission_perms', conditions={'method': ['GET']})
406 406
407 407 # ADMIN DEFAULTS REST ROUTES
408 408 with rmap.submapper(path_prefix=ADMIN_PREFIX,
409 409 controller='admin/defaults') as m:
410 410 m.connect('admin_defaults_repositories', '/defaults/repositories',
411 411 action='update_repository_defaults', conditions={'method': ['POST']})
412 412 m.connect('admin_defaults_repositories', '/defaults/repositories',
413 413 action='index', conditions={'method': ['GET']})
414 414
415 415 # ADMIN DEBUG STYLE ROUTES
416 416 if str2bool(config.get('debug_style')):
417 417 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
418 418 controller='debug_style') as m:
419 419 m.connect('debug_style_home', '',
420 420 action='index', conditions={'method': ['GET']})
421 421 m.connect('debug_style_template', '/t/{t_path}',
422 422 action='template', conditions={'method': ['GET']})
423 423
424 424 # ADMIN SETTINGS ROUTES
425 425 with rmap.submapper(path_prefix=ADMIN_PREFIX,
426 426 controller='admin/settings') as m:
427 427
428 428 # default
429 429 m.connect('admin_settings', '/settings',
430 430 action='settings_global_update',
431 431 conditions={'method': ['POST']})
432 432 m.connect('admin_settings', '/settings',
433 433 action='settings_global', conditions={'method': ['GET']})
434 434
435 435 m.connect('admin_settings_vcs', '/settings/vcs',
436 436 action='settings_vcs_update',
437 437 conditions={'method': ['POST']})
438 438 m.connect('admin_settings_vcs', '/settings/vcs',
439 439 action='settings_vcs',
440 440 conditions={'method': ['GET']})
441 441 m.connect('admin_settings_vcs', '/settings/vcs',
442 442 action='delete_svn_pattern',
443 443 conditions={'method': ['DELETE']})
444 444
445 445 m.connect('admin_settings_mapping', '/settings/mapping',
446 446 action='settings_mapping_update',
447 447 conditions={'method': ['POST']})
448 448 m.connect('admin_settings_mapping', '/settings/mapping',
449 449 action='settings_mapping', conditions={'method': ['GET']})
450 450
451 451 m.connect('admin_settings_global', '/settings/global',
452 452 action='settings_global_update',
453 453 conditions={'method': ['POST']})
454 454 m.connect('admin_settings_global', '/settings/global',
455 455 action='settings_global', conditions={'method': ['GET']})
456 456
457 457 m.connect('admin_settings_visual', '/settings/visual',
458 458 action='settings_visual_update',
459 459 conditions={'method': ['POST']})
460 460 m.connect('admin_settings_visual', '/settings/visual',
461 461 action='settings_visual', conditions={'method': ['GET']})
462 462
463 463 m.connect('admin_settings_issuetracker',
464 464 '/settings/issue-tracker', action='settings_issuetracker',
465 465 conditions={'method': ['GET']})
466 466 m.connect('admin_settings_issuetracker_save',
467 467 '/settings/issue-tracker/save',
468 468 action='settings_issuetracker_save',
469 469 conditions={'method': ['POST']})
470 470 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
471 471 action='settings_issuetracker_test',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_issuetracker_delete',
474 474 '/settings/issue-tracker/delete',
475 475 action='settings_issuetracker_delete',
476 476 conditions={'method': ['DELETE']})
477 477
478 478 m.connect('admin_settings_email', '/settings/email',
479 479 action='settings_email_update',
480 480 conditions={'method': ['POST']})
481 481 m.connect('admin_settings_email', '/settings/email',
482 482 action='settings_email', conditions={'method': ['GET']})
483 483
484 484 m.connect('admin_settings_hooks', '/settings/hooks',
485 485 action='settings_hooks_update',
486 486 conditions={'method': ['POST', 'DELETE']})
487 487 m.connect('admin_settings_hooks', '/settings/hooks',
488 488 action='settings_hooks', conditions={'method': ['GET']})
489 489
490 490 m.connect('admin_settings_search', '/settings/search',
491 491 action='settings_search', conditions={'method': ['GET']})
492 492
493 493 m.connect('admin_settings_supervisor', '/settings/supervisor',
494 494 action='settings_supervisor', conditions={'method': ['GET']})
495 495 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
496 496 action='settings_supervisor_log', conditions={'method': ['GET']})
497 497
498 498 m.connect('admin_settings_labs', '/settings/labs',
499 499 action='settings_labs_update',
500 500 conditions={'method': ['POST']})
501 501 m.connect('admin_settings_labs', '/settings/labs',
502 502 action='settings_labs', conditions={'method': ['GET']})
503 503
504 504 # ADMIN MY ACCOUNT
505 505 with rmap.submapper(path_prefix=ADMIN_PREFIX,
506 506 controller='admin/my_account') as m:
507 507
508 m.connect('my_account', '/my_account',
509 action='my_account', conditions={'method': ['GET']})
510 508 m.connect('my_account_edit', '/my_account/edit',
511 509 action='my_account_edit', conditions={'method': ['GET']})
512 m.connect('my_account', '/my_account',
510 m.connect('my_account', '/my_account/update',
513 511 action='my_account_update', conditions={'method': ['POST']})
514 512
515 513 # NOTE(marcink): this needs to be kept for password force flag to be
516 514 # handler, remove after migration to pyramid
517 515 m.connect('my_account_password', '/my_account/password',
518 516 action='my_account_password', conditions={'method': ['GET']})
519 517
520 518 m.connect('my_account_repos', '/my_account/repos',
521 519 action='my_account_repos', conditions={'method': ['GET']})
522 520
523 521 m.connect('my_account_watched', '/my_account/watched',
524 522 action='my_account_watched', conditions={'method': ['GET']})
525 523
526 524 m.connect('my_account_pullrequests', '/my_account/pull_requests',
527 525 action='my_account_pullrequests', conditions={'method': ['GET']})
528 526
529 527 m.connect('my_account_perms', '/my_account/perms',
530 528 action='my_account_perms', conditions={'method': ['GET']})
531 529
532 530 m.connect('my_account_emails', '/my_account/emails',
533 531 action='my_account_emails', conditions={'method': ['GET']})
534 532 m.connect('my_account_emails', '/my_account/emails',
535 533 action='my_account_emails_add', conditions={'method': ['POST']})
536 534 m.connect('my_account_emails', '/my_account/emails',
537 535 action='my_account_emails_delete', conditions={'method': ['DELETE']})
538 536
539 537 m.connect('my_account_notifications', '/my_account/notifications',
540 538 action='my_notifications',
541 539 conditions={'method': ['GET']})
542 540 m.connect('my_account_notifications_toggle_visibility',
543 541 '/my_account/toggle_visibility',
544 542 action='my_notifications_toggle_visibility',
545 543 conditions={'method': ['POST']})
546 544 m.connect('my_account_notifications_test_channelstream',
547 545 '/my_account/test_channelstream',
548 546 action='my_account_notifications_test_channelstream',
549 547 conditions={'method': ['POST']})
550 548
551 549 # NOTIFICATION REST ROUTES
552 550 with rmap.submapper(path_prefix=ADMIN_PREFIX,
553 551 controller='admin/notifications') as m:
554 552 m.connect('notifications', '/notifications',
555 553 action='index', conditions={'method': ['GET']})
556 554 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
557 555 action='mark_all_read', conditions={'method': ['POST']})
558 556 m.connect('/notifications/{notification_id}',
559 557 action='update', conditions={'method': ['PUT']})
560 558 m.connect('/notifications/{notification_id}',
561 559 action='delete', conditions={'method': ['DELETE']})
562 560 m.connect('notification', '/notifications/{notification_id}',
563 561 action='show', conditions={'method': ['GET']})
564 562
565 563 # ADMIN GIST
566 564 with rmap.submapper(path_prefix=ADMIN_PREFIX,
567 565 controller='admin/gists') as m:
568 566 m.connect('gists', '/gists',
569 567 action='create', conditions={'method': ['POST']})
570 568 m.connect('gists', '/gists', jsroute=True,
571 569 action='index', conditions={'method': ['GET']})
572 570 m.connect('new_gist', '/gists/new', jsroute=True,
573 571 action='new', conditions={'method': ['GET']})
574 572
575 573 m.connect('/gists/{gist_id}',
576 574 action='delete', conditions={'method': ['DELETE']})
577 575 m.connect('edit_gist', '/gists/{gist_id}/edit',
578 576 action='edit_form', conditions={'method': ['GET']})
579 577 m.connect('edit_gist', '/gists/{gist_id}/edit',
580 578 action='edit', conditions={'method': ['POST']})
581 579 m.connect(
582 580 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
583 581 action='check_revision', conditions={'method': ['GET']})
584 582
585 583 m.connect('gist', '/gists/{gist_id}',
586 584 action='show', conditions={'method': ['GET']})
587 585 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
588 586 revision='tip',
589 587 action='show', conditions={'method': ['GET']})
590 588 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
591 589 revision='tip',
592 590 action='show', conditions={'method': ['GET']})
593 591 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
594 592 revision='tip',
595 593 action='show', conditions={'method': ['GET']},
596 594 requirements=URL_NAME_REQUIREMENTS)
597 595
598 596 # ADMIN MAIN PAGES
599 597 with rmap.submapper(path_prefix=ADMIN_PREFIX,
600 598 controller='admin/admin') as m:
601 599 m.connect('admin_home', '', action='index')
602 600 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
603 601 action='add_repo')
604 602 m.connect(
605 603 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
606 604 action='pull_requests')
607 605 m.connect(
608 606 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
609 607 action='pull_requests')
610 608 m.connect(
611 609 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
612 610 action='pull_requests')
613 611
614 612 # USER JOURNAL
615 613 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
616 614 controller='journal', action='index')
617 615 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
618 616 controller='journal', action='journal_rss')
619 617 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
620 618 controller='journal', action='journal_atom')
621 619
622 620 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
623 621 controller='journal', action='public_journal')
624 622
625 623 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
626 624 controller='journal', action='public_journal_rss')
627 625
628 626 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
629 627 controller='journal', action='public_journal_rss')
630 628
631 629 rmap.connect('public_journal_atom',
632 630 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
633 631 action='public_journal_atom')
634 632
635 633 rmap.connect('public_journal_atom_old',
636 634 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
637 635 action='public_journal_atom')
638 636
639 637 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
640 638 controller='journal', action='toggle_following', jsroute=True,
641 639 conditions={'method': ['POST']})
642 640
643 641 # FULL TEXT SEARCH
644 642 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
645 643 controller='search')
646 644 rmap.connect('search_repo_home', '/{repo_name}/search',
647 645 controller='search',
648 646 action='index',
649 647 conditions={'function': check_repo},
650 648 requirements=URL_NAME_REQUIREMENTS)
651 649
652 650 # FEEDS
653 651 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
654 652 controller='feed', action='rss',
655 653 conditions={'function': check_repo},
656 654 requirements=URL_NAME_REQUIREMENTS)
657 655
658 656 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
659 657 controller='feed', action='atom',
660 658 conditions={'function': check_repo},
661 659 requirements=URL_NAME_REQUIREMENTS)
662 660
663 661 #==========================================================================
664 662 # REPOSITORY ROUTES
665 663 #==========================================================================
666 664
667 665 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
668 666 controller='admin/repos', action='repo_creating',
669 667 requirements=URL_NAME_REQUIREMENTS)
670 668 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
671 669 controller='admin/repos', action='repo_check',
672 670 requirements=URL_NAME_REQUIREMENTS)
673 671
674 672 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
675 673 controller='summary', action='repo_stats',
676 674 conditions={'function': check_repo},
677 675 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
678 676
679 677 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
680 678 controller='summary', action='repo_refs_data',
681 679 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
682 680 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
683 681 controller='summary', action='repo_refs_changelog_data',
684 682 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
685 683 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
686 684 controller='summary', action='repo_default_reviewers_data',
687 685 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
688 686
689 687 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
690 688 controller='changeset', revision='tip',
691 689 conditions={'function': check_repo},
692 690 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
693 691 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
694 692 controller='changeset', revision='tip', action='changeset_children',
695 693 conditions={'function': check_repo},
696 694 requirements=URL_NAME_REQUIREMENTS)
697 695 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
698 696 controller='changeset', revision='tip', action='changeset_parents',
699 697 conditions={'function': check_repo},
700 698 requirements=URL_NAME_REQUIREMENTS)
701 699
702 700 # repo edit options
703 701 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
704 702 controller='admin/repos', action='edit',
705 703 conditions={'method': ['GET'], 'function': check_repo},
706 704 requirements=URL_NAME_REQUIREMENTS)
707 705
708 706 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
709 707 jsroute=True,
710 708 controller='admin/repos', action='edit_permissions',
711 709 conditions={'method': ['GET'], 'function': check_repo},
712 710 requirements=URL_NAME_REQUIREMENTS)
713 711 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
714 712 controller='admin/repos', action='edit_permissions_update',
715 713 conditions={'method': ['PUT'], 'function': check_repo},
716 714 requirements=URL_NAME_REQUIREMENTS)
717 715
718 716 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
719 717 controller='admin/repos', action='edit_fields',
720 718 conditions={'method': ['GET'], 'function': check_repo},
721 719 requirements=URL_NAME_REQUIREMENTS)
722 720 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
723 721 controller='admin/repos', action='create_repo_field',
724 722 conditions={'method': ['PUT'], 'function': check_repo},
725 723 requirements=URL_NAME_REQUIREMENTS)
726 724 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
727 725 controller='admin/repos', action='delete_repo_field',
728 726 conditions={'method': ['DELETE'], 'function': check_repo},
729 727 requirements=URL_NAME_REQUIREMENTS)
730 728
731 729 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
732 730 controller='admin/repos', action='edit_advanced',
733 731 conditions={'method': ['GET'], 'function': check_repo},
734 732 requirements=URL_NAME_REQUIREMENTS)
735 733
736 734 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
737 735 controller='admin/repos', action='edit_advanced_locking',
738 736 conditions={'method': ['PUT'], 'function': check_repo},
739 737 requirements=URL_NAME_REQUIREMENTS)
740 738 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
741 739 controller='admin/repos', action='toggle_locking',
742 740 conditions={'method': ['GET'], 'function': check_repo},
743 741 requirements=URL_NAME_REQUIREMENTS)
744 742
745 743 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
746 744 controller='admin/repos', action='edit_advanced_journal',
747 745 conditions={'method': ['PUT'], 'function': check_repo},
748 746 requirements=URL_NAME_REQUIREMENTS)
749 747
750 748 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
751 749 controller='admin/repos', action='edit_advanced_fork',
752 750 conditions={'method': ['PUT'], 'function': check_repo},
753 751 requirements=URL_NAME_REQUIREMENTS)
754 752
755 753 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
756 754 controller='admin/repos', action='edit_caches_form',
757 755 conditions={'method': ['GET'], 'function': check_repo},
758 756 requirements=URL_NAME_REQUIREMENTS)
759 757 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
760 758 controller='admin/repos', action='edit_caches',
761 759 conditions={'method': ['PUT'], 'function': check_repo},
762 760 requirements=URL_NAME_REQUIREMENTS)
763 761
764 762 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
765 763 controller='admin/repos', action='edit_remote_form',
766 764 conditions={'method': ['GET'], 'function': check_repo},
767 765 requirements=URL_NAME_REQUIREMENTS)
768 766 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
769 767 controller='admin/repos', action='edit_remote',
770 768 conditions={'method': ['PUT'], 'function': check_repo},
771 769 requirements=URL_NAME_REQUIREMENTS)
772 770
773 771 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
774 772 controller='admin/repos', action='edit_statistics_form',
775 773 conditions={'method': ['GET'], 'function': check_repo},
776 774 requirements=URL_NAME_REQUIREMENTS)
777 775 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
778 776 controller='admin/repos', action='edit_statistics',
779 777 conditions={'method': ['PUT'], 'function': check_repo},
780 778 requirements=URL_NAME_REQUIREMENTS)
781 779 rmap.connect('repo_settings_issuetracker',
782 780 '/{repo_name}/settings/issue-tracker',
783 781 controller='admin/repos', action='repo_issuetracker',
784 782 conditions={'method': ['GET'], 'function': check_repo},
785 783 requirements=URL_NAME_REQUIREMENTS)
786 784 rmap.connect('repo_issuetracker_test',
787 785 '/{repo_name}/settings/issue-tracker/test',
788 786 controller='admin/repos', action='repo_issuetracker_test',
789 787 conditions={'method': ['POST'], 'function': check_repo},
790 788 requirements=URL_NAME_REQUIREMENTS)
791 789 rmap.connect('repo_issuetracker_delete',
792 790 '/{repo_name}/settings/issue-tracker/delete',
793 791 controller='admin/repos', action='repo_issuetracker_delete',
794 792 conditions={'method': ['DELETE'], 'function': check_repo},
795 793 requirements=URL_NAME_REQUIREMENTS)
796 794 rmap.connect('repo_issuetracker_save',
797 795 '/{repo_name}/settings/issue-tracker/save',
798 796 controller='admin/repos', action='repo_issuetracker_save',
799 797 conditions={'method': ['POST'], 'function': check_repo},
800 798 requirements=URL_NAME_REQUIREMENTS)
801 799 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
802 800 controller='admin/repos', action='repo_settings_vcs_update',
803 801 conditions={'method': ['POST'], 'function': check_repo},
804 802 requirements=URL_NAME_REQUIREMENTS)
805 803 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
806 804 controller='admin/repos', action='repo_settings_vcs',
807 805 conditions={'method': ['GET'], 'function': check_repo},
808 806 requirements=URL_NAME_REQUIREMENTS)
809 807 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
810 808 controller='admin/repos', action='repo_delete_svn_pattern',
811 809 conditions={'method': ['DELETE'], 'function': check_repo},
812 810 requirements=URL_NAME_REQUIREMENTS)
813 811 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
814 812 controller='admin/repos', action='repo_settings_pullrequest',
815 813 conditions={'method': ['GET', 'POST'], 'function': check_repo},
816 814 requirements=URL_NAME_REQUIREMENTS)
817 815
818 816 # still working url for backward compat.
819 817 rmap.connect('raw_changeset_home_depraced',
820 818 '/{repo_name}/raw-changeset/{revision}',
821 819 controller='changeset', action='changeset_raw',
822 820 revision='tip', conditions={'function': check_repo},
823 821 requirements=URL_NAME_REQUIREMENTS)
824 822
825 823 # new URLs
826 824 rmap.connect('changeset_raw_home',
827 825 '/{repo_name}/changeset-diff/{revision}',
828 826 controller='changeset', action='changeset_raw',
829 827 revision='tip', conditions={'function': check_repo},
830 828 requirements=URL_NAME_REQUIREMENTS)
831 829
832 830 rmap.connect('changeset_patch_home',
833 831 '/{repo_name}/changeset-patch/{revision}',
834 832 controller='changeset', action='changeset_patch',
835 833 revision='tip', conditions={'function': check_repo},
836 834 requirements=URL_NAME_REQUIREMENTS)
837 835
838 836 rmap.connect('changeset_download_home',
839 837 '/{repo_name}/changeset-download/{revision}',
840 838 controller='changeset', action='changeset_download',
841 839 revision='tip', conditions={'function': check_repo},
842 840 requirements=URL_NAME_REQUIREMENTS)
843 841
844 842 rmap.connect('changeset_comment',
845 843 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
846 844 controller='changeset', revision='tip', action='comment',
847 845 conditions={'function': check_repo},
848 846 requirements=URL_NAME_REQUIREMENTS)
849 847
850 848 rmap.connect('changeset_comment_preview',
851 849 '/{repo_name}/changeset/comment/preview', jsroute=True,
852 850 controller='changeset', action='preview_comment',
853 851 conditions={'function': check_repo, 'method': ['POST']},
854 852 requirements=URL_NAME_REQUIREMENTS)
855 853
856 854 rmap.connect('changeset_comment_delete',
857 855 '/{repo_name}/changeset/comment/{comment_id}/delete',
858 856 controller='changeset', action='delete_comment',
859 857 conditions={'function': check_repo, 'method': ['DELETE']},
860 858 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
861 859
862 860 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
863 861 controller='changeset', action='changeset_info',
864 862 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
865 863
866 864 rmap.connect('compare_home',
867 865 '/{repo_name}/compare',
868 866 controller='compare', action='index',
869 867 conditions={'function': check_repo},
870 868 requirements=URL_NAME_REQUIREMENTS)
871 869
872 870 rmap.connect('compare_url',
873 871 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
874 872 controller='compare', action='compare',
875 873 conditions={'function': check_repo},
876 874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 875
878 876 rmap.connect('pullrequest_home',
879 877 '/{repo_name}/pull-request/new', controller='pullrequests',
880 878 action='index', conditions={'function': check_repo,
881 879 'method': ['GET']},
882 880 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
883 881
884 882 rmap.connect('pullrequest',
885 883 '/{repo_name}/pull-request/new', controller='pullrequests',
886 884 action='create', conditions={'function': check_repo,
887 885 'method': ['POST']},
888 886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889 887
890 888 rmap.connect('pullrequest_repo_refs',
891 889 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
892 890 controller='pullrequests',
893 891 action='get_repo_refs',
894 892 conditions={'function': check_repo, 'method': ['GET']},
895 893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
896 894
897 895 rmap.connect('pullrequest_repo_destinations',
898 896 '/{repo_name}/pull-request/repo-destinations',
899 897 controller='pullrequests',
900 898 action='get_repo_destinations',
901 899 conditions={'function': check_repo, 'method': ['GET']},
902 900 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903 901
904 902 rmap.connect('pullrequest_show',
905 903 '/{repo_name}/pull-request/{pull_request_id}',
906 904 controller='pullrequests',
907 905 action='show', conditions={'function': check_repo,
908 906 'method': ['GET']},
909 907 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
910 908
911 909 rmap.connect('pullrequest_update',
912 910 '/{repo_name}/pull-request/{pull_request_id}',
913 911 controller='pullrequests',
914 912 action='update', conditions={'function': check_repo,
915 913 'method': ['PUT']},
916 914 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
917 915
918 916 rmap.connect('pullrequest_merge',
919 917 '/{repo_name}/pull-request/{pull_request_id}',
920 918 controller='pullrequests',
921 919 action='merge', conditions={'function': check_repo,
922 920 'method': ['POST']},
923 921 requirements=URL_NAME_REQUIREMENTS)
924 922
925 923 rmap.connect('pullrequest_delete',
926 924 '/{repo_name}/pull-request/{pull_request_id}',
927 925 controller='pullrequests',
928 926 action='delete', conditions={'function': check_repo,
929 927 'method': ['DELETE']},
930 928 requirements=URL_NAME_REQUIREMENTS)
931 929
932 930 rmap.connect('pullrequest_show_all',
933 931 '/{repo_name}/pull-request',
934 932 controller='pullrequests',
935 933 action='show_all', conditions={'function': check_repo,
936 934 'method': ['GET']},
937 935 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
938 936
939 937 rmap.connect('pullrequest_comment',
940 938 '/{repo_name}/pull-request-comment/{pull_request_id}',
941 939 controller='pullrequests',
942 940 action='comment', conditions={'function': check_repo,
943 941 'method': ['POST']},
944 942 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
945 943
946 944 rmap.connect('pullrequest_comment_delete',
947 945 '/{repo_name}/pull-request-comment/{comment_id}/delete',
948 946 controller='pullrequests', action='delete_comment',
949 947 conditions={'function': check_repo, 'method': ['DELETE']},
950 948 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
951 949
952 950 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
953 951 controller='summary', conditions={'function': check_repo},
954 952 requirements=URL_NAME_REQUIREMENTS)
955 953
956 954 rmap.connect('branches_home', '/{repo_name}/branches',
957 955 controller='branches', conditions={'function': check_repo},
958 956 requirements=URL_NAME_REQUIREMENTS)
959 957
960 958 rmap.connect('tags_home', '/{repo_name}/tags',
961 959 controller='tags', conditions={'function': check_repo},
962 960 requirements=URL_NAME_REQUIREMENTS)
963 961
964 962 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
965 963 controller='bookmarks', conditions={'function': check_repo},
966 964 requirements=URL_NAME_REQUIREMENTS)
967 965
968 966 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
969 967 controller='changelog', conditions={'function': check_repo},
970 968 requirements=URL_NAME_REQUIREMENTS)
971 969
972 970 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
973 971 controller='changelog', action='changelog_summary',
974 972 conditions={'function': check_repo},
975 973 requirements=URL_NAME_REQUIREMENTS)
976 974
977 975 rmap.connect('changelog_file_home',
978 976 '/{repo_name}/changelog/{revision}/{f_path}',
979 977 controller='changelog', f_path=None,
980 978 conditions={'function': check_repo},
981 979 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
982 980
983 981 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
984 982 controller='changelog', action='changelog_elements',
985 983 conditions={'function': check_repo},
986 984 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
987 985
988 986 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
989 987 controller='files', revision='tip', f_path='',
990 988 conditions={'function': check_repo},
991 989 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
992 990
993 991 rmap.connect('files_home_simple_catchrev',
994 992 '/{repo_name}/files/{revision}',
995 993 controller='files', revision='tip', f_path='',
996 994 conditions={'function': check_repo},
997 995 requirements=URL_NAME_REQUIREMENTS)
998 996
999 997 rmap.connect('files_home_simple_catchall',
1000 998 '/{repo_name}/files',
1001 999 controller='files', revision='tip', f_path='',
1002 1000 conditions={'function': check_repo},
1003 1001 requirements=URL_NAME_REQUIREMENTS)
1004 1002
1005 1003 rmap.connect('files_history_home',
1006 1004 '/{repo_name}/history/{revision}/{f_path}',
1007 1005 controller='files', action='history', revision='tip', f_path='',
1008 1006 conditions={'function': check_repo},
1009 1007 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1010 1008
1011 1009 rmap.connect('files_authors_home',
1012 1010 '/{repo_name}/authors/{revision}/{f_path}',
1013 1011 controller='files', action='authors', revision='tip', f_path='',
1014 1012 conditions={'function': check_repo},
1015 1013 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1016 1014
1017 1015 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1018 1016 controller='files', action='diff', f_path='',
1019 1017 conditions={'function': check_repo},
1020 1018 requirements=URL_NAME_REQUIREMENTS)
1021 1019
1022 1020 rmap.connect('files_diff_2way_home',
1023 1021 '/{repo_name}/diff-2way/{f_path}',
1024 1022 controller='files', action='diff_2way', f_path='',
1025 1023 conditions={'function': check_repo},
1026 1024 requirements=URL_NAME_REQUIREMENTS)
1027 1025
1028 1026 rmap.connect('files_rawfile_home',
1029 1027 '/{repo_name}/rawfile/{revision}/{f_path}',
1030 1028 controller='files', action='rawfile', revision='tip',
1031 1029 f_path='', conditions={'function': check_repo},
1032 1030 requirements=URL_NAME_REQUIREMENTS)
1033 1031
1034 1032 rmap.connect('files_raw_home',
1035 1033 '/{repo_name}/raw/{revision}/{f_path}',
1036 1034 controller='files', action='raw', revision='tip', f_path='',
1037 1035 conditions={'function': check_repo},
1038 1036 requirements=URL_NAME_REQUIREMENTS)
1039 1037
1040 1038 rmap.connect('files_render_home',
1041 1039 '/{repo_name}/render/{revision}/{f_path}',
1042 1040 controller='files', action='index', revision='tip', f_path='',
1043 1041 rendered=True, conditions={'function': check_repo},
1044 1042 requirements=URL_NAME_REQUIREMENTS)
1045 1043
1046 1044 rmap.connect('files_annotate_home',
1047 1045 '/{repo_name}/annotate/{revision}/{f_path}',
1048 1046 controller='files', action='index', revision='tip',
1049 1047 f_path='', annotate=True, conditions={'function': check_repo},
1050 1048 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1051 1049
1052 1050 rmap.connect('files_annotate_previous',
1053 1051 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1054 1052 controller='files', action='annotate_previous', revision='tip',
1055 1053 f_path='', annotate=True, conditions={'function': check_repo},
1056 1054 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1057 1055
1058 1056 rmap.connect('files_edit',
1059 1057 '/{repo_name}/edit/{revision}/{f_path}',
1060 1058 controller='files', action='edit', revision='tip',
1061 1059 f_path='',
1062 1060 conditions={'function': check_repo, 'method': ['POST']},
1063 1061 requirements=URL_NAME_REQUIREMENTS)
1064 1062
1065 1063 rmap.connect('files_edit_home',
1066 1064 '/{repo_name}/edit/{revision}/{f_path}',
1067 1065 controller='files', action='edit_home', revision='tip',
1068 1066 f_path='', conditions={'function': check_repo},
1069 1067 requirements=URL_NAME_REQUIREMENTS)
1070 1068
1071 1069 rmap.connect('files_add',
1072 1070 '/{repo_name}/add/{revision}/{f_path}',
1073 1071 controller='files', action='add', revision='tip',
1074 1072 f_path='',
1075 1073 conditions={'function': check_repo, 'method': ['POST']},
1076 1074 requirements=URL_NAME_REQUIREMENTS)
1077 1075
1078 1076 rmap.connect('files_add_home',
1079 1077 '/{repo_name}/add/{revision}/{f_path}',
1080 1078 controller='files', action='add_home', revision='tip',
1081 1079 f_path='', conditions={'function': check_repo},
1082 1080 requirements=URL_NAME_REQUIREMENTS)
1083 1081
1084 1082 rmap.connect('files_delete',
1085 1083 '/{repo_name}/delete/{revision}/{f_path}',
1086 1084 controller='files', action='delete', revision='tip',
1087 1085 f_path='',
1088 1086 conditions={'function': check_repo, 'method': ['POST']},
1089 1087 requirements=URL_NAME_REQUIREMENTS)
1090 1088
1091 1089 rmap.connect('files_delete_home',
1092 1090 '/{repo_name}/delete/{revision}/{f_path}',
1093 1091 controller='files', action='delete_home', revision='tip',
1094 1092 f_path='', conditions={'function': check_repo},
1095 1093 requirements=URL_NAME_REQUIREMENTS)
1096 1094
1097 1095 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1098 1096 controller='files', action='archivefile',
1099 1097 conditions={'function': check_repo},
1100 1098 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1101 1099
1102 1100 rmap.connect('files_nodelist_home',
1103 1101 '/{repo_name}/nodelist/{revision}/{f_path}',
1104 1102 controller='files', action='nodelist',
1105 1103 conditions={'function': check_repo},
1106 1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1107 1105
1108 1106 rmap.connect('files_nodetree_full',
1109 1107 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1110 1108 controller='files', action='nodetree_full',
1111 1109 conditions={'function': check_repo},
1112 1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1113 1111
1114 1112 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1115 1113 controller='forks', action='fork_create',
1116 1114 conditions={'function': check_repo, 'method': ['POST']},
1117 1115 requirements=URL_NAME_REQUIREMENTS)
1118 1116
1119 1117 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1120 1118 controller='forks', action='fork',
1121 1119 conditions={'function': check_repo},
1122 1120 requirements=URL_NAME_REQUIREMENTS)
1123 1121
1124 1122 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1125 1123 controller='forks', action='forks',
1126 1124 conditions={'function': check_repo},
1127 1125 requirements=URL_NAME_REQUIREMENTS)
1128 1126
1129 1127 # must be here for proper group/repo catching pattern
1130 1128 _connect_with_slash(
1131 1129 rmap, 'repo_group_home', '/{group_name}',
1132 1130 controller='home', action='index_repo_group',
1133 1131 conditions={'function': check_group},
1134 1132 requirements=URL_NAME_REQUIREMENTS)
1135 1133
1136 1134 # catch all, at the end
1137 1135 _connect_with_slash(
1138 1136 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1139 1137 controller='summary', action='index',
1140 1138 conditions={'function': check_repo},
1141 1139 requirements=URL_NAME_REQUIREMENTS)
1142 1140
1143 1141 return rmap
1144 1142
1145 1143
1146 1144 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1147 1145 """
1148 1146 Connect a route with an optional trailing slash in `path`.
1149 1147 """
1150 1148 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1151 1149 mapper.connect(name, path, *args, **kwargs)
@@ -1,375 +1,364 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 import datetime
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pyramid.httpexceptions import HTTPFound
33
34 from pylons import request, tmpl_context as c, url
33 35 from pylons.controllers.util import redirect
34 36 from pylons.i18n.translation import _
35 37 from sqlalchemy.orm import joinedload
36 38
37 39 from rhodecode.lib import helpers as h
38 40 from rhodecode.lib import auth
39 41 from rhodecode.lib.auth import (
40 42 LoginRequired, NotAnonymous, AuthUser)
41 43 from rhodecode.lib.base import BaseController, render
42 44 from rhodecode.lib.utils import jsonify
43 45 from rhodecode.lib.utils2 import safe_int, str2bool
44 46 from rhodecode.lib.ext_json import json
45 47 from rhodecode.lib.channelstream import channelstream_request, \
46 48 ChannelstreamException
47 49
48 50 from rhodecode.model.db import (
49 51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
50 52 from rhodecode.model.forms import UserForm
51 53 from rhodecode.model.scm import RepoList
52 54 from rhodecode.model.user import UserModel
53 55 from rhodecode.model.repo import RepoModel
54 56 from rhodecode.model.meta import Session
55 57 from rhodecode.model.pull_request import PullRequestModel
56 58 from rhodecode.model.comment import CommentsModel
57 59
58 60 log = logging.getLogger(__name__)
59 61
60 62
61 63 class MyAccountController(BaseController):
62 64 """REST Controller styled on the Atom Publishing Protocol"""
63 65 # To properly map this controller, ensure your config/routing.py
64 66 # file has a resource setup:
65 67 # map.resource('setting', 'settings', controller='admin/settings',
66 68 # path_prefix='/admin', name_prefix='admin_')
67 69
68 70 @LoginRequired()
69 71 @NotAnonymous()
70 72 def __before__(self):
71 73 super(MyAccountController, self).__before__()
72 74
73 75 def __load_data(self):
74 76 c.user = User.get(c.rhodecode_user.user_id)
75 77 if c.user.username == User.DEFAULT_USER:
76 78 h.flash(_("You can't edit this user since it's"
77 79 " crucial for entire application"), category='warning')
78 80 return redirect(h.route_path('users'))
79 81
80 82 c.auth_user = AuthUser(
81 83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
82 84
83 85 def _load_my_repos_data(self, watched=False):
84 86 if watched:
85 87 admin = False
86 88 follows_repos = Session().query(UserFollowing)\
87 89 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
88 90 .options(joinedload(UserFollowing.follows_repository))\
89 91 .all()
90 92 repo_list = [x.follows_repository for x in follows_repos]
91 93 else:
92 94 admin = True
93 95 repo_list = Repository.get_all_repos(
94 96 user_id=c.rhodecode_user.user_id)
95 97 repo_list = RepoList(repo_list, perm_set=[
96 98 'repository.read', 'repository.write', 'repository.admin'])
97 99
98 100 repos_data = RepoModel().get_repos_as_dict(
99 101 repo_list=repo_list, admin=admin)
100 102 # json used to render the grid
101 103 return json.dumps(repos_data)
102 104
103 105 @auth.CSRFRequired()
104 106 def my_account_update(self):
105 107 """
106 108 POST /_admin/my_account Updates info of my account
107 109 """
108 110 # url('my_account')
109 111 c.active = 'profile_edit'
110 112 self.__load_data()
111 113 c.perm_user = c.auth_user
112 114 c.extern_type = c.user.extern_type
113 115 c.extern_name = c.user.extern_name
114 116
115 117 defaults = c.user.get_dict()
116 118 update = False
117 119 _form = UserForm(edit=True,
118 120 old_data={'user_id': c.rhodecode_user.user_id,
119 121 'email': c.rhodecode_user.email})()
120 122 form_result = {}
121 123 try:
122 124 post_data = dict(request.POST)
123 125 post_data['new_password'] = ''
124 126 post_data['password_confirmation'] = ''
125 127 form_result = _form.to_python(post_data)
126 128 # skip updating those attrs for my account
127 129 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
128 130 'new_password', 'password_confirmation']
129 131 # TODO: plugin should define if username can be updated
130 132 if c.extern_type != "rhodecode":
131 133 # forbid updating username for external accounts
132 134 skip_attrs.append('username')
133 135
134 136 UserModel().update_user(
135 137 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
136 138 h.flash(_('Your account was updated successfully'),
137 139 category='success')
138 140 Session().commit()
139 141 update = True
140 142
141 143 except formencode.Invalid as errors:
142 144 return htmlfill.render(
143 145 render('admin/my_account/my_account.mako'),
144 146 defaults=errors.value,
145 147 errors=errors.error_dict or {},
146 148 prefix_error=False,
147 149 encoding="UTF-8",
148 150 force_defaults=False)
149 151 except Exception:
150 152 log.exception("Exception updating user")
151 153 h.flash(_('Error occurred during update of user %s')
152 154 % form_result.get('username'), category='error')
153 155
154 156 if update:
155 return redirect('my_account')
157 raise HTTPFound(h.route_path('my_account_profile'))
156 158
157 159 return htmlfill.render(
158 160 render('admin/my_account/my_account.mako'),
159 161 defaults=defaults,
160 162 encoding="UTF-8",
161 163 force_defaults=False
162 164 )
163 165
164 def my_account(self):
165 """
166 GET /_admin/my_account Displays info about my account
167 """
168 # url('my_account')
169 c.active = 'profile'
170 self.__load_data()
171
172 defaults = c.user.get_dict()
173 return htmlfill.render(
174 render('admin/my_account/my_account.mako'),
175 defaults=defaults, encoding="UTF-8", force_defaults=False)
176
177 166 def my_account_edit(self):
178 167 """
179 168 GET /_admin/my_account/edit Displays edit form of my account
180 169 """
181 170 c.active = 'profile_edit'
182 171 self.__load_data()
183 172 c.perm_user = c.auth_user
184 173 c.extern_type = c.user.extern_type
185 174 c.extern_name = c.user.extern_name
186 175
187 176 defaults = c.user.get_dict()
188 177 return htmlfill.render(
189 178 render('admin/my_account/my_account.mako'),
190 179 defaults=defaults,
191 180 encoding="UTF-8",
192 181 force_defaults=False
193 182 )
194 183
195 184 def my_account_repos(self):
196 185 c.active = 'repos'
197 186 self.__load_data()
198 187
199 188 # json used to render the grid
200 189 c.data = self._load_my_repos_data()
201 190 return render('admin/my_account/my_account.mako')
202 191
203 192 def my_account_watched(self):
204 193 c.active = 'watched'
205 194 self.__load_data()
206 195
207 196 # json used to render the grid
208 197 c.data = self._load_my_repos_data(watched=True)
209 198 return render('admin/my_account/my_account.mako')
210 199
211 200 def my_account_perms(self):
212 201 c.active = 'perms'
213 202 self.__load_data()
214 203 c.perm_user = c.auth_user
215 204
216 205 return render('admin/my_account/my_account.mako')
217 206
218 207 def my_account_emails(self):
219 208 c.active = 'emails'
220 209 self.__load_data()
221 210
222 211 c.user_email_map = UserEmailMap.query()\
223 212 .filter(UserEmailMap.user == c.user).all()
224 213 return render('admin/my_account/my_account.mako')
225 214
226 215 @auth.CSRFRequired()
227 216 def my_account_emails_add(self):
228 217 email = request.POST.get('new_email')
229 218
230 219 try:
231 220 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
232 221 Session().commit()
233 222 h.flash(_("Added new email address `%s` for user account") % email,
234 223 category='success')
235 224 except formencode.Invalid as error:
236 225 msg = error.error_dict['email']
237 226 h.flash(msg, category='error')
238 227 except Exception:
239 228 log.exception("Exception in my_account_emails")
240 229 h.flash(_('An error occurred during email saving'),
241 230 category='error')
242 231 return redirect(url('my_account_emails'))
243 232
244 233 @auth.CSRFRequired()
245 234 def my_account_emails_delete(self):
246 235 email_id = request.POST.get('del_email_id')
247 236 user_model = UserModel()
248 237 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
249 238 Session().commit()
250 239 h.flash(_("Removed email address from user account"),
251 240 category='success')
252 241 return redirect(url('my_account_emails'))
253 242
254 243 def _extract_ordering(self, request):
255 244 column_index = safe_int(request.GET.get('order[0][column]'))
256 245 order_dir = request.GET.get('order[0][dir]', 'desc')
257 246 order_by = request.GET.get(
258 247 'columns[%s][data][sort]' % column_index, 'name_raw')
259 248 return order_by, order_dir
260 249
261 250 def _get_pull_requests_list(self, statuses):
262 251 start = safe_int(request.GET.get('start'), 0)
263 252 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
264 253 order_by, order_dir = self._extract_ordering(request)
265 254
266 255 pull_requests = PullRequestModel().get_im_participating_in(
267 256 user_id=c.rhodecode_user.user_id,
268 257 statuses=statuses,
269 258 offset=start, length=length, order_by=order_by,
270 259 order_dir=order_dir)
271 260
272 261 pull_requests_total_count = PullRequestModel().count_im_participating_in(
273 262 user_id=c.rhodecode_user.user_id, statuses=statuses)
274 263
275 264 from rhodecode.lib.utils import PartialRenderer
276 265 _render = PartialRenderer('data_table/_dt_elements.mako')
277 266 data = []
278 267 for pr in pull_requests:
279 268 repo_id = pr.target_repo_id
280 269 comments = CommentsModel().get_all_comments(
281 270 repo_id, pull_request=pr)
282 271 owned = pr.user_id == c.rhodecode_user.user_id
283 272 status = pr.calculated_review_status()
284 273
285 274 data.append({
286 275 'target_repo': _render('pullrequest_target_repo',
287 276 pr.target_repo.repo_name),
288 277 'name': _render('pullrequest_name',
289 278 pr.pull_request_id, pr.target_repo.repo_name,
290 279 short=True),
291 280 'name_raw': pr.pull_request_id,
292 281 'status': _render('pullrequest_status', status),
293 282 'title': _render(
294 283 'pullrequest_title', pr.title, pr.description),
295 284 'description': h.escape(pr.description),
296 285 'updated_on': _render('pullrequest_updated_on',
297 286 h.datetime_to_time(pr.updated_on)),
298 287 'updated_on_raw': h.datetime_to_time(pr.updated_on),
299 288 'created_on': _render('pullrequest_updated_on',
300 289 h.datetime_to_time(pr.created_on)),
301 290 'created_on_raw': h.datetime_to_time(pr.created_on),
302 291 'author': _render('pullrequest_author',
303 292 pr.author.full_contact, ),
304 293 'author_raw': pr.author.full_name,
305 294 'comments': _render('pullrequest_comments', len(comments)),
306 295 'comments_raw': len(comments),
307 296 'closed': pr.is_closed(),
308 297 'owned': owned
309 298 })
310 299 # json used to render the grid
311 300 data = ({
312 301 'data': data,
313 302 'recordsTotal': pull_requests_total_count,
314 303 'recordsFiltered': pull_requests_total_count,
315 304 })
316 305 return data
317 306
318 307 def my_account_pullrequests(self):
319 308 c.active = 'pullrequests'
320 309 self.__load_data()
321 310 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
322 311
323 312 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
324 313 if c.show_closed:
325 314 statuses += [PullRequest.STATUS_CLOSED]
326 315 data = self._get_pull_requests_list(statuses)
327 316 if not request.is_xhr:
328 317 c.data_participate = json.dumps(data['data'])
329 318 c.records_total_participate = data['recordsTotal']
330 319 return render('admin/my_account/my_account.mako')
331 320 else:
332 321 return json.dumps(data)
333 322
334 323 def my_notifications(self):
335 324 c.active = 'notifications'
336 325 return render('admin/my_account/my_account.mako')
337 326
338 327 @auth.CSRFRequired()
339 328 @jsonify
340 329 def my_notifications_toggle_visibility(self):
341 330 user = c.rhodecode_user.get_instance()
342 331 new_status = not user.user_data.get('notification_status', True)
343 332 user.update_userdata(notification_status=new_status)
344 333 Session().commit()
345 334 return user.user_data['notification_status']
346 335
347 336 @auth.CSRFRequired()
348 337 @jsonify
349 338 def my_account_notifications_test_channelstream(self):
350 339 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
351 340 c.rhodecode_user.username, datetime.datetime.now())
352 341 payload = {
353 342 'type': 'message',
354 343 'timestamp': datetime.datetime.utcnow(),
355 344 'user': 'system',
356 345 #'channel': 'broadcast',
357 346 'pm_users': [c.rhodecode_user.username],
358 347 'message': {
359 348 'message': message,
360 349 'level': 'info',
361 350 'topic': '/notifications'
362 351 }
363 352 }
364 353
365 354 registry = get_current_registry()
366 355 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
367 356 channelstream_config = rhodecode_plugins.get('channelstream', {})
368 357
369 358 try:
370 359 channelstream_request(channelstream_config, [payload], '/message')
371 360 except ChannelstreamException as e:
372 361 log.exception('Failed to send channelstream data')
373 362 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
374 363 return {"response": 'Channelstream data sent. '
375 364 'You should see a new live message now.'}
@@ -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 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
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.url('my_account_emails')}">${_('Emails')}</a></li>
38 38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Repositories')}</a></li>
39 39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('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 41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Permissions')}</a></li>
42 42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('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,113 +1,113 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <div class="panel panel-default user-profile">
3 3 <div class="panel-heading">
4 4 <h3 class="panel-title">${_('My Profile')}</h3>
5 <a href="${url('my_account')}" class="panel-edit">Close</a>
5 <a href="${h.route_path('my_account_profile')}" class="panel-edit">Close</a>
6 6 </div>
7 7
8 8 <div class="panel-body">
9 9 ${h.secure_form(url('my_account'), method='post', class_='form')}
10 10 <% readonly = None %>
11 11 <% disabled = "" %>
12 12
13 13 % if c.extern_type != 'rhodecode':
14 14 <% readonly = "readonly" %>
15 15 <% disabled = "disabled" %>
16 16 <div class="infoform">
17 17 <div class="fields">
18 18 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
19 19 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
20 20 </p>
21 21
22 22 <div class="field">
23 23 <div class="label">
24 24 <label for="username">${_('Username')}:</label>
25 25 </div>
26 26 <div class="input">
27 27 ${h.text('username', class_='input-valuedisplay', readonly=readonly)}
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="name">${_('First Name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('firstname', class_='input-valuedisplay', readonly=readonly)}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label">
42 42 <label for="lastname">${_('Last Name')}:</label>
43 43 </div>
44 44 <div class="input-valuedisplay">
45 45 ${h.text('lastname', class_='input-valuedisplay', readonly=readonly)}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 </div>
50 50 % else:
51 51 <div class="form">
52 52 <div class="fields">
53 53 <div class="field">
54 54 <div class="label photo">
55 55 ${_('Photo')}:
56 56 </div>
57 57 <div class="input profile">
58 58 %if c.visual.use_gravatar:
59 59 ${base.gravatar(c.user.email, 100)}
60 60 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
61 61 %else:
62 62 ${base.gravatar(c.user.email, 20)}
63 63 ${_('Avatars are disabled')}
64 64 %endif
65 65 </div>
66 66 </div>
67 67 <div class="field">
68 68 <div class="label">
69 69 <label for="username">${_('Username')}:</label>
70 70 </div>
71 71 <div class="input">
72 72 ${h.text('username', class_='medium%s' % disabled, readonly=readonly)}
73 73 ${h.hidden('extern_name', c.extern_name)}
74 74 ${h.hidden('extern_type', c.extern_type)}
75 75 </div>
76 76 </div>
77 77 <div class="field">
78 78 <div class="label">
79 79 <label for="name">${_('First Name')}:</label>
80 80 </div>
81 81 <div class="input">
82 82 ${h.text('firstname', class_="medium")}
83 83 </div>
84 84 </div>
85 85
86 86 <div class="field">
87 87 <div class="label">
88 88 <label for="lastname">${_('Last Name')}:</label>
89 89 </div>
90 90 <div class="input">
91 91 ${h.text('lastname', class_="medium")}
92 92 </div>
93 93 </div>
94 94
95 95 <div class="field">
96 96 <div class="label">
97 97 <label for="email">${_('Email')}:</label>
98 98 </div>
99 99 <div class="input">
100 100 ## we should be able to edit email !
101 101 ${h.text('email', class_="medium")}
102 102 </div>
103 103 </div>
104 104
105 105 <div class="buttons">
106 106 ${h.submit('save', _('Save'), class_="btn")}
107 107 ${h.reset('reset', _('Reset'), class_="btn")}
108 108 </div>
109 109 </div>
110 110 </div>
111 111 % endif
112 112 </div>
113 113 </div> No newline at end of file
@@ -1,601 +1,601 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${title}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 253 ${_('Compare fork')}</a></li>
254 254 %endif
255 255
256 256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 257
258 258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 259 %if c.rhodecode_db_repo.locked[0]:
260 260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 261 %else:
262 262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 263 %endif
264 264 %endif
265 265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 269 %endif
270 270 %endif
271 271 </ul>
272 272 </li>
273 273 </ul>
274 274 </div>
275 275 <div class="clear"></div>
276 276 </div>
277 277 <!--- END CONTEXT BAR -->
278 278
279 279 </%def>
280 280
281 281 <%def name="usermenu(active=False)">
282 282 ## USER MENU
283 283 <li id="quick_login_li" class="${'active' if active else ''}">
284 284 <a id="quick_login_link" class="menulink childs">
285 285 ${gravatar(c.rhodecode_user.email, 20)}
286 286 <span class="user">
287 287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 289 %else:
290 290 <span>${_('Sign in')}</span>
291 291 %endif
292 292 </span>
293 293 </a>
294 294
295 295 <div class="user-menu submenu">
296 296 <div id="quick_login">
297 297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 298 <h4>${_('Sign in to your account')}</h4>
299 299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 300 <div class="form form-vertical">
301 301 <div class="fields">
302 302 <div class="field">
303 303 <div class="label">
304 304 <label for="username">${_('Username')}:</label>
305 305 </div>
306 306 <div class="input">
307 307 ${h.text('username',class_='focus',tabindex=1)}
308 308 </div>
309 309
310 310 </div>
311 311 <div class="field">
312 312 <div class="label">
313 313 <label for="password">${_('Password')}:</label>
314 314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 316 %endif
317 317 </div>
318 318 <div class="input">
319 319 ${h.password('password',class_='focus',tabindex=2)}
320 320 </div>
321 321 </div>
322 322 <div class="buttons">
323 323 <div class="register">
324 324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
326 326 %endif
327 327 </div>
328 328 <div class="submit">
329 329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
330 330 </div>
331 331 </div>
332 332 </div>
333 333 </div>
334 334 ${h.end_form()}
335 335 %else:
336 336 <div class="">
337 337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
338 338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
339 339 <div class="email">${c.rhodecode_user.email}</div>
340 340 </div>
341 341 <div class="">
342 342 <ol class="links">
343 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 344 % if c.rhodecode_user.personal_repo_group:
345 345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 346 % endif
347 347 <li class="logout">
348 348 ${h.secure_form(h.route_path('logout'))}
349 349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
350 350 ${h.end_form()}
351 351 </li>
352 352 </ol>
353 353 </div>
354 354 %endif
355 355 </div>
356 356 </div>
357 357 %if c.rhodecode_user.username != h.DEFAULT_USER:
358 358 <div class="pill_container">
359 359 % if c.unread_notifications == 0:
360 360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
361 361 % else:
362 362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
363 363 % endif
364 364 </div>
365 365 % endif
366 366 </li>
367 367 </%def>
368 368
369 369 <%def name="menu_items(active=None)">
370 370 <%
371 371 def is_active(selected):
372 372 if selected == active:
373 373 return "active"
374 374 return ""
375 375 %>
376 376 <ul id="quick" class="main_nav navigation horizontal-list">
377 377 <!-- repo switcher -->
378 378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
379 379 <input id="repo_switcher" name="repo_switcher" type="hidden">
380 380 </li>
381 381
382 382 ## ROOT MENU
383 383 %if c.rhodecode_user.username != h.DEFAULT_USER:
384 384 <li class="${is_active('journal')}">
385 385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
386 386 <div class="menulabel">${_('Journal')}</div>
387 387 </a>
388 388 </li>
389 389 %else:
390 390 <li class="${is_active('journal')}">
391 391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
392 392 <div class="menulabel">${_('Public journal')}</div>
393 393 </a>
394 394 </li>
395 395 %endif
396 396 <li class="${is_active('gists')}">
397 397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
398 398 <div class="menulabel">${_('Gists')}</div>
399 399 </a>
400 400 </li>
401 401 <li class="${is_active('search')}">
402 402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
403 403 <div class="menulabel">${_('Search')}</div>
404 404 </a>
405 405 </li>
406 406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
407 407 <li class="${is_active('admin')}">
408 408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
409 409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 410 </a>
411 411 ${admin_menu()}
412 412 </li>
413 413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
414 414 <li class="${is_active('admin')}">
415 415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
416 416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 417 </a>
418 418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
419 419 c.rhodecode_user.repository_groups_admin,
420 420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
421 421 </li>
422 422 % endif
423 423 % if c.debug_style:
424 424 <li class="${is_active('debug_style')}">
425 425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
426 426 <div class="menulabel">${_('Style')}</div>
427 427 </a>
428 428 </li>
429 429 % endif
430 430 ## render extra user menu
431 431 ${usermenu(active=(active=='my_account'))}
432 432 </ul>
433 433
434 434 <script type="text/javascript">
435 435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
436 436
437 437 /*format the look of items in the list*/
438 438 var format = function(state, escapeMarkup){
439 439 if (!state.id){
440 440 return state.text; // optgroup
441 441 }
442 442 var obj_dict = state.obj;
443 443 var tmpl = '';
444 444
445 445 if(obj_dict && state.type == 'repo'){
446 446 if(obj_dict['repo_type'] === 'hg'){
447 447 tmpl += '<i class="icon-hg"></i> ';
448 448 }
449 449 else if(obj_dict['repo_type'] === 'git'){
450 450 tmpl += '<i class="icon-git"></i> ';
451 451 }
452 452 else if(obj_dict['repo_type'] === 'svn'){
453 453 tmpl += '<i class="icon-svn"></i> ';
454 454 }
455 455 if(obj_dict['private']){
456 456 tmpl += '<i class="icon-lock" ></i> ';
457 457 }
458 458 else if(visual_show_public_icon){
459 459 tmpl += '<i class="icon-unlock-alt"></i> ';
460 460 }
461 461 }
462 462 if(obj_dict && state.type == 'commit') {
463 463 tmpl += '<i class="icon-tag"></i>';
464 464 }
465 465 if(obj_dict && state.type == 'group'){
466 466 tmpl += '<i class="icon-folder-close"></i> ';
467 467 }
468 468 tmpl += escapeMarkup(state.text);
469 469 return tmpl;
470 470 };
471 471
472 472 var formatResult = function(result, container, query, escapeMarkup) {
473 473 return format(result, escapeMarkup);
474 474 };
475 475
476 476 var formatSelection = function(data, container, escapeMarkup) {
477 477 return format(data, escapeMarkup);
478 478 };
479 479
480 480 $("#repo_switcher").select2({
481 481 cachedDataSource: {},
482 482 minimumInputLength: 2,
483 483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
484 484 dropdownAutoWidth: true,
485 485 formatResult: formatResult,
486 486 formatSelection: formatSelection,
487 487 containerCssClass: "repo-switcher",
488 488 dropdownCssClass: "repo-switcher-dropdown",
489 489 escapeMarkup: function(m){
490 490 // don't escape our custom placeholder
491 491 if(m.substr(0,23) == '<div class="menulabel">'){
492 492 return m;
493 493 }
494 494
495 495 return Select2.util.escapeMarkup(m);
496 496 },
497 497 query: $.debounce(250, function(query){
498 498 self = this;
499 499 var cacheKey = query.term;
500 500 var cachedData = self.cachedDataSource[cacheKey];
501 501
502 502 if (cachedData) {
503 503 query.callback({results: cachedData.results});
504 504 } else {
505 505 $.ajax({
506 506 url: "${h.url('goto_switcher_data')}",
507 507 data: {'query': query.term},
508 508 dataType: 'json',
509 509 type: 'GET',
510 510 success: function(data) {
511 511 self.cachedDataSource[cacheKey] = data;
512 512 query.callback({results: data.results});
513 513 },
514 514 error: function(data, textStatus, errorThrown) {
515 515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 516 }
517 517 })
518 518 }
519 519 })
520 520 });
521 521
522 522 $("#repo_switcher").on('select2-selecting', function(e){
523 523 e.preventDefault();
524 524 window.location = e.choice.url;
525 525 });
526 526
527 527 </script>
528 528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
529 529 </%def>
530 530
531 531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
532 532 <div class="modal-dialog">
533 533 <div class="modal-content">
534 534 <div class="modal-header">
535 535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
536 536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
537 537 </div>
538 538 <div class="modal-body">
539 539 <div class="block-left">
540 540 <table class="keyboard-mappings">
541 541 <tbody>
542 542 <tr>
543 543 <th></th>
544 544 <th>${_('Site-wide shortcuts')}</th>
545 545 </tr>
546 546 <%
547 547 elems = [
548 548 ('/', 'Open quick search box'),
549 549 ('g h', 'Goto home page'),
550 550 ('g g', 'Goto my private gists page'),
551 551 ('g G', 'Goto my public gists page'),
552 552 ('n r', 'New repository page'),
553 553 ('n g', 'New gist page'),
554 554 ]
555 555 %>
556 556 %for key, desc in elems:
557 557 <tr>
558 558 <td class="keys">
559 559 <span class="key tag">${key}</span>
560 560 </td>
561 561 <td>${desc}</td>
562 562 </tr>
563 563 %endfor
564 564 </tbody>
565 565 </table>
566 566 </div>
567 567 <div class="block-left">
568 568 <table class="keyboard-mappings">
569 569 <tbody>
570 570 <tr>
571 571 <th></th>
572 572 <th>${_('Repositories')}</th>
573 573 </tr>
574 574 <%
575 575 elems = [
576 576 ('g s', 'Goto summary page'),
577 577 ('g c', 'Goto changelog page'),
578 578 ('g f', 'Goto files page'),
579 579 ('g F', 'Goto files page with file search activated'),
580 580 ('g p', 'Goto pull requests page'),
581 581 ('g o', 'Goto repository settings'),
582 582 ('g O', 'Goto repository permissions settings'),
583 583 ]
584 584 %>
585 585 %for key, desc in elems:
586 586 <tr>
587 587 <td class="keys">
588 588 <span class="key tag">${key}</span>
589 589 </td>
590 590 <td>${desc}</td>
591 591 </tr>
592 592 %endfor
593 593 </tbody>
594 594 </table>
595 595 </div>
596 596 </div>
597 597 <div class="modal-footer">
598 598 </div>
599 599 </div><!-- /.modal-content -->
600 600 </div><!-- /.modal-dialog -->
601 601 </div><!-- /.modal -->
@@ -1,253 +1,246 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, TEST_USER_REGULAR_EMAIL,
27 27 assert_session_flash)
28 28 from rhodecode.tests.fixture import Fixture
29 29 from rhodecode.tests.utils import AssertResponse
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 class TestMyAccountController(TestController):
35 35 test_user_1 = 'testme'
36 36 test_user_1_password = '0jd83nHNS/d23n'
37 37 destroy_users = set()
38 38
39 39 @classmethod
40 40 def teardown_class(cls):
41 41 fixture.destroy_users(cls.destroy_users)
42 42
43 def test_my_account(self):
44 self.log_user()
45 response = self.app.get(url('my_account'))
46
47 response.mustcontain('test_admin')
48 response.mustcontain('href="/_admin/my_account/edit"')
49
50 43 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
51 response = self.app.get(url('my_account'))
44 response = self.app.get(url('home'))
52 45 assert_response = AssertResponse(response)
53 46 element = assert_response.get_element('.logout #csrf_token')
54 47 assert element.value == csrf_token
55 48
56 49 def test_my_account_edit(self):
57 50 self.log_user()
58 51 response = self.app.get(url('my_account_edit'))
59 52
60 53 response.mustcontain('value="test_admin')
61 54
62 55 def test_my_account_my_repos(self):
63 56 self.log_user()
64 57 response = self.app.get(url('my_account_repos'))
65 58 repos = Repository.query().filter(
66 59 Repository.user == User.get_by_username(
67 60 TEST_USER_ADMIN_LOGIN)).all()
68 61 for repo in repos:
69 62 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
70 63
71 64 def test_my_account_my_watched(self):
72 65 self.log_user()
73 66 response = self.app.get(url('my_account_watched'))
74 67
75 68 repos = UserFollowing.query().filter(
76 69 UserFollowing.user == User.get_by_username(
77 70 TEST_USER_ADMIN_LOGIN)).all()
78 71 for repo in repos:
79 72 response.mustcontain(
80 73 '"name_raw": "%s"' % repo.follows_repository.repo_name)
81 74
82 75 @pytest.mark.backends("git", "hg")
83 76 def test_my_account_my_pullrequests(self, pr_util):
84 77 self.log_user()
85 78 response = self.app.get(url('my_account_pullrequests'))
86 79 response.mustcontain('There are currently no open pull '
87 80 'requests requiring your participation.')
88 81
89 82 pr = pr_util.create_pull_request(title='TestMyAccountPR')
90 83 response = self.app.get(url('my_account_pullrequests'))
91 84 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
92 85 response.mustcontain('TestMyAccountPR')
93 86
94 87 def test_my_account_my_emails(self):
95 88 self.log_user()
96 89 response = self.app.get(url('my_account_emails'))
97 90 response.mustcontain('No additional emails specified')
98 91
99 92 def test_my_account_my_emails_add_existing_email(self):
100 93 self.log_user()
101 94 response = self.app.get(url('my_account_emails'))
102 95 response.mustcontain('No additional emails specified')
103 96 response = self.app.post(url('my_account_emails'),
104 97 {'new_email': TEST_USER_REGULAR_EMAIL,
105 98 'csrf_token': self.csrf_token})
106 99 assert_session_flash(response, 'This e-mail address is already taken')
107 100
108 101 def test_my_account_my_emails_add_mising_email_in_form(self):
109 102 self.log_user()
110 103 response = self.app.get(url('my_account_emails'))
111 104 response.mustcontain('No additional emails specified')
112 105 response = self.app.post(url('my_account_emails'),
113 106 {'csrf_token': self.csrf_token})
114 107 assert_session_flash(response, 'Please enter an email address')
115 108
116 109 def test_my_account_my_emails_add_remove(self):
117 110 self.log_user()
118 111 response = self.app.get(url('my_account_emails'))
119 112 response.mustcontain('No additional emails specified')
120 113
121 114 response = self.app.post(url('my_account_emails'),
122 115 {'new_email': 'foo@barz.com',
123 116 'csrf_token': self.csrf_token})
124 117
125 118 response = self.app.get(url('my_account_emails'))
126 119
127 120 from rhodecode.model.db import UserEmailMap
128 121 email_id = UserEmailMap.query().filter(
129 122 UserEmailMap.user == User.get_by_username(
130 123 TEST_USER_ADMIN_LOGIN)).filter(
131 124 UserEmailMap.email == 'foo@barz.com').one().email_id
132 125
133 126 response.mustcontain('foo@barz.com')
134 127 response.mustcontain('<input id="del_email_id" name="del_email_id" '
135 128 'type="hidden" value="%s" />' % email_id)
136 129
137 130 response = self.app.post(
138 131 url('my_account_emails'), {
139 132 'del_email_id': email_id, '_method': 'delete',
140 133 'csrf_token': self.csrf_token})
141 134 assert_session_flash(response, 'Removed email address from user account')
142 135 response = self.app.get(url('my_account_emails'))
143 136 response.mustcontain('No additional emails specified')
144 137
145 138 @pytest.mark.parametrize(
146 139 "name, attrs", [
147 140 ('firstname', {'firstname': 'new_username'}),
148 141 ('lastname', {'lastname': 'new_username'}),
149 142 ('admin', {'admin': True}),
150 143 ('admin', {'admin': False}),
151 144 ('extern_type', {'extern_type': 'ldap'}),
152 145 ('extern_type', {'extern_type': None}),
153 146 # ('extern_name', {'extern_name': 'test'}),
154 147 # ('extern_name', {'extern_name': None}),
155 148 ('active', {'active': False}),
156 149 ('active', {'active': True}),
157 150 ('email', {'email': 'some@email.com'}),
158 151 ])
159 152 def test_my_account_update(self, name, attrs):
160 153 usr = fixture.create_user(self.test_user_1,
161 154 password=self.test_user_1_password,
162 155 email='testme@rhodecode.org',
163 156 extern_type='rhodecode',
164 157 extern_name=self.test_user_1,
165 158 skip_if_exists=True)
166 159 self.destroy_users.add(self.test_user_1)
167 160
168 161 params = usr.get_api_data() # current user data
169 162 user_id = usr.user_id
170 163 self.log_user(
171 164 username=self.test_user_1, password=self.test_user_1_password)
172 165
173 166 params.update({'password_confirmation': ''})
174 167 params.update({'new_password': ''})
175 168 params.update({'extern_type': 'rhodecode'})
176 169 params.update({'extern_name': self.test_user_1})
177 170 params.update({'csrf_token': self.csrf_token})
178 171
179 172 params.update(attrs)
180 173 # my account page cannot set language param yet, only for admins
181 174 del params['language']
182 175 response = self.app.post(url('my_account'), params)
183 176
184 177 assert_session_flash(
185 178 response, 'Your account was updated successfully')
186 179
187 180 del params['csrf_token']
188 181
189 182 updated_user = User.get_by_username(self.test_user_1)
190 183 updated_params = updated_user.get_api_data()
191 184 updated_params.update({'password_confirmation': ''})
192 185 updated_params.update({'new_password': ''})
193 186
194 187 params['last_login'] = updated_params['last_login']
195 188 # my account page cannot set language param yet, only for admins
196 189 # but we get this info from API anyway
197 190 params['language'] = updated_params['language']
198 191
199 192 if name == 'email':
200 193 params['emails'] = [attrs['email']]
201 194 if name == 'extern_type':
202 195 # cannot update this via form, expected value is original one
203 196 params['extern_type'] = "rhodecode"
204 197 if name == 'extern_name':
205 198 # cannot update this via form, expected value is original one
206 199 params['extern_name'] = str(user_id)
207 200 if name == 'active':
208 201 # my account cannot deactivate account
209 202 params['active'] = True
210 203 if name == 'admin':
211 204 # my account cannot make you an admin !
212 205 params['admin'] = False
213 206
214 207 assert params == updated_params
215 208
216 209 def test_my_account_update_err_email_exists(self):
217 210 self.log_user()
218 211
219 212 new_email = 'test_regular@mail.com' # already exisitn email
220 213 response = self.app.post(url('my_account'),
221 214 params={
222 215 'username': 'test_admin',
223 216 'new_password': 'test12',
224 217 'password_confirmation': 'test122',
225 218 'firstname': 'NewName',
226 219 'lastname': 'NewLastname',
227 220 'email': new_email,
228 221 'csrf_token': self.csrf_token,
229 222 })
230 223
231 224 response.mustcontain('This e-mail address is already taken')
232 225
233 226 def test_my_account_update_err(self):
234 227 self.log_user('test_regular2', 'test12')
235 228
236 229 new_email = 'newmail.pl'
237 230 response = self.app.post(url('my_account'),
238 231 params={
239 232 'username': 'test_admin',
240 233 'new_password': 'test12',
241 234 'password_confirmation': 'test122',
242 235 'firstname': 'NewName',
243 236 'lastname': 'NewLastname',
244 237 'email': new_email,
245 238 'csrf_token': self.csrf_token,
246 239 })
247 240
248 241 response.mustcontain('An email address must contain a single @')
249 242 from rhodecode.model import validators
250 243 msg = validators.ValidUsername(
251 244 edit=False, old_data={})._messages['username_exists']
252 245 msg = h.html_escape(msg % {'username': 'test_admin'})
253 246 response.mustcontain(u"%s" % msg)
General Comments 0
You need to be logged in to leave comments. Login now