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