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