##// END OF EJS Templates
repo-groups: moved to pyramid
marcink -
r2175:ea878558 default
parent child Browse files
Show More
@@ -0,0 +1,176 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 os
22 import pytest
23
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
26 from rhodecode.model.db import Repository, UserRepoToPerm, User
27 from rhodecode.model.meta import Session
28 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.tests import (
30 assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, TestController)
31 from rhodecode.tests.fixture import Fixture
32
33 fixture = Fixture()
34
35
36 def route_path(name, params=None, **kwargs):
37 import urllib
38
39 base_url = {
40 'repo_groups': ADMIN_PREFIX + '/repo_groups',
41 'repo_group_new': ADMIN_PREFIX + '/repo_group/new',
42 'repo_group_create': ADMIN_PREFIX + '/repo_group/create',
43
44 }[name].format(**kwargs)
45
46 if params:
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 return base_url
49
50
51 def _get_permission_for_user(user, repo):
52 perm = UserRepoToPerm.query()\
53 .filter(UserRepoToPerm.repository ==
54 Repository.get_by_repo_name(repo))\
55 .filter(UserRepoToPerm.user == User.get_by_username(user))\
56 .all()
57 return perm
58
59
60 @pytest.mark.usefixtures("app")
61 class TestAdminRepositoryGroups(object):
62 def test_show_repo_groups(self, autologin_user):
63 response = self.app.get(route_path('repo_groups'))
64 response.mustcontain('data: []')
65
66 def test_show_repo_groups_after_creating_group(self, autologin_user):
67 fixture.create_repo_group('test_repo_group')
68 response = self.app.get(route_path('repo_groups'))
69 response.mustcontain('"name_raw": "test_repo_group"')
70 fixture.destroy_repo_group('test_repo_group')
71
72 def test_new(self, autologin_user):
73 self.app.get(route_path('repo_group_new'))
74
75 def test_new_with_parent_group(self, autologin_user, user_util):
76 gr = user_util.create_repo_group()
77
78 self.app.get(route_path('repo_group_new'),
79 params=dict(parent_group=gr.group_name))
80
81 def test_new_by_regular_user_no_permission(self, autologin_regular_user):
82 self.app.get(route_path('repo_group_new'), status=403)
83
84 @pytest.mark.parametrize('repo_group_name', [
85 'git_repo',
86 'git_repo_ąć',
87 'hg_repo',
88 '12345',
89 'hg_repo_ąć',
90 ])
91 def test_create(self, autologin_user, repo_group_name, csrf_token):
92 repo_group_name_unicode = repo_group_name.decode('utf8')
93 description = 'description for newly created repo group'
94
95 response = self.app.post(
96 route_path('repo_group_create'),
97 fixture._get_group_create_params(
98 group_name=repo_group_name,
99 group_description=description,
100 csrf_token=csrf_token))
101
102 # run the check page that triggers the flash message
103 repo_gr_url = h.route_path(
104 'repo_group_home', repo_group_name=repo_group_name)
105
106 assert_session_flash(
107 response,
108 'Created repository group <a href="%s">%s</a>' % (
109 repo_gr_url, repo_group_name_unicode))
110
111 # # test if the repo group was created in the database
112 new_repo_group = RepoGroupModel()._get_repo_group(
113 repo_group_name_unicode)
114 assert new_repo_group is not None
115
116 assert new_repo_group.group_name == repo_group_name_unicode
117 assert new_repo_group.group_description == description
118
119 # test if the repository is visible in the list ?
120 response = self.app.get(repo_gr_url)
121 response.mustcontain(repo_group_name)
122
123 # test if the repository group was created on filesystem
124 is_on_filesystem = os.path.isdir(
125 os.path.join(TESTS_TMP_PATH, repo_group_name))
126 if not is_on_filesystem:
127 self.fail('no repo group %s in filesystem' % repo_group_name)
128
129 RepoGroupModel().delete(repo_group_name_unicode)
130 Session().commit()
131
132 @pytest.mark.parametrize('repo_group_name', [
133 'git_repo',
134 'git_repo_ąć',
135 'hg_repo',
136 '12345',
137 'hg_repo_ąć',
138 ])
139 def test_create_subgroup(self, autologin_user, user_util, repo_group_name, csrf_token):
140 parent_group = user_util.create_repo_group()
141 parent_group_name = parent_group.group_name
142
143 expected_group_name = '{}/{}'.format(
144 parent_group_name, repo_group_name)
145 expected_group_name_unicode = expected_group_name.decode('utf8')
146
147 try:
148 response = self.app.post(
149 route_path('repo_group_create'),
150 fixture._get_group_create_params(
151 group_name=repo_group_name,
152 group_parent_id=parent_group.group_id,
153 group_description='Test desciption',
154 csrf_token=csrf_token))
155
156 assert_session_flash(
157 response,
158 u'Created repository group <a href="%s">%s</a>' % (
159 h.route_path('repo_group_home',
160 repo_group_name=expected_group_name),
161 expected_group_name_unicode))
162 finally:
163 RepoGroupModel().delete(expected_group_name_unicode)
164 Session().commit()
165
166 def test_user_with_creation_permissions_cannot_create_subgroups(
167 self, autologin_regular_user, user_util):
168
169 user_util.grant_user_permission(
170 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
171 parent_group = user_util.create_repo_group()
172 parent_group_id = parent_group.group_id
173 self.app.get(
174 route_path('repo_group_new',
175 params=dict(parent_group=parent_group_id), ),
176 status=403)
@@ -0,0 +1,204 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-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 logging
22 import formencode
23 import formencode.htmlfill
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
27 from pyramid.renderers import render
28 from pyramid.response import Response
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
34 LoginRequired, CSRFRequired, NotAnonymous,
35 HasPermissionAny, HasRepoGroupPermissionAny)
36 from rhodecode.lib import helpers as h, audit_logger
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.scm import RepoGroupList
41 from rhodecode.model.db import Session, RepoGroup
42
43 log = logging.getLogger(__name__)
44
45
46 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
47
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
50 self._register_global_c(c)
51 return c
52
53 def _load_form_data(self, c):
54 allow_empty_group = False
55
56 if self._can_create_repo_group():
57 # we're global admin, we're ok and we can create TOP level groups
58 allow_empty_group = True
59
60 # override the choices for this form, we need to filter choices
61 # and display only those we have ADMIN right
62 groups_with_admin_rights = RepoGroupList(
63 RepoGroup.query().all(),
64 perm_set=['group.admin'])
65 c.repo_groups = RepoGroup.groups_choices(
66 groups=groups_with_admin_rights,
67 show_empty_group=allow_empty_group)
68
69 def _can_create_repo_group(self, parent_group_id=None):
70 is_admin = HasPermissionAny('hg.admin')('group create controller')
71 create_repo_group = HasPermissionAny(
72 'hg.repogroup.create.true')('group create controller')
73 if is_admin or (create_repo_group and not parent_group_id):
74 # we're global admin, or we have global repo group create
75 # permission
76 # we're ok and we can create TOP level groups
77 return True
78 elif parent_group_id:
79 # we check the permission if we can write to parent group
80 group = RepoGroup.get(parent_group_id)
81 group_name = group.group_name if group else None
82 if HasRepoGroupPermissionAny('group.admin')(
83 group_name, 'check if user is an admin of group'):
84 # we're an admin of passed in group, we're ok.
85 return True
86 else:
87 return False
88 return False
89
90 @LoginRequired()
91 @NotAnonymous()
92 # perms check inside
93 @view_config(
94 route_name='repo_groups', request_method='GET',
95 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
96 def repo_group_list(self):
97 c = self.load_default_context()
98
99 repo_group_list = RepoGroup.get_all_repo_groups()
100 repo_group_list_acl = RepoGroupList(
101 repo_group_list, perm_set=['group.admin'])
102 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
103 repo_group_list=repo_group_list_acl, admin=True)
104 c.data = json.dumps(repo_group_data)
105 return self._get_template_context(c)
106
107 @LoginRequired()
108 @NotAnonymous()
109 # perm checks inside
110 @view_config(
111 route_name='repo_group_new', request_method='GET',
112 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
113 def repo_group_new(self):
114 c = self.load_default_context()
115
116 # perm check for admin, create_group perm or admin of parent_group
117 parent_group_id = safe_int(self.request.GET.get('parent_group'))
118 if not self._can_create_repo_group(parent_group_id):
119 raise HTTPForbidden()
120
121 self._load_form_data(c)
122
123 defaults = {} # Future proof for default of repo group
124 data = render(
125 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
126 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
128 data,
129 defaults=defaults,
130 encoding="UTF-8",
131 force_defaults=False
132 )
133 return Response(html)
134
135 @LoginRequired()
136 @NotAnonymous()
137 @CSRFRequired()
138 # perm checks inside
139 @view_config(
140 route_name='repo_group_create', request_method='POST',
141 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
142 def repo_group_create(self):
143 c = self.load_default_context()
144 _ = self.request.translate
145
146 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
147 can_create = self._can_create_repo_group(parent_group_id)
148
149 self._load_form_data(c)
150 # permissions for can create group based on parent_id are checked
151 # here in the Form
152 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
153 repo_group_form = RepoGroupForm(available_groups=available_groups,
154 can_create_in_root=can_create)()
155
156 repo_group_name = self.request.POST.get('group_name')
157 try:
158 owner = self._rhodecode_user
159 form_result = repo_group_form.to_python(dict(self.request.POST))
160 repo_group = RepoGroupModel().create(
161 group_name=form_result['group_name_full'],
162 group_description=form_result['group_description'],
163 owner=owner.user_id,
164 copy_permissions=form_result['group_copy_permissions']
165 )
166 Session().flush()
167
168 repo_group_data = repo_group.get_api_data()
169 audit_logger.store_web(
170 'repo_group.create', action_data={'data': repo_group_data},
171 user=self._rhodecode_user)
172
173 Session().commit()
174
175 _new_group_name = form_result['group_name_full']
176
177 repo_group_url = h.link_to(
178 _new_group_name,
179 h.route_path('repo_group_home', repo_group_name=_new_group_name))
180 h.flash(h.literal(_('Created repository group %s')
181 % repo_group_url), category='success')
182
183 except formencode.Invalid as errors:
184 data = render(
185 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
186 self._get_template_context(c), self.request)
187 html = formencode.htmlfill.render(
188 data,
189 defaults=errors.value,
190 errors=errors.error_dict or {},
191 prefix_error=False,
192 encoding="UTF-8",
193 force_defaults=False
194 )
195 return Response(html)
196 except Exception:
197 log.exception("Exception during creation of repository group")
198 h.flash(_('Error occurred during creation of repository group %s')
199 % repo_group_name, category='error')
200 raise HTTPFound(h.route_path('home'))
201
202 raise HTTPFound(
203 h.route_path('repo_group_home',
204 repo_group_name=form_result['group_name_full']))
@@ -0,0 +1,89 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests import assert_session_flash
24
25
26 def route_path(name, params=None, **kwargs):
27 import urllib
28
29 base_url = {
30 'edit_repo_group_advanced':
31 '/{repo_group_name}/_settings/advanced',
32 'edit_repo_group_advanced_delete':
33 '/{repo_group_name}/_settings/advanced/delete',
34 }[name].format(**kwargs)
35
36 if params:
37 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
38 return base_url
39
40
41 @pytest.mark.usefixtures("app")
42 class TestRepoGroupsAdvancedView(object):
43
44 @pytest.mark.parametrize('repo_group_name', [
45 'gro',
46 '12345',
47 ])
48 def test_show_advanced_settings(self, autologin_user, user_util, repo_group_name):
49 user_util._test_name = repo_group_name
50 gr = user_util.create_repo_group()
51 self.app.get(
52 route_path('edit_repo_group_advanced',
53 repo_group_name=gr.group_name))
54
55 def test_show_advanced_settings_delete(self, autologin_user, user_util,
56 csrf_token):
57 gr = user_util.create_repo_group(auto_cleanup=False)
58 repo_group_name = gr.group_name
59
60 params = dict(
61 csrf_token=csrf_token
62 )
63 response = self.app.post(
64 route_path('edit_repo_group_advanced_delete',
65 repo_group_name=repo_group_name), params=params)
66 assert_session_flash(
67 response, 'Removed repository group `{}`'.format(repo_group_name))
68
69 def test_delete_not_possible_with_objects_inside(self, autologin_user,
70 repo_groups, csrf_token):
71 zombie_group, parent_group, child_group = repo_groups
72
73 response = self.app.get(
74 route_path('edit_repo_group_advanced',
75 repo_group_name=parent_group.group_name))
76
77 response.mustcontain(
78 'This repository group includes 1 children repository group')
79
80 params = dict(
81 csrf_token=csrf_token
82 )
83 response = self.app.post(
84 route_path('edit_repo_group_advanced_delete',
85 repo_group_name=parent_group.group_name), params=params)
86
87 assert_session_flash(
88 response, 'This repository group contains 1 subgroup '
89 'and cannot be deleted')
@@ -0,0 +1,49 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23
24 def route_path(name, params=None, **kwargs):
25 import urllib
26
27 base_url = {
28 'edit_repo_group_perms':
29 '/{repo_group_name:}/_settings/permissions',
30 'edit_repo_group_perms_update':
31 '/{repo_group_name}/_settings/permissions/update',
32 }[name].format(**kwargs)
33
34 if params:
35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 return base_url
37
38
39 @pytest.mark.usefixtures("app")
40 class TestRepoGroupsPermissionsView(object):
41
42 def test_edit_repo_group_perms(self, user_util, autologin_user):
43 repo_group = user_util.create_repo_group()
44 self.app.get(
45 route_path('edit_repo_group_perms',
46 repo_group_name=repo_group.group_name), status=200)
47
48 def test_update_permissions(self):
49 pass
@@ -0,0 +1,90 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests import assert_session_flash
24
25
26 def route_path(name, params=None, **kwargs):
27 import urllib
28
29 base_url = {
30 'edit_repo_group': '/{repo_group_name}/_edit',
31 # Update is POST to the above url
32 }[name].format(**kwargs)
33
34 if params:
35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 return base_url
37
38
39 @pytest.mark.usefixtures("app")
40 class TestRepoGroupsSettingsView(object):
41
42 @pytest.mark.parametrize('repo_group_name', [
43 'gro',
44 u'12345',
45 ])
46 def test_edit(self, user_util, autologin_user, repo_group_name):
47 user_util._test_name = repo_group_name
48 repo_group = user_util.create_repo_group()
49
50 self.app.get(
51 route_path('edit_repo_group', repo_group_name=repo_group.group_name),
52 status=200)
53
54 def test_update(self, csrf_token, autologin_user, user_util, rc_fixture):
55 repo_group = user_util.create_repo_group()
56 repo_group_name = repo_group.group_name
57
58 description = 'description for newly created repo group'
59 form_data = rc_fixture._get_group_create_params(
60 group_name=repo_group.group_name,
61 group_description=description,
62 csrf_token=csrf_token,
63 repo_group_name=repo_group.group_name,
64 repo_group_owner=repo_group.user.username)
65
66 response = self.app.post(
67 route_path('edit_repo_group',
68 repo_group_name=repo_group.group_name),
69 form_data,
70 status=302)
71
72 assert_session_flash(
73 response, 'Repository Group `{}` updated successfully'.format(
74 repo_group_name))
75
76 def test_update_fails_when_parent_pointing_to_self(
77 self, csrf_token, user_util, autologin_user, rc_fixture):
78 group = user_util.create_repo_group()
79 response = self.app.post(
80 route_path('edit_repo_group', repo_group_name=group.group_name),
81 rc_fixture._get_group_create_params(
82 repo_group_name=group.group_name,
83 repo_group_owner=group.user.username,
84 repo_group=group.group_id,
85 csrf_token=csrf_token),
86 status=200
87 )
88 response.mustcontain(
89 '<span class="error-message">"{}" is not one of -1'.format(
90 group.group_id))
@@ -0,0 +1,105 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-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 logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
25
26 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
30 LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator)
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.meta import Session
33
34 log = logging.getLogger(__name__)
35
36
37 class RepoGroupSettingsView(RepoGroupAppView):
38 def load_default_context(self):
39 c = self._get_local_tmpl_context()
40 self._register_global_c(c)
41 return c
42
43 @LoginRequired()
44 @HasRepoGroupPermissionAnyDecorator('group.admin')
45 @view_config(
46 route_name='edit_repo_group_advanced', request_method='GET',
47 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
48 def edit_repo_group_advanced(self):
49 c = self.load_default_context()
50 c.active = 'advanced'
51 c.repo_group = self.db_repo_group
52 return self._get_template_context(c)
53
54 @LoginRequired()
55 @HasRepoGroupPermissionAnyDecorator('group.admin')
56 @CSRFRequired()
57 @view_config(
58 route_name='edit_repo_group_advanced_delete', request_method='POST',
59 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
60 def edit_repo_group_delete(self):
61 _ = self.request.translate
62 _ungettext = self.request.plularize
63 c = self.load_default_context()
64 c.repo_group = self.db_repo_group
65
66 repos = c.repo_group.repositories.all()
67 if repos:
68 msg = _ungettext(
69 'This repository group contains %(num)d repository and cannot be deleted',
70 'This repository group contains %(num)d repositories and cannot be'
71 ' deleted',
72 len(repos)) % {'num': len(repos)}
73 h.flash(msg, category='warning')
74 raise HTTPFound(
75 h.route_path('edit_repo_group_advanced',
76 repo_group_name=self.db_repo_group_name))
77
78 children = c.repo_group.children.all()
79 if children:
80 msg = _ungettext(
81 'This repository group contains %(num)d subgroup and cannot be deleted',
82 'This repository group contains %(num)d subgroups and cannot be deleted',
83 len(children)) % {'num': len(children)}
84 h.flash(msg, category='warning')
85 raise HTTPFound(
86 h.route_path('edit_repo_group_advanced',
87 repo_group_name=self.db_repo_group_name))
88
89 try:
90 old_values = c.repo_group.get_api_data()
91 RepoGroupModel().delete(self.db_repo_group_name)
92
93 audit_logger.store_web(
94 'repo_group.delete', action_data={'old_data': old_values},
95 user=c.rhodecode_user)
96
97 Session().commit()
98 h.flash(_('Removed repository group `%s`') % self.db_repo_group_name,
99 category='success')
100 except Exception:
101 log.exception("Exception during deletion of repository group")
102 h.flash(_('Error occurred during deletion of repository group %s')
103 % self.db_repo_group_name, category='error')
104
105 raise HTTPFound(h.route_path('repo_groups'))
@@ -0,0 +1,100 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-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 logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
25
26 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.forms import RepoGroupPermsForm
33 from rhodecode.model.meta import Session
34
35 log = logging.getLogger(__name__)
36
37
38 class RepoGroupPermissionsView(RepoGroupAppView):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
41 self._register_global_c(c)
42 return c
43
44 @LoginRequired()
45 @HasRepoGroupPermissionAnyDecorator('group.admin')
46 @view_config(
47 route_name='edit_repo_group_perms', request_method='GET',
48 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
49 def edit_repo_group_permissions(self):
50 c = self.load_default_context()
51 c.active = 'permissions'
52 c.repo_group = self.db_repo_group
53 return self._get_template_context(c)
54
55 @LoginRequired()
56 @HasRepoGroupPermissionAnyDecorator('group.admin')
57 @CSRFRequired()
58 @view_config(
59 route_name='edit_repo_group_perms_update', request_method='POST',
60 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
61 def edit_repo_groups_permissions_update(self):
62 _ = self.request.translate
63 c = self.load_default_context()
64 c.active = 'perms'
65 c.repo_group = self.db_repo_group
66
67 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
68 form = RepoGroupPermsForm(valid_recursive_choices)()\
69 .to_python(self.request.POST)
70
71 if not c.rhodecode_user.is_admin:
72 if self._revoke_perms_on_yourself(form):
73 msg = _('Cannot change permission for yourself as admin')
74 h.flash(msg, category='warning')
75 raise HTTPFound(
76 h.route_path('edit_repo_group_perms',
77 group_name=self.db_repo_group_name))
78
79 # iterate over all members(if in recursive mode) of this groups and
80 # set the permissions !
81 # this can be potentially heavy operation
82 changes = RepoGroupModel().update_permissions(
83 c.repo_group,
84 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
85 form['recursive'])
86
87 action_data = {
88 'added': changes['added'],
89 'updated': changes['updated'],
90 'deleted': changes['deleted'],
91 }
92 audit_logger.store_web(
93 'repo_group.edit.permissions', action_data=action_data,
94 user=c.rhodecode_user)
95
96 Session().commit()
97 h.flash(_('Repository Group permissions updated'), category='success')
98 raise HTTPFound(
99 h.route_path('edit_repo_group_perms',
100 repo_group_name=self.db_repo_group_name))
@@ -0,0 +1,183 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-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 logging
22 import deform
23
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
26
27 from rhodecode.apps._base import RepoGroupAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
32 LoginRequired, HasPermissionAll,
33 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.db import Session, RepoGroup
35 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38
39 log = logging.getLogger(__name__)
40
41
42 class RepoGroupSettingsView(RepoGroupAppView):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
45 c.repo_group = self.db_repo_group
46 no_parrent = not c.repo_group.parent_group
47 can_create_in_root = self._can_create_repo_group()
48
49 show_root_location = False
50 if no_parrent or can_create_in_root:
51 # we're global admin, we're ok and we can create TOP level groups
52 # or in case this group is already at top-level we also allow
53 # creation in root
54 show_root_location = True
55
56 acl_groups = RepoGroupList(
57 RepoGroup.query().all(),
58 perm_set=['group.admin'])
59 c.repo_groups = RepoGroup.groups_choices(
60 groups=acl_groups,
61 show_empty_group=show_root_location)
62 # filter out current repo group
63 exclude_group_ids = [c.repo_group.group_id]
64 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
65 c.repo_groups)
66 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
67
68 parent_group = c.repo_group.parent_group
69
70 add_parent_group = (parent_group and (
71 parent_group.group_id not in c.repo_groups_choices))
72 if add_parent_group:
73 c.repo_groups_choices.append(parent_group.group_id)
74 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
75
76 self._register_global_c(c)
77 return c
78
79 def _can_create_repo_group(self, parent_group_id=None):
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
81 create_repo_group = HasPermissionAll(
82 'hg.repogroup.create.true')('group create controller')
83 if is_admin or (create_repo_group and not parent_group_id):
84 # we're global admin, or we have global repo group create
85 # permission
86 # we're ok and we can create TOP level groups
87 return True
88 elif parent_group_id:
89 # we check the permission if we can write to parent group
90 group = RepoGroup.get(parent_group_id)
91 group_name = group.group_name if group else None
92 if HasRepoGroupPermissionAny('group.admin')(
93 group_name, 'check if user is an admin of group'):
94 # we're an admin of passed in group, we're ok.
95 return True
96 else:
97 return False
98 return False
99
100 def _get_schema(self, c, old_values=None):
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
102 repo_group_repo_group_options=c.repo_groups_choices,
103 repo_group_repo_group_items=c.repo_groups,
104
105 # user caller
106 user=self._rhodecode_user,
107 old_values=old_values
108 )
109
110 @LoginRequired()
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
112 @view_config(
113 route_name='edit_repo_group', request_method='GET',
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
115 def edit_settings(self):
116 c = self.load_default_context()
117 c.active = 'settings'
118
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
120 defaults['repo_group_owner'] = defaults['user']
121
122 schema = self._get_schema(c)
123 c.form = RcForm(schema, appstruct=defaults)
124 return self._get_template_context(c)
125
126 @LoginRequired()
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
128 @CSRFRequired()
129 @view_config(
130 route_name='edit_repo_group', request_method='POST',
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
132 def edit_settings_update(self):
133 _ = self.request.translate
134 c = self.load_default_context()
135 c.active = 'settings'
136
137 old_repo_group_name = self.db_repo_group_name
138 new_repo_group_name = old_repo_group_name
139
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
141 schema = self._get_schema(c, old_values=old_values)
142
143 c.form = RcForm(schema)
144 pstruct = self.request.POST.items()
145
146 try:
147 schema_data = c.form.validate(pstruct)
148 except deform.ValidationFailure as err_form:
149 return self._get_template_context(c)
150
151 # data is now VALID, proceed with updates
152 # save validated data back into the updates dict
153 validated_updates = dict(
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
156 user=schema_data['repo_group_owner'],
157 group_description=schema_data['repo_group_description'],
158 enable_locking=schema_data['repo_group_enable_locking'],
159 )
160
161 try:
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
163
164 audit_logger.store_web(
165 'repo_group.edit', action_data={'old_data': old_values},
166 user=c.rhodecode_user)
167
168 Session().commit()
169
170 # use the new full name for redirect once we know we updated
171 # the name on filesystem and in DB
172 new_repo_group_name = schema_data['repo_group_name']
173
174 h.flash(_('Repository Group `{}` updated successfully').format(
175 old_repo_group_name), category='success')
176
177 except Exception:
178 log.exception("Exception during update or repository group")
179 h.flash(_('Error occurred during update of repository group %s')
180 % old_repo_group_name, category='error')
181
182 raise HTTPFound(
183 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
@@ -1,570 +1,584 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 time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user
33 from rhodecode.model import user
34 from rhodecode.model.db import User
34 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 ADMIN_PREFIX = '/_admin'
40 ADMIN_PREFIX = '/_admin'
41 STATIC_FILE_PREFIX = '/_static'
41 STATIC_FILE_PREFIX = '/_static'
42
42
43 URL_NAME_REQUIREMENTS = {
43 URL_NAME_REQUIREMENTS = {
44 # group name can have a slash in them, but they must not end with a slash
44 # group name can have a slash in them, but they must not end with a slash
45 'group_name': r'.*?[^/]',
45 'group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
47 # repo names can have a slash in them, but they must not end with a slash
47 # repo names can have a slash in them, but they must not end with a slash
48 'repo_name': r'.*?[^/]',
48 'repo_name': r'.*?[^/]',
49 # file path eats up everything at the end
49 # file path eats up everything at the end
50 'f_path': r'.*',
50 'f_path': r'.*',
51 # reference types
51 # reference types
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 }
54 }
55
55
56
56
57 def add_route_with_slash(config,name, pattern, **kw):
57 def add_route_with_slash(config,name, pattern, **kw):
58 config.add_route(name, pattern, **kw)
58 config.add_route(name, pattern, **kw)
59 if not pattern.endswith('/'):
59 if not pattern.endswith('/'):
60 config.add_route(name + '_slash', pattern + '/', **kw)
60 config.add_route(name + '_slash', pattern + '/', **kw)
61
61
62
62
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 """
64 """
65 Adds regex requirements to pyramid routes using a mapping dict
65 Adds regex requirements to pyramid routes using a mapping dict
66 e.g::
66 e.g::
67 add_route_requirements('{repo_name}/settings')
67 add_route_requirements('{repo_name}/settings')
68 """
68 """
69 for key, regex in requirements.items():
69 for key, regex in requirements.items():
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 return route_path
71 return route_path
72
72
73
73
74 def get_format_ref_id(repo):
74 def get_format_ref_id(repo):
75 """Returns a `repo` specific reference formatter function"""
75 """Returns a `repo` specific reference formatter function"""
76 if h.is_svn(repo):
76 if h.is_svn(repo):
77 return _format_ref_id_svn
77 return _format_ref_id_svn
78 else:
78 else:
79 return _format_ref_id
79 return _format_ref_id
80
80
81
81
82 def _format_ref_id(name, raw_id):
82 def _format_ref_id(name, raw_id):
83 """Default formatting of a given reference `name`"""
83 """Default formatting of a given reference `name`"""
84 return name
84 return name
85
85
86
86
87 def _format_ref_id_svn(name, raw_id):
87 def _format_ref_id_svn(name, raw_id):
88 """Special way of formatting a reference for Subversion including path"""
88 """Special way of formatting a reference for Subversion including path"""
89 return '%s@%s' % (name, raw_id)
89 return '%s@%s' % (name, raw_id)
90
90
91
91
92 class TemplateArgs(StrictAttributeDict):
92 class TemplateArgs(StrictAttributeDict):
93 pass
93 pass
94
94
95
95
96 class BaseAppView(object):
96 class BaseAppView(object):
97
97
98 def __init__(self, context, request):
98 def __init__(self, context, request):
99 self.request = request
99 self.request = request
100 self.context = context
100 self.context = context
101 self.session = request.session
101 self.session = request.session
102 self._rhodecode_user = request.user # auth user
102 self._rhodecode_user = request.user # auth user
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 self._maybe_needs_password_change(
104 self._maybe_needs_password_change(
105 request.matched_route.name, self._rhodecode_db_user)
105 request.matched_route.name, self._rhodecode_db_user)
106
106
107 def _maybe_needs_password_change(self, view_name, user_obj):
107 def _maybe_needs_password_change(self, view_name, user_obj):
108 log.debug('Checking if user %s needs password change on view %s',
108 log.debug('Checking if user %s needs password change on view %s',
109 user_obj, view_name)
109 user_obj, view_name)
110 skip_user_views = [
110 skip_user_views = [
111 'logout', 'login',
111 'logout', 'login',
112 'my_account_password', 'my_account_password_update'
112 'my_account_password', 'my_account_password_update'
113 ]
113 ]
114
114
115 if not user_obj:
115 if not user_obj:
116 return
116 return
117
117
118 if user_obj.username == User.DEFAULT_USER:
118 if user_obj.username == User.DEFAULT_USER:
119 return
119 return
120
120
121 now = time.time()
121 now = time.time()
122 should_change = user_obj.user_data.get('force_password_change')
122 should_change = user_obj.user_data.get('force_password_change')
123 change_after = safe_int(should_change) or 0
123 change_after = safe_int(should_change) or 0
124 if should_change and now > change_after:
124 if should_change and now > change_after:
125 log.debug('User %s requires password change', user_obj)
125 log.debug('User %s requires password change', user_obj)
126 h.flash('You are required to change your password', 'warning',
126 h.flash('You are required to change your password', 'warning',
127 ignore_duplicate=True)
127 ignore_duplicate=True)
128
128
129 if view_name not in skip_user_views:
129 if view_name not in skip_user_views:
130 raise HTTPFound(
130 raise HTTPFound(
131 self.request.route_path('my_account_password'))
131 self.request.route_path('my_account_password'))
132
132
133 def _log_creation_exception(self, e, repo_name):
133 def _log_creation_exception(self, e, repo_name):
134 _ = self.request.translate
134 _ = self.request.translate
135 reason = None
135 reason = None
136 if len(e.args) == 2:
136 if len(e.args) == 2:
137 reason = e.args[1]
137 reason = e.args[1]
138
138
139 if reason == 'INVALID_CERTIFICATE':
139 if reason == 'INVALID_CERTIFICATE':
140 log.exception(
140 log.exception(
141 'Exception creating a repository: invalid certificate')
141 'Exception creating a repository: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
143 % repo_name)
143 % repo_name)
144 else:
144 else:
145 log.exception("Exception creating a repository")
145 log.exception("Exception creating a repository")
146 msg = (_('Error creating repository %s')
146 msg = (_('Error creating repository %s')
147 % repo_name)
147 % repo_name)
148 return msg
148 return msg
149
149
150 def _get_local_tmpl_context(self, include_app_defaults=False):
150 def _get_local_tmpl_context(self, include_app_defaults=False):
151 c = TemplateArgs()
151 c = TemplateArgs()
152 c.auth_user = self.request.user
152 c.auth_user = self.request.user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 c.rhodecode_user = self.request.user
154 c.rhodecode_user = self.request.user
155
155
156 if include_app_defaults:
156 if include_app_defaults:
157 # NOTE(marcink): after full pyramid migration include_app_defaults
157 # NOTE(marcink): after full pyramid migration include_app_defaults
158 # should be turned on by default
158 # should be turned on by default
159 from rhodecode.lib.base import attach_context_attributes
159 from rhodecode.lib.base import attach_context_attributes
160 attach_context_attributes(c, self.request, self.request.user.user_id)
160 attach_context_attributes(c, self.request, self.request.user.user_id)
161
161
162 return c
162 return c
163
163
164 def _register_global_c(self, tmpl_args):
164 def _register_global_c(self, tmpl_args):
165 """
165 """
166 Registers attributes to pylons global `c`
166 Registers attributes to pylons global `c`
167 """
167 """
168
168
169 # TODO(marcink): remove once pyramid migration is finished
169 # TODO(marcink): remove once pyramid migration is finished
170 from pylons import tmpl_context as c
170 from pylons import tmpl_context as c
171 try:
171 try:
172 for k, v in tmpl_args.items():
172 for k, v in tmpl_args.items():
173 setattr(c, k, v)
173 setattr(c, k, v)
174 except TypeError:
174 except TypeError:
175 log.exception('Failed to register pylons C')
175 log.exception('Failed to register pylons C')
176 pass
176 pass
177
177
178 def _get_template_context(self, tmpl_args):
178 def _get_template_context(self, tmpl_args):
179 self._register_global_c(tmpl_args)
179 self._register_global_c(tmpl_args)
180
180
181 local_tmpl_args = {
181 local_tmpl_args = {
182 'defaults': {},
182 'defaults': {},
183 'errors': {},
183 'errors': {},
184 # register a fake 'c' to be used in templates instead of global
184 # register a fake 'c' to be used in templates instead of global
185 # pylons c, after migration to pyramid we should rename it to 'c'
185 # pylons c, after migration to pyramid we should rename it to 'c'
186 # make sure we replace usage of _c in templates too
186 # make sure we replace usage of _c in templates too
187 '_c': tmpl_args
187 '_c': tmpl_args
188 }
188 }
189 local_tmpl_args.update(tmpl_args)
189 local_tmpl_args.update(tmpl_args)
190 return local_tmpl_args
190 return local_tmpl_args
191
191
192 def load_default_context(self):
192 def load_default_context(self):
193 """
193 """
194 example:
194 example:
195
195
196 def load_default_context(self):
196 def load_default_context(self):
197 c = self._get_local_tmpl_context()
197 c = self._get_local_tmpl_context()
198 c.custom_var = 'foobar'
198 c.custom_var = 'foobar'
199 self._register_global_c(c)
199 self._register_global_c(c)
200 return c
200 return c
201 """
201 """
202 raise NotImplementedError('Needs implementation in view class')
202 raise NotImplementedError('Needs implementation in view class')
203
203
204
204
205 class RepoAppView(BaseAppView):
205 class RepoAppView(BaseAppView):
206
206
207 def __init__(self, context, request):
207 def __init__(self, context, request):
208 super(RepoAppView, self).__init__(context, request)
208 super(RepoAppView, self).__init__(context, request)
209 self.db_repo = request.db_repo
209 self.db_repo = request.db_repo
210 self.db_repo_name = self.db_repo.repo_name
210 self.db_repo_name = self.db_repo.repo_name
211 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
211 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
212
212
213 def _handle_missing_requirements(self, error):
213 def _handle_missing_requirements(self, error):
214 log.error(
214 log.error(
215 'Requirements are missing for repository %s: %s',
215 'Requirements are missing for repository %s: %s',
216 self.db_repo_name, error.message)
216 self.db_repo_name, error.message)
217
217
218 def _get_local_tmpl_context(self, include_app_defaults=False):
218 def _get_local_tmpl_context(self, include_app_defaults=False):
219 _ = self.request.translate
219 _ = self.request.translate
220 c = super(RepoAppView, self)._get_local_tmpl_context(
220 c = super(RepoAppView, self)._get_local_tmpl_context(
221 include_app_defaults=include_app_defaults)
221 include_app_defaults=include_app_defaults)
222
222
223 # register common vars for this type of view
223 # register common vars for this type of view
224 c.rhodecode_db_repo = self.db_repo
224 c.rhodecode_db_repo = self.db_repo
225 c.repo_name = self.db_repo_name
225 c.repo_name = self.db_repo_name
226 c.repository_pull_requests = self.db_repo_pull_requests
226 c.repository_pull_requests = self.db_repo_pull_requests
227
227
228 c.repository_requirements_missing = False
228 c.repository_requirements_missing = False
229 try:
229 try:
230 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
230 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
231 except RepositoryRequirementError as e:
231 except RepositoryRequirementError as e:
232 c.repository_requirements_missing = True
232 c.repository_requirements_missing = True
233 self._handle_missing_requirements(e)
233 self._handle_missing_requirements(e)
234 self.rhodecode_vcs_repo = None
234 self.rhodecode_vcs_repo = None
235
235
236 if (not c.repository_requirements_missing
236 if (not c.repository_requirements_missing
237 and self.rhodecode_vcs_repo is None):
237 and self.rhodecode_vcs_repo is None):
238 # unable to fetch this repo as vcs instance, report back to user
238 # unable to fetch this repo as vcs instance, report back to user
239 h.flash(_(
239 h.flash(_(
240 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
240 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
241 "Please check if it exist, or is not damaged.") %
241 "Please check if it exist, or is not damaged.") %
242 {'repo_name': c.repo_name},
242 {'repo_name': c.repo_name},
243 category='error', ignore_duplicate=True)
243 category='error', ignore_duplicate=True)
244 raise HTTPFound(h.route_path('home'))
244 raise HTTPFound(h.route_path('home'))
245
245
246 return c
246 return c
247
247
248 def _get_f_path(self, matchdict, default=None):
248 def _get_f_path(self, matchdict, default=None):
249 f_path = matchdict.get('f_path')
249 f_path = matchdict.get('f_path')
250 if f_path:
250 if f_path:
251 # fix for multiple initial slashes that causes errors for GIT
251 # fix for multiple initial slashes that causes errors for GIT
252 return f_path.lstrip('/')
252 return f_path.lstrip('/')
253
253
254 return default
254 return default
255
255
256
256
257 class RepoGroupAppView(BaseAppView):
257 class RepoGroupAppView(BaseAppView):
258 def __init__(self, context, request):
258 def __init__(self, context, request):
259 super(RepoGroupAppView, self).__init__(context, request)
259 super(RepoGroupAppView, self).__init__(context, request)
260 self.db_repo_group = request.db_repo_group
260 self.db_repo_group = request.db_repo_group
261 self.db_repo_group_name = self.db_repo_group.group_name
261 self.db_repo_group_name = self.db_repo_group.group_name
262
262
263 def _revoke_perms_on_yourself(self, form_result):
264 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
265 form_result['perm_updates'])
266 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
267 form_result['perm_additions'])
268 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
269 form_result['perm_deletions'])
270 admin_perm = 'group.admin'
271 if _updates and _updates[0][1] != admin_perm or \
272 _additions and _additions[0][1] != admin_perm or \
273 _deletions and _deletions[0][1] != admin_perm:
274 return True
275 return False
276
263
277
264 class UserGroupAppView(BaseAppView):
278 class UserGroupAppView(BaseAppView):
265 def __init__(self, context, request):
279 def __init__(self, context, request):
266 super(UserGroupAppView, self).__init__(context, request)
280 super(UserGroupAppView, self).__init__(context, request)
267 self.db_user_group = request.db_user_group
281 self.db_user_group = request.db_user_group
268 self.db_user_group_name = self.db_user_group.users_group_name
282 self.db_user_group_name = self.db_user_group.users_group_name
269
283
270
284
271 class UserAppView(BaseAppView):
285 class UserAppView(BaseAppView):
272 def __init__(self, context, request):
286 def __init__(self, context, request):
273 super(UserAppView, self).__init__(context, request)
287 super(UserAppView, self).__init__(context, request)
274 self.db_user = request.db_user
288 self.db_user = request.db_user
275 self.db_user_id = self.db_user.user_id
289 self.db_user_id = self.db_user.user_id
276
290
277 _ = self.request.translate
291 _ = self.request.translate
278 if not request.db_user_supports_default:
292 if not request.db_user_supports_default:
279 if self.db_user.username == User.DEFAULT_USER:
293 if self.db_user.username == User.DEFAULT_USER:
280 h.flash(_("Editing user `{}` is disabled.".format(
294 h.flash(_("Editing user `{}` is disabled.".format(
281 User.DEFAULT_USER)), category='warning')
295 User.DEFAULT_USER)), category='warning')
282 raise HTTPFound(h.route_path('users'))
296 raise HTTPFound(h.route_path('users'))
283
297
284
298
285 class DataGridAppView(object):
299 class DataGridAppView(object):
286 """
300 """
287 Common class to have re-usable grid rendering components
301 Common class to have re-usable grid rendering components
288 """
302 """
289
303
290 def _extract_ordering(self, request, column_map=None):
304 def _extract_ordering(self, request, column_map=None):
291 column_map = column_map or {}
305 column_map = column_map or {}
292 column_index = safe_int(request.GET.get('order[0][column]'))
306 column_index = safe_int(request.GET.get('order[0][column]'))
293 order_dir = request.GET.get(
307 order_dir = request.GET.get(
294 'order[0][dir]', 'desc')
308 'order[0][dir]', 'desc')
295 order_by = request.GET.get(
309 order_by = request.GET.get(
296 'columns[%s][data][sort]' % column_index, 'name_raw')
310 'columns[%s][data][sort]' % column_index, 'name_raw')
297
311
298 # translate datatable to DB columns
312 # translate datatable to DB columns
299 order_by = column_map.get(order_by) or order_by
313 order_by = column_map.get(order_by) or order_by
300
314
301 search_q = request.GET.get('search[value]')
315 search_q = request.GET.get('search[value]')
302 return search_q, order_by, order_dir
316 return search_q, order_by, order_dir
303
317
304 def _extract_chunk(self, request):
318 def _extract_chunk(self, request):
305 start = safe_int(request.GET.get('start'), 0)
319 start = safe_int(request.GET.get('start'), 0)
306 length = safe_int(request.GET.get('length'), 25)
320 length = safe_int(request.GET.get('length'), 25)
307 draw = safe_int(request.GET.get('draw'))
321 draw = safe_int(request.GET.get('draw'))
308 return draw, start, length
322 return draw, start, length
309
323
310 def _get_order_col(self, order_by, model):
324 def _get_order_col(self, order_by, model):
311 if isinstance(order_by, basestring):
325 if isinstance(order_by, basestring):
312 try:
326 try:
313 return operator.attrgetter(order_by)(model)
327 return operator.attrgetter(order_by)(model)
314 except AttributeError:
328 except AttributeError:
315 return None
329 return None
316 else:
330 else:
317 return order_by
331 return order_by
318
332
319
333
320 class BaseReferencesView(RepoAppView):
334 class BaseReferencesView(RepoAppView):
321 """
335 """
322 Base for reference view for branches, tags and bookmarks.
336 Base for reference view for branches, tags and bookmarks.
323 """
337 """
324 def load_default_context(self):
338 def load_default_context(self):
325 c = self._get_local_tmpl_context()
339 c = self._get_local_tmpl_context()
326
340
327 self._register_global_c(c)
341 self._register_global_c(c)
328 return c
342 return c
329
343
330 def load_refs_context(self, ref_items, partials_template):
344 def load_refs_context(self, ref_items, partials_template):
331 _render = self.request.get_partial_renderer(partials_template)
345 _render = self.request.get_partial_renderer(partials_template)
332 pre_load = ["author", "date", "message"]
346 pre_load = ["author", "date", "message"]
333
347
334 is_svn = h.is_svn(self.rhodecode_vcs_repo)
348 is_svn = h.is_svn(self.rhodecode_vcs_repo)
335 is_hg = h.is_hg(self.rhodecode_vcs_repo)
349 is_hg = h.is_hg(self.rhodecode_vcs_repo)
336
350
337 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
351 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
338
352
339 closed_refs = {}
353 closed_refs = {}
340 if is_hg:
354 if is_hg:
341 closed_refs = self.rhodecode_vcs_repo.branches_closed
355 closed_refs = self.rhodecode_vcs_repo.branches_closed
342
356
343 data = []
357 data = []
344 for ref_name, commit_id in ref_items:
358 for ref_name, commit_id in ref_items:
345 commit = self.rhodecode_vcs_repo.get_commit(
359 commit = self.rhodecode_vcs_repo.get_commit(
346 commit_id=commit_id, pre_load=pre_load)
360 commit_id=commit_id, pre_load=pre_load)
347 closed = ref_name in closed_refs
361 closed = ref_name in closed_refs
348
362
349 # TODO: johbo: Unify generation of reference links
363 # TODO: johbo: Unify generation of reference links
350 use_commit_id = '/' in ref_name or is_svn
364 use_commit_id = '/' in ref_name or is_svn
351
365
352 if use_commit_id:
366 if use_commit_id:
353 files_url = h.route_path(
367 files_url = h.route_path(
354 'repo_files',
368 'repo_files',
355 repo_name=self.db_repo_name,
369 repo_name=self.db_repo_name,
356 f_path=ref_name if is_svn else '',
370 f_path=ref_name if is_svn else '',
357 commit_id=commit_id)
371 commit_id=commit_id)
358
372
359 else:
373 else:
360 files_url = h.route_path(
374 files_url = h.route_path(
361 'repo_files',
375 'repo_files',
362 repo_name=self.db_repo_name,
376 repo_name=self.db_repo_name,
363 f_path=ref_name if is_svn else '',
377 f_path=ref_name if is_svn else '',
364 commit_id=ref_name,
378 commit_id=ref_name,
365 _query=dict(at=ref_name))
379 _query=dict(at=ref_name))
366
380
367 data.append({
381 data.append({
368 "name": _render('name', ref_name, files_url, closed),
382 "name": _render('name', ref_name, files_url, closed),
369 "name_raw": ref_name,
383 "name_raw": ref_name,
370 "date": _render('date', commit.date),
384 "date": _render('date', commit.date),
371 "date_raw": datetime_to_time(commit.date),
385 "date_raw": datetime_to_time(commit.date),
372 "author": _render('author', commit.author),
386 "author": _render('author', commit.author),
373 "commit": _render(
387 "commit": _render(
374 'commit', commit.message, commit.raw_id, commit.idx),
388 'commit', commit.message, commit.raw_id, commit.idx),
375 "commit_raw": commit.idx,
389 "commit_raw": commit.idx,
376 "compare": _render(
390 "compare": _render(
377 'compare', format_ref_id(ref_name, commit.raw_id)),
391 'compare', format_ref_id(ref_name, commit.raw_id)),
378 })
392 })
379
393
380 return data
394 return data
381
395
382
396
383 class RepoRoutePredicate(object):
397 class RepoRoutePredicate(object):
384 def __init__(self, val, config):
398 def __init__(self, val, config):
385 self.val = val
399 self.val = val
386
400
387 def text(self):
401 def text(self):
388 return 'repo_route = %s' % self.val
402 return 'repo_route = %s' % self.val
389
403
390 phash = text
404 phash = text
391
405
392 def __call__(self, info, request):
406 def __call__(self, info, request):
393
407
394 if hasattr(request, 'vcs_call'):
408 if hasattr(request, 'vcs_call'):
395 # skip vcs calls
409 # skip vcs calls
396 return
410 return
397
411
398 repo_name = info['match']['repo_name']
412 repo_name = info['match']['repo_name']
399 repo_model = repo.RepoModel()
413 repo_model = repo.RepoModel()
400 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
414 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
401
415
402 def redirect_if_creating(db_repo):
416 def redirect_if_creating(db_repo):
403 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
417 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
404 raise HTTPFound(
418 raise HTTPFound(
405 request.route_path('repo_creating',
419 request.route_path('repo_creating',
406 repo_name=db_repo.repo_name))
420 repo_name=db_repo.repo_name))
407
421
408 if by_name_match:
422 if by_name_match:
409 # register this as request object we can re-use later
423 # register this as request object we can re-use later
410 request.db_repo = by_name_match
424 request.db_repo = by_name_match
411 redirect_if_creating(by_name_match)
425 redirect_if_creating(by_name_match)
412 return True
426 return True
413
427
414 by_id_match = repo_model.get_repo_by_id(repo_name)
428 by_id_match = repo_model.get_repo_by_id(repo_name)
415 if by_id_match:
429 if by_id_match:
416 request.db_repo = by_id_match
430 request.db_repo = by_id_match
417 redirect_if_creating(by_id_match)
431 redirect_if_creating(by_id_match)
418 return True
432 return True
419
433
420 return False
434 return False
421
435
422
436
423 class RepoTypeRoutePredicate(object):
437 class RepoTypeRoutePredicate(object):
424 def __init__(self, val, config):
438 def __init__(self, val, config):
425 self.val = val or ['hg', 'git', 'svn']
439 self.val = val or ['hg', 'git', 'svn']
426
440
427 def text(self):
441 def text(self):
428 return 'repo_accepted_type = %s' % self.val
442 return 'repo_accepted_type = %s' % self.val
429
443
430 phash = text
444 phash = text
431
445
432 def __call__(self, info, request):
446 def __call__(self, info, request):
433 if hasattr(request, 'vcs_call'):
447 if hasattr(request, 'vcs_call'):
434 # skip vcs calls
448 # skip vcs calls
435 return
449 return
436
450
437 rhodecode_db_repo = request.db_repo
451 rhodecode_db_repo = request.db_repo
438
452
439 log.debug(
453 log.debug(
440 '%s checking repo type for %s in %s',
454 '%s checking repo type for %s in %s',
441 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
455 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
442
456
443 if rhodecode_db_repo.repo_type in self.val:
457 if rhodecode_db_repo.repo_type in self.val:
444 return True
458 return True
445 else:
459 else:
446 log.warning('Current view is not supported for repo type:%s',
460 log.warning('Current view is not supported for repo type:%s',
447 rhodecode_db_repo.repo_type)
461 rhodecode_db_repo.repo_type)
448 #
462 #
449 # h.flash(h.literal(
463 # h.flash(h.literal(
450 # _('Action not supported for %s.' % rhodecode_repo.alias)),
464 # _('Action not supported for %s.' % rhodecode_repo.alias)),
451 # category='warning')
465 # category='warning')
452 # return redirect(
466 # return redirect(
453 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
467 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
454
468
455 return False
469 return False
456
470
457
471
458 class RepoGroupRoutePredicate(object):
472 class RepoGroupRoutePredicate(object):
459 def __init__(self, val, config):
473 def __init__(self, val, config):
460 self.val = val
474 self.val = val
461
475
462 def text(self):
476 def text(self):
463 return 'repo_group_route = %s' % self.val
477 return 'repo_group_route = %s' % self.val
464
478
465 phash = text
479 phash = text
466
480
467 def __call__(self, info, request):
481 def __call__(self, info, request):
468 if hasattr(request, 'vcs_call'):
482 if hasattr(request, 'vcs_call'):
469 # skip vcs calls
483 # skip vcs calls
470 return
484 return
471
485
472 repo_group_name = info['match']['repo_group_name']
486 repo_group_name = info['match']['repo_group_name']
473 repo_group_model = repo_group.RepoGroupModel()
487 repo_group_model = repo_group.RepoGroupModel()
474 by_name_match = repo_group_model.get_by_group_name(
488 by_name_match = repo_group_model.get_by_group_name(
475 repo_group_name, cache=True)
489 repo_group_name, cache=True)
476
490
477 if by_name_match:
491 if by_name_match:
478 # register this as request object we can re-use later
492 # register this as request object we can re-use later
479 request.db_repo_group = by_name_match
493 request.db_repo_group = by_name_match
480 return True
494 return True
481
495
482 return False
496 return False
483
497
484
498
485 class UserGroupRoutePredicate(object):
499 class UserGroupRoutePredicate(object):
486 def __init__(self, val, config):
500 def __init__(self, val, config):
487 self.val = val
501 self.val = val
488
502
489 def text(self):
503 def text(self):
490 return 'user_group_route = %s' % self.val
504 return 'user_group_route = %s' % self.val
491
505
492 phash = text
506 phash = text
493
507
494 def __call__(self, info, request):
508 def __call__(self, info, request):
495 if hasattr(request, 'vcs_call'):
509 if hasattr(request, 'vcs_call'):
496 # skip vcs calls
510 # skip vcs calls
497 return
511 return
498
512
499 user_group_id = info['match']['user_group_id']
513 user_group_id = info['match']['user_group_id']
500 user_group_model = user_group.UserGroup()
514 user_group_model = user_group.UserGroup()
501 by_id_match = user_group_model.get(
515 by_id_match = user_group_model.get(
502 user_group_id, cache=True)
516 user_group_id, cache=True)
503
517
504 if by_id_match:
518 if by_id_match:
505 # register this as request object we can re-use later
519 # register this as request object we can re-use later
506 request.db_user_group = by_id_match
520 request.db_user_group = by_id_match
507 return True
521 return True
508
522
509 return False
523 return False
510
524
511
525
512 class UserRoutePredicateBase(object):
526 class UserRoutePredicateBase(object):
513 supports_default = None
527 supports_default = None
514
528
515 def __init__(self, val, config):
529 def __init__(self, val, config):
516 self.val = val
530 self.val = val
517
531
518 def text(self):
532 def text(self):
519 raise NotImplementedError()
533 raise NotImplementedError()
520
534
521 def __call__(self, info, request):
535 def __call__(self, info, request):
522 if hasattr(request, 'vcs_call'):
536 if hasattr(request, 'vcs_call'):
523 # skip vcs calls
537 # skip vcs calls
524 return
538 return
525
539
526 user_id = info['match']['user_id']
540 user_id = info['match']['user_id']
527 user_model = user.User()
541 user_model = user.User()
528 by_id_match = user_model.get(
542 by_id_match = user_model.get(
529 user_id, cache=True)
543 user_id, cache=True)
530
544
531 if by_id_match:
545 if by_id_match:
532 # register this as request object we can re-use later
546 # register this as request object we can re-use later
533 request.db_user = by_id_match
547 request.db_user = by_id_match
534 request.db_user_supports_default = self.supports_default
548 request.db_user_supports_default = self.supports_default
535 return True
549 return True
536
550
537 return False
551 return False
538
552
539
553
540 class UserRoutePredicate(UserRoutePredicateBase):
554 class UserRoutePredicate(UserRoutePredicateBase):
541 supports_default = False
555 supports_default = False
542
556
543 def text(self):
557 def text(self):
544 return 'user_route = %s' % self.val
558 return 'user_route = %s' % self.val
545
559
546 phash = text
560 phash = text
547
561
548
562
549 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
563 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
550 supports_default = True
564 supports_default = True
551
565
552 def text(self):
566 def text(self):
553 return 'user_with_default_route = %s' % self.val
567 return 'user_with_default_route = %s' % self.val
554
568
555 phash = text
569 phash = text
556
570
557
571
558 def includeme(config):
572 def includeme(config):
559 config.add_route_predicate(
573 config.add_route_predicate(
560 'repo_route', RepoRoutePredicate)
574 'repo_route', RepoRoutePredicate)
561 config.add_route_predicate(
575 config.add_route_predicate(
562 'repo_accepted_types', RepoTypeRoutePredicate)
576 'repo_accepted_types', RepoTypeRoutePredicate)
563 config.add_route_predicate(
577 config.add_route_predicate(
564 'repo_group_route', RepoGroupRoutePredicate)
578 'repo_group_route', RepoGroupRoutePredicate)
565 config.add_route_predicate(
579 config.add_route_predicate(
566 'user_group_route', UserGroupRoutePredicate)
580 'user_group_route', UserGroupRoutePredicate)
567 config.add_route_predicate(
581 config.add_route_predicate(
568 'user_route_with_default', UserRouteWithDefaultPredicate)
582 'user_route_with_default', UserRouteWithDefaultPredicate)
569 config.add_route_predicate(
583 config.add_route_predicate(
570 'user_route', UserRoutePredicate) No newline at end of file
584 'user_route', UserRoutePredicate)
@@ -1,312 +1,325 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.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='admin_audit_log_entry',
37 name='admin_audit_log_entry',
38 pattern='/audit_logs/{audit_log_id}')
38 pattern='/audit_logs/{audit_log_id}')
39
39
40 config.add_route(
40 config.add_route(
41 name='pull_requests_global_0', # backward compat
41 name='pull_requests_global_0', # backward compat
42 pattern='/pull_requests/{pull_request_id:\d+}')
42 pattern='/pull_requests/{pull_request_id:\d+}')
43 config.add_route(
43 config.add_route(
44 name='pull_requests_global_1', # backward compat
44 name='pull_requests_global_1', # backward compat
45 pattern='/pull-requests/{pull_request_id:\d+}')
45 pattern='/pull-requests/{pull_request_id:\d+}')
46 config.add_route(
46 config.add_route(
47 name='pull_requests_global',
47 name='pull_requests_global',
48 pattern='/pull-request/{pull_request_id:\d+}')
48 pattern='/pull-request/{pull_request_id:\d+}')
49
49
50 config.add_route(
50 config.add_route(
51 name='admin_settings_open_source',
51 name='admin_settings_open_source',
52 pattern='/settings/open_source')
52 pattern='/settings/open_source')
53 config.add_route(
53 config.add_route(
54 name='admin_settings_vcs_svn_generate_cfg',
54 name='admin_settings_vcs_svn_generate_cfg',
55 pattern='/settings/vcs/svn_generate_cfg')
55 pattern='/settings/vcs/svn_generate_cfg')
56
56
57 config.add_route(
57 config.add_route(
58 name='admin_settings_system',
58 name='admin_settings_system',
59 pattern='/settings/system')
59 pattern='/settings/system')
60 config.add_route(
60 config.add_route(
61 name='admin_settings_system_update',
61 name='admin_settings_system_update',
62 pattern='/settings/system/updates')
62 pattern='/settings/system/updates')
63
63
64 config.add_route(
64 config.add_route(
65 name='admin_settings_sessions',
65 name='admin_settings_sessions',
66 pattern='/settings/sessions')
66 pattern='/settings/sessions')
67 config.add_route(
67 config.add_route(
68 name='admin_settings_sessions_cleanup',
68 name='admin_settings_sessions_cleanup',
69 pattern='/settings/sessions/cleanup')
69 pattern='/settings/sessions/cleanup')
70
70
71 config.add_route(
71 config.add_route(
72 name='admin_settings_process_management',
72 name='admin_settings_process_management',
73 pattern='/settings/process_management')
73 pattern='/settings/process_management')
74 config.add_route(
74 config.add_route(
75 name='admin_settings_process_management_signal',
75 name='admin_settings_process_management_signal',
76 pattern='/settings/process_management/signal')
76 pattern='/settings/process_management/signal')
77
77
78 # default settings
78 # default settings
79 config.add_route(
79 config.add_route(
80 name='admin_defaults_repositories',
80 name='admin_defaults_repositories',
81 pattern='/defaults/repositories')
81 pattern='/defaults/repositories')
82 config.add_route(
82 config.add_route(
83 name='admin_defaults_repositories_update',
83 name='admin_defaults_repositories_update',
84 pattern='/defaults/repositories/update')
84 pattern='/defaults/repositories/update')
85
85
86 # global permissions
86 # global permissions
87
87
88 config.add_route(
88 config.add_route(
89 name='admin_permissions_application',
89 name='admin_permissions_application',
90 pattern='/permissions/application')
90 pattern='/permissions/application')
91 config.add_route(
91 config.add_route(
92 name='admin_permissions_application_update',
92 name='admin_permissions_application_update',
93 pattern='/permissions/application/update')
93 pattern='/permissions/application/update')
94
94
95 config.add_route(
95 config.add_route(
96 name='admin_permissions_global',
96 name='admin_permissions_global',
97 pattern='/permissions/global')
97 pattern='/permissions/global')
98 config.add_route(
98 config.add_route(
99 name='admin_permissions_global_update',
99 name='admin_permissions_global_update',
100 pattern='/permissions/global/update')
100 pattern='/permissions/global/update')
101
101
102 config.add_route(
102 config.add_route(
103 name='admin_permissions_object',
103 name='admin_permissions_object',
104 pattern='/permissions/object')
104 pattern='/permissions/object')
105 config.add_route(
105 config.add_route(
106 name='admin_permissions_object_update',
106 name='admin_permissions_object_update',
107 pattern='/permissions/object/update')
107 pattern='/permissions/object/update')
108
108
109 config.add_route(
109 config.add_route(
110 name='admin_permissions_ips',
110 name='admin_permissions_ips',
111 pattern='/permissions/ips')
111 pattern='/permissions/ips')
112
112
113 config.add_route(
113 config.add_route(
114 name='admin_permissions_overview',
114 name='admin_permissions_overview',
115 pattern='/permissions/overview')
115 pattern='/permissions/overview')
116
116
117 config.add_route(
117 config.add_route(
118 name='admin_permissions_auth_token_access',
118 name='admin_permissions_auth_token_access',
119 pattern='/permissions/auth_token_access')
119 pattern='/permissions/auth_token_access')
120
120
121 config.add_route(
121 config.add_route(
122 name='admin_permissions_ssh_keys',
122 name='admin_permissions_ssh_keys',
123 pattern='/permissions/ssh_keys')
123 pattern='/permissions/ssh_keys')
124 config.add_route(
124 config.add_route(
125 name='admin_permissions_ssh_keys_data',
125 name='admin_permissions_ssh_keys_data',
126 pattern='/permissions/ssh_keys/data')
126 pattern='/permissions/ssh_keys/data')
127 config.add_route(
127 config.add_route(
128 name='admin_permissions_ssh_keys_update',
128 name='admin_permissions_ssh_keys_update',
129 pattern='/permissions/ssh_keys/update')
129 pattern='/permissions/ssh_keys/update')
130
130
131 # users admin
131 # users admin
132 config.add_route(
132 config.add_route(
133 name='users',
133 name='users',
134 pattern='/users')
134 pattern='/users')
135
135
136 config.add_route(
136 config.add_route(
137 name='users_data',
137 name='users_data',
138 pattern='/users_data')
138 pattern='/users_data')
139
139
140 config.add_route(
140 config.add_route(
141 name='users_create',
141 name='users_create',
142 pattern='/users/create')
142 pattern='/users/create')
143
143
144 config.add_route(
144 config.add_route(
145 name='users_new',
145 name='users_new',
146 pattern='/users/new')
146 pattern='/users/new')
147
147
148 # user management
148 # user management
149 config.add_route(
149 config.add_route(
150 name='user_edit',
150 name='user_edit',
151 pattern='/users/{user_id:\d+}/edit',
151 pattern='/users/{user_id:\d+}/edit',
152 user_route=True)
152 user_route=True)
153 config.add_route(
153 config.add_route(
154 name='user_edit_advanced',
154 name='user_edit_advanced',
155 pattern='/users/{user_id:\d+}/edit/advanced',
155 pattern='/users/{user_id:\d+}/edit/advanced',
156 user_route=True)
156 user_route=True)
157 config.add_route(
157 config.add_route(
158 name='user_edit_global_perms',
158 name='user_edit_global_perms',
159 pattern='/users/{user_id:\d+}/edit/global_permissions',
159 pattern='/users/{user_id:\d+}/edit/global_permissions',
160 user_route=True)
160 user_route=True)
161 config.add_route(
161 config.add_route(
162 name='user_edit_global_perms_update',
162 name='user_edit_global_perms_update',
163 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
163 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
164 user_route=True)
164 user_route=True)
165 config.add_route(
165 config.add_route(
166 name='user_update',
166 name='user_update',
167 pattern='/users/{user_id:\d+}/update',
167 pattern='/users/{user_id:\d+}/update',
168 user_route=True)
168 user_route=True)
169 config.add_route(
169 config.add_route(
170 name='user_delete',
170 name='user_delete',
171 pattern='/users/{user_id:\d+}/delete',
171 pattern='/users/{user_id:\d+}/delete',
172 user_route=True)
172 user_route=True)
173 config.add_route(
173 config.add_route(
174 name='user_force_password_reset',
174 name='user_force_password_reset',
175 pattern='/users/{user_id:\d+}/password_reset',
175 pattern='/users/{user_id:\d+}/password_reset',
176 user_route=True)
176 user_route=True)
177 config.add_route(
177 config.add_route(
178 name='user_create_personal_repo_group',
178 name='user_create_personal_repo_group',
179 pattern='/users/{user_id:\d+}/create_repo_group',
179 pattern='/users/{user_id:\d+}/create_repo_group',
180 user_route=True)
180 user_route=True)
181
181
182 # user auth tokens
182 # user auth tokens
183 config.add_route(
183 config.add_route(
184 name='edit_user_auth_tokens',
184 name='edit_user_auth_tokens',
185 pattern='/users/{user_id:\d+}/edit/auth_tokens',
185 pattern='/users/{user_id:\d+}/edit/auth_tokens',
186 user_route=True)
186 user_route=True)
187 config.add_route(
187 config.add_route(
188 name='edit_user_auth_tokens_add',
188 name='edit_user_auth_tokens_add',
189 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
189 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
190 user_route=True)
190 user_route=True)
191 config.add_route(
191 config.add_route(
192 name='edit_user_auth_tokens_delete',
192 name='edit_user_auth_tokens_delete',
193 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
193 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
194 user_route=True)
194 user_route=True)
195
195
196 # user ssh keys
196 # user ssh keys
197 config.add_route(
197 config.add_route(
198 name='edit_user_ssh_keys',
198 name='edit_user_ssh_keys',
199 pattern='/users/{user_id:\d+}/edit/ssh_keys',
199 pattern='/users/{user_id:\d+}/edit/ssh_keys',
200 user_route=True)
200 user_route=True)
201 config.add_route(
201 config.add_route(
202 name='edit_user_ssh_keys_generate_keypair',
202 name='edit_user_ssh_keys_generate_keypair',
203 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
203 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
204 user_route=True)
204 user_route=True)
205 config.add_route(
205 config.add_route(
206 name='edit_user_ssh_keys_add',
206 name='edit_user_ssh_keys_add',
207 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
207 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
208 user_route=True)
208 user_route=True)
209 config.add_route(
209 config.add_route(
210 name='edit_user_ssh_keys_delete',
210 name='edit_user_ssh_keys_delete',
211 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
211 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
212 user_route=True)
212 user_route=True)
213
213
214 # user emails
214 # user emails
215 config.add_route(
215 config.add_route(
216 name='edit_user_emails',
216 name='edit_user_emails',
217 pattern='/users/{user_id:\d+}/edit/emails',
217 pattern='/users/{user_id:\d+}/edit/emails',
218 user_route=True)
218 user_route=True)
219 config.add_route(
219 config.add_route(
220 name='edit_user_emails_add',
220 name='edit_user_emails_add',
221 pattern='/users/{user_id:\d+}/edit/emails/new',
221 pattern='/users/{user_id:\d+}/edit/emails/new',
222 user_route=True)
222 user_route=True)
223 config.add_route(
223 config.add_route(
224 name='edit_user_emails_delete',
224 name='edit_user_emails_delete',
225 pattern='/users/{user_id:\d+}/edit/emails/delete',
225 pattern='/users/{user_id:\d+}/edit/emails/delete',
226 user_route=True)
226 user_route=True)
227
227
228 # user IPs
228 # user IPs
229 config.add_route(
229 config.add_route(
230 name='edit_user_ips',
230 name='edit_user_ips',
231 pattern='/users/{user_id:\d+}/edit/ips',
231 pattern='/users/{user_id:\d+}/edit/ips',
232 user_route=True)
232 user_route=True)
233 config.add_route(
233 config.add_route(
234 name='edit_user_ips_add',
234 name='edit_user_ips_add',
235 pattern='/users/{user_id:\d+}/edit/ips/new',
235 pattern='/users/{user_id:\d+}/edit/ips/new',
236 user_route_with_default=True) # enabled for default user too
236 user_route_with_default=True) # enabled for default user too
237 config.add_route(
237 config.add_route(
238 name='edit_user_ips_delete',
238 name='edit_user_ips_delete',
239 pattern='/users/{user_id:\d+}/edit/ips/delete',
239 pattern='/users/{user_id:\d+}/edit/ips/delete',
240 user_route_with_default=True) # enabled for default user too
240 user_route_with_default=True) # enabled for default user too
241
241
242 # user perms
242 # user perms
243 config.add_route(
243 config.add_route(
244 name='edit_user_perms_summary',
244 name='edit_user_perms_summary',
245 pattern='/users/{user_id:\d+}/edit/permissions_summary',
245 pattern='/users/{user_id:\d+}/edit/permissions_summary',
246 user_route=True)
246 user_route=True)
247 config.add_route(
247 config.add_route(
248 name='edit_user_perms_summary_json',
248 name='edit_user_perms_summary_json',
249 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
249 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
250 user_route=True)
250 user_route=True)
251
251
252 # user user groups management
252 # user user groups management
253 config.add_route(
253 config.add_route(
254 name='edit_user_groups_management',
254 name='edit_user_groups_management',
255 pattern='/users/{user_id:\d+}/edit/groups_management',
255 pattern='/users/{user_id:\d+}/edit/groups_management',
256 user_route=True)
256 user_route=True)
257
257
258 config.add_route(
258 config.add_route(
259 name='edit_user_groups_management_updates',
259 name='edit_user_groups_management_updates',
260 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
260 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
261 user_route=True)
261 user_route=True)
262
262
263 # user audit logs
263 # user audit logs
264 config.add_route(
264 config.add_route(
265 name='edit_user_audit_logs',
265 name='edit_user_audit_logs',
266 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
266 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
267
267
268 # user-groups admin
268 # user-groups admin
269 config.add_route(
269 config.add_route(
270 name='user_groups',
270 name='user_groups',
271 pattern='/user_groups')
271 pattern='/user_groups')
272
272
273 config.add_route(
273 config.add_route(
274 name='user_groups_data',
274 name='user_groups_data',
275 pattern='/user_groups_data')
275 pattern='/user_groups_data')
276
276
277 config.add_route(
277 config.add_route(
278 name='user_groups_new',
278 name='user_groups_new',
279 pattern='/user_groups/new')
279 pattern='/user_groups/new')
280
280
281 config.add_route(
281 config.add_route(
282 name='user_groups_create',
282 name='user_groups_create',
283 pattern='/user_groups/create')
283 pattern='/user_groups/create')
284
284
285 # repos admin
285 # repos admin
286 config.add_route(
286 config.add_route(
287 name='repos',
287 name='repos',
288 pattern='/repos')
288 pattern='/repos')
289
289
290 config.add_route(
290 config.add_route(
291 name='repo_new',
291 name='repo_new',
292 pattern='/repos/new')
292 pattern='/repos/new')
293
293
294 config.add_route(
294 config.add_route(
295 name='repo_create',
295 name='repo_create',
296 pattern='/repos/create')
296 pattern='/repos/create')
297
297
298 # repo groups admin
299 config.add_route(
300 name='repo_groups',
301 pattern='/repo_groups')
302
303 config.add_route(
304 name='repo_group_new',
305 pattern='/repo_group/new')
306
307 config.add_route(
308 name='repo_group_create',
309 pattern='/repo_group/create')
310
298
311
299 def includeme(config):
312 def includeme(config):
300 settings = config.get_settings()
313 settings = config.get_settings()
301
314
302 # Create admin navigation registry and add it to the pyramid registry.
315 # Create admin navigation registry and add it to the pyramid registry.
303 labs_active = str2bool(settings.get('labs_settings_active', False))
316 labs_active = str2bool(settings.get('labs_settings_active', False))
304 navigation_registry = NavigationRegistry(labs_active=labs_active)
317 navigation_registry = NavigationRegistry(labs_active=labs_active)
305 config.registry.registerUtility(navigation_registry)
318 config.registry.registerUtility(navigation_registry)
306
319
307 # main admin routes
320 # main admin routes
308 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
321 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
309 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
322 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
310
323
311 # Scan module for configuration decorators.
324 # Scan module for configuration decorators.
312 config.scan('.views', ignore='.tests')
325 config.scan('.views', ignore='.tests')
@@ -1,33 +1,61 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 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # Summary
25 # Settings
26 config.add_route(
27 name='edit_repo_group',
28 pattern='/{repo_group_name:.*?[^/]}/_edit',
29 repo_group_route=True)
30 # update is POST on edit_repo_group
31
32 # Settings advanced
33 config.add_route(
34 name='edit_repo_group_advanced',
35 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced',
36 repo_group_route=True)
37
38 config.add_route(
39 name='edit_repo_group_advanced_delete',
40 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced/delete',
41 repo_group_route=True)
42
43 # settings permissions
44 config.add_route(
45 name='edit_repo_group_perms',
46 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions',
47 repo_group_route=True)
48
49 config.add_route(
50 name='edit_repo_group_perms_update',
51 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions/update',
52 repo_group_route=True)
53
54 # Summary, NOTE(marcink): needs to be at the end for catch-all
26 add_route_with_slash(
55 add_route_with_slash(
27 config,
56 config,
28 name='repo_group_home',
57 name='repo_group_home',
29 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
58 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
30
59
31 # Scan module for configuration decorators.
60 # Scan module for configuration decorators.
32 config.scan('.views', ignore='.tests')
61 config.scan('.views', ignore='.tests')
33
@@ -0,0 +1,19 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/
@@ -1,455 +1,455 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 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84
84
85 # still working url for backward compat.
85 # still working url for backward compat.
86 config.add_route(
86 config.add_route(
87 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89
89
90 # Files
90 # Files
91 config.add_route(
91 config.add_route(
92 name='repo_archivefile',
92 name='repo_archivefile',
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94
94
95 config.add_route(
95 config.add_route(
96 name='repo_files_diff',
96 name='repo_files_diff',
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
99 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101
101
102 config.add_route(
102 config.add_route(
103 name='repo_files',
103 name='repo_files',
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 config.add_route(
105 config.add_route(
106 name='repo_files:default_path',
106 name='repo_files:default_path',
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files:default_commit',
109 name='repo_files:default_commit',
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111
111
112 config.add_route(
112 config.add_route(
113 name='repo_files:rendered',
113 name='repo_files:rendered',
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115
115
116 config.add_route(
116 config.add_route(
117 name='repo_files:annotated',
117 name='repo_files:annotated',
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 config.add_route(
119 config.add_route(
120 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122
122
123 config.add_route(
123 config.add_route(
124 name='repo_nodetree_full',
124 name='repo_nodetree_full',
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 config.add_route(
126 config.add_route(
127 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129
129
130 config.add_route(
130 config.add_route(
131 name='repo_files_nodelist',
131 name='repo_files_nodelist',
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_file_raw',
135 name='repo_file_raw',
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_file_download',
139 name='repo_file_download',
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
142 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 repo_route=True)
144 repo_route=True)
145
145
146 config.add_route(
146 config.add_route(
147 name='repo_file_history',
147 name='repo_file_history',
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149
149
150 config.add_route(
150 config.add_route(
151 name='repo_file_authors',
151 name='repo_file_authors',
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_files_remove_file',
155 name='repo_files_remove_file',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
157 repo_route=True)
158 config.add_route(
158 config.add_route(
159 name='repo_files_delete_file',
159 name='repo_files_delete_file',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
161 repo_route=True)
162 config.add_route(
162 config.add_route(
163 name='repo_files_edit_file',
163 name='repo_files_edit_file',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
165 repo_route=True)
166 config.add_route(
166 config.add_route(
167 name='repo_files_update_file',
167 name='repo_files_update_file',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
169 repo_route=True)
170 config.add_route(
170 config.add_route(
171 name='repo_files_add_file',
171 name='repo_files_add_file',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
173 repo_route=True)
174 config.add_route(
174 config.add_route(
175 name='repo_files_create_file',
175 name='repo_files_create_file',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
177 repo_route=True)
178
178
179 # Refs data
179 # Refs data
180 config.add_route(
180 config.add_route(
181 name='repo_refs_data',
181 name='repo_refs_data',
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183
183
184 config.add_route(
184 config.add_route(
185 name='repo_refs_changelog_data',
185 name='repo_refs_changelog_data',
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187
187
188 config.add_route(
188 config.add_route(
189 name='repo_stats',
189 name='repo_stats',
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191
191
192 # Changelog
192 # Changelog
193 config.add_route(
193 config.add_route(
194 name='repo_changelog',
194 name='repo_changelog',
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 config.add_route(
196 config.add_route(
197 name='repo_changelog_file',
197 name='repo_changelog_file',
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 config.add_route(
199 config.add_route(
200 name='repo_changelog_elements',
200 name='repo_changelog_elements',
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 config.add_route(
202 config.add_route(
203 name='repo_changelog_elements_file',
203 name='repo_changelog_elements_file',
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205
205
206 # Compare
206 # Compare
207 config.add_route(
207 config.add_route(
208 name='repo_compare_select',
208 name='repo_compare_select',
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210
210
211 config.add_route(
211 config.add_route(
212 name='repo_compare',
212 name='repo_compare',
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214
214
215 # Tags
215 # Tags
216 config.add_route(
216 config.add_route(
217 name='tags_home',
217 name='tags_home',
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219
219
220 # Branches
220 # Branches
221 config.add_route(
221 config.add_route(
222 name='branches_home',
222 name='branches_home',
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224
224
225 # Bookmarks
225 # Bookmarks
226 config.add_route(
226 config.add_route(
227 name='bookmarks_home',
227 name='bookmarks_home',
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229
229
230 # Forks
230 # Forks
231 config.add_route(
231 config.add_route(
232 name='repo_fork_new',
232 name='repo_fork_new',
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 repo_accepted_types=['hg', 'git'])
234 repo_accepted_types=['hg', 'git'])
235
235
236 config.add_route(
236 config.add_route(
237 name='repo_fork_create',
237 name='repo_fork_create',
238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 repo_accepted_types=['hg', 'git'])
239 repo_accepted_types=['hg', 'git'])
240
240
241 config.add_route(
241 config.add_route(
242 name='repo_forks_show_all',
242 name='repo_forks_show_all',
243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
244 repo_accepted_types=['hg', 'git'])
244 repo_accepted_types=['hg', 'git'])
245 config.add_route(
245 config.add_route(
246 name='repo_forks_data',
246 name='repo_forks_data',
247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
248 repo_accepted_types=['hg', 'git'])
248 repo_accepted_types=['hg', 'git'])
249
249
250 # Pull Requests
250 # Pull Requests
251 config.add_route(
251 config.add_route(
252 name='pullrequest_show',
252 name='pullrequest_show',
253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
254 repo_route=True)
254 repo_route=True)
255
255
256 config.add_route(
256 config.add_route(
257 name='pullrequest_show_all',
257 name='pullrequest_show_all',
258 pattern='/{repo_name:.*?[^/]}/pull-request',
258 pattern='/{repo_name:.*?[^/]}/pull-request',
259 repo_route=True, repo_accepted_types=['hg', 'git'])
259 repo_route=True, repo_accepted_types=['hg', 'git'])
260
260
261 config.add_route(
261 config.add_route(
262 name='pullrequest_show_all_data',
262 name='pullrequest_show_all_data',
263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
264 repo_route=True, repo_accepted_types=['hg', 'git'])
264 repo_route=True, repo_accepted_types=['hg', 'git'])
265
265
266 config.add_route(
266 config.add_route(
267 name='pullrequest_repo_refs',
267 name='pullrequest_repo_refs',
268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
269 repo_route=True)
269 repo_route=True)
270
270
271 config.add_route(
271 config.add_route(
272 name='pullrequest_repo_destinations',
272 name='pullrequest_repo_destinations',
273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
274 repo_route=True)
274 repo_route=True)
275
275
276 config.add_route(
276 config.add_route(
277 name='pullrequest_new',
277 name='pullrequest_new',
278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
279 repo_route=True, repo_accepted_types=['hg', 'git'])
279 repo_route=True, repo_accepted_types=['hg', 'git'])
280
280
281 config.add_route(
281 config.add_route(
282 name='pullrequest_create',
282 name='pullrequest_create',
283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
284 repo_route=True, repo_accepted_types=['hg', 'git'])
284 repo_route=True, repo_accepted_types=['hg', 'git'])
285
285
286 config.add_route(
286 config.add_route(
287 name='pullrequest_update',
287 name='pullrequest_update',
288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
289 repo_route=True)
289 repo_route=True)
290
290
291 config.add_route(
291 config.add_route(
292 name='pullrequest_merge',
292 name='pullrequest_merge',
293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
294 repo_route=True)
294 repo_route=True)
295
295
296 config.add_route(
296 config.add_route(
297 name='pullrequest_delete',
297 name='pullrequest_delete',
298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
299 repo_route=True)
299 repo_route=True)
300
300
301 config.add_route(
301 config.add_route(
302 name='pullrequest_comment_create',
302 name='pullrequest_comment_create',
303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
304 repo_route=True)
304 repo_route=True)
305
305
306 config.add_route(
306 config.add_route(
307 name='pullrequest_comment_delete',
307 name='pullrequest_comment_delete',
308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
309 repo_route=True, repo_accepted_types=['hg', 'git'])
309 repo_route=True, repo_accepted_types=['hg', 'git'])
310
310
311 # Settings
311 # Settings
312 config.add_route(
312 config.add_route(
313 name='edit_repo',
313 name='edit_repo',
314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
315 # update is POST on edit_repo
315
316
316 # Settings advanced
317 # Settings advanced
317 config.add_route(
318 config.add_route(
318 name='edit_repo_advanced',
319 name='edit_repo_advanced',
319 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
320 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
320 config.add_route(
321 config.add_route(
321 name='edit_repo_advanced_delete',
322 name='edit_repo_advanced_delete',
322 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
323 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
323 config.add_route(
324 config.add_route(
324 name='edit_repo_advanced_locking',
325 name='edit_repo_advanced_locking',
325 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
326 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
326 config.add_route(
327 config.add_route(
327 name='edit_repo_advanced_journal',
328 name='edit_repo_advanced_journal',
328 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
329 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
329 config.add_route(
330 config.add_route(
330 name='edit_repo_advanced_fork',
331 name='edit_repo_advanced_fork',
331 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
332 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
332
333
333 # Caches
334 # Caches
334 config.add_route(
335 config.add_route(
335 name='edit_repo_caches',
336 name='edit_repo_caches',
336 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
337 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
337
338
338 # Permissions
339 # Permissions
339 config.add_route(
340 config.add_route(
340 name='edit_repo_perms',
341 name='edit_repo_perms',
341 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
342 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
342
343
343 # Maintenance
344 # Maintenance
344 config.add_route(
345 config.add_route(
345 name='edit_repo_maintenance',
346 name='edit_repo_maintenance',
346 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
347 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
347
348
348 config.add_route(
349 config.add_route(
349 name='edit_repo_maintenance_execute',
350 name='edit_repo_maintenance_execute',
350 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
351 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
351
352
352 # Fields
353 # Fields
353 config.add_route(
354 config.add_route(
354 name='edit_repo_fields',
355 name='edit_repo_fields',
355 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
356 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
356 config.add_route(
357 config.add_route(
357 name='edit_repo_fields_create',
358 name='edit_repo_fields_create',
358 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
359 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
359 config.add_route(
360 config.add_route(
360 name='edit_repo_fields_delete',
361 name='edit_repo_fields_delete',
361 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
362 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
362
363
363 # Locking
364 # Locking
364 config.add_route(
365 config.add_route(
365 name='repo_edit_toggle_locking',
366 name='repo_edit_toggle_locking',
366 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
367 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
367
368
368 # Remote
369 # Remote
369 config.add_route(
370 config.add_route(
370 name='edit_repo_remote',
371 name='edit_repo_remote',
371 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
372 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
372 config.add_route(
373 config.add_route(
373 name='edit_repo_remote_pull',
374 name='edit_repo_remote_pull',
374 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
375 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
375
376
376
377 # Statistics
377 # Statistics
378 config.add_route(
378 config.add_route(
379 name='edit_repo_statistics',
379 name='edit_repo_statistics',
380 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
380 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
381 config.add_route(
381 config.add_route(
382 name='edit_repo_statistics_reset',
382 name='edit_repo_statistics_reset',
383 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
383 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
384
384
385 # Issue trackers
385 # Issue trackers
386 config.add_route(
386 config.add_route(
387 name='edit_repo_issuetracker',
387 name='edit_repo_issuetracker',
388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
389 config.add_route(
389 config.add_route(
390 name='edit_repo_issuetracker_test',
390 name='edit_repo_issuetracker_test',
391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
392 config.add_route(
392 config.add_route(
393 name='edit_repo_issuetracker_delete',
393 name='edit_repo_issuetracker_delete',
394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
395 config.add_route(
395 config.add_route(
396 name='edit_repo_issuetracker_update',
396 name='edit_repo_issuetracker_update',
397 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
397 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
398
398
399 # VCS Settings
399 # VCS Settings
400 config.add_route(
400 config.add_route(
401 name='edit_repo_vcs',
401 name='edit_repo_vcs',
402 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
402 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
403 config.add_route(
403 config.add_route(
404 name='edit_repo_vcs_update',
404 name='edit_repo_vcs_update',
405 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
405 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
406
406
407 # svn pattern
407 # svn pattern
408 config.add_route(
408 config.add_route(
409 name='edit_repo_vcs_svn_pattern_delete',
409 name='edit_repo_vcs_svn_pattern_delete',
410 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
410 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
411
411
412 # Repo Review Rules (EE feature)
412 # Repo Review Rules (EE feature)
413 config.add_route(
413 config.add_route(
414 name='repo_reviewers',
414 name='repo_reviewers',
415 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
415 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
416
416
417 config.add_route(
417 config.add_route(
418 name='repo_default_reviewers_data',
418 name='repo_default_reviewers_data',
419 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
419 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
420
420
421 # Strip
421 # Strip
422 config.add_route(
422 config.add_route(
423 name='edit_repo_strip',
423 name='edit_repo_strip',
424 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
424 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
425
425
426 config.add_route(
426 config.add_route(
427 name='strip_check',
427 name='strip_check',
428 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
428 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
429
429
430 config.add_route(
430 config.add_route(
431 name='strip_execute',
431 name='strip_execute',
432 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
432 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
433
433
434 # Audit logs
434 # Audit logs
435 config.add_route(
435 config.add_route(
436 name='edit_repo_audit_logs',
436 name='edit_repo_audit_logs',
437 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
437 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
438
438
439 # ATOM/RSS Feed
439 # ATOM/RSS Feed
440 config.add_route(
440 config.add_route(
441 name='rss_feed_home',
441 name='rss_feed_home',
442 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
442 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
443
443
444 config.add_route(
444 config.add_route(
445 name='atom_feed_home',
445 name='atom_feed_home',
446 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
446 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
447
447
448 # NOTE(marcink): needs to be at the end for catch-all
448 # NOTE(marcink): needs to be at the end for catch-all
449 add_route_with_slash(
449 add_route_with_slash(
450 config,
450 config,
451 name='repo_summary',
451 name='repo_summary',
452 pattern='/{repo_name:.*?[^/]}', repo_route=True)
452 pattern='/{repo_name:.*?[^/]}', repo_route=True)
453
453
454 # Scan module for configuration decorators.
454 # Scan module for configuration decorators.
455 config.scan('.views', ignore='.tests')
455 config.scan('.views', ignore='.tests')
@@ -0,0 +1,19 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/
@@ -1,232 +1,232 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31
31
32 fixture = Fixture()
32 fixture = Fixture()
33
33
34
34
35 def route_path(name, params=None, **kwargs):
35 def route_path(name, params=None, **kwargs):
36 import urllib
36 import urllib
37
37
38 base_url = {
38 base_url = {
39 'edit_repo': '/{repo_name}/settings',
39 'edit_repo': '/{repo_name}/settings',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 'edit_repo_caches': '/{repo_name}/settings/caches',
41 'edit_repo_caches': '/{repo_name}/settings/caches',
42 'edit_repo_perms': '/{repo_name}/settings/permissions',
42 'edit_repo_perms': '/{repo_name}/settings/permissions',
43 'edit_repo_vcs': '/{repo_name}/settings/vcs',
43 'edit_repo_vcs': '/{repo_name}/settings/vcs',
44 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
44 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
45 'edit_repo_fields': '/{repo_name}/settings/fields',
45 'edit_repo_fields': '/{repo_name}/settings/fields',
46 'edit_repo_remote': '/{repo_name}/settings/remote',
46 'edit_repo_remote': '/{repo_name}/settings/remote',
47 'edit_repo_statistics': '/{repo_name}/settings/statistics',
47 'edit_repo_statistics': '/{repo_name}/settings/statistics',
48 }[name].format(**kwargs)
48 }[name].format(**kwargs)
49
49
50 if params:
50 if params:
51 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
51 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
52 return base_url
52 return base_url
53
53
54
54
55 def _get_permission_for_user(user, repo):
55 def _get_permission_for_user(user, repo):
56 perm = UserRepoToPerm.query()\
56 perm = UserRepoToPerm.query()\
57 .filter(UserRepoToPerm.repository ==
57 .filter(UserRepoToPerm.repository ==
58 Repository.get_by_repo_name(repo))\
58 Repository.get_by_repo_name(repo))\
59 .filter(UserRepoToPerm.user == User.get_by_username(user))\
59 .filter(UserRepoToPerm.user == User.get_by_username(user))\
60 .all()
60 .all()
61 return perm
61 return perm
62
62
63
63
64 @pytest.mark.usefixtures('autologin_user', 'app')
64 @pytest.mark.usefixtures('autologin_user', 'app')
65 class TestAdminRepoSettings(object):
65 class TestAdminRepoSettings(object):
66 @pytest.mark.parametrize('urlname', [
66 @pytest.mark.parametrize('urlname', [
67 'edit_repo',
67 'edit_repo',
68 'edit_repo_caches',
68 'edit_repo_caches',
69 'edit_repo_perms',
69 'edit_repo_perms',
70 'edit_repo_advanced',
70 'edit_repo_advanced',
71 'edit_repo_vcs',
71 'edit_repo_vcs',
72 'edit_repo_issuetracker',
72 'edit_repo_issuetracker',
73 'edit_repo_fields',
73 'edit_repo_fields',
74 'edit_repo_remote',
74 'edit_repo_remote',
75 'edit_repo_statistics',
75 'edit_repo_statistics',
76 ])
76 ])
77 def test_show_page(self, urlname, app, backend):
77 def test_show_page(self, urlname, app, backend):
78 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
78 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
79
79
80 def test_edit_accessible_when_missing_requirements(
80 def test_edit_accessible_when_missing_requirements(
81 self, backend_hg, autologin_user):
81 self, backend_hg, autologin_user):
82 scm_patcher = mock.patch.object(
82 scm_patcher = mock.patch.object(
83 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
83 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
84 with scm_patcher:
84 with scm_patcher:
85 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
85 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
86
86
87 @pytest.mark.parametrize('update_settings', [
87 @pytest.mark.parametrize('update_settings', [
88 {'repo_description': 'alter-desc'},
88 {'repo_description': 'alter-desc'},
89 {'repo_owner': TEST_USER_REGULAR_LOGIN},
89 {'repo_owner': TEST_USER_REGULAR_LOGIN},
90 {'repo_private': 'true'},
90 {'repo_private': 'true'},
91 {'repo_enable_locking': 'true'},
91 {'repo_enable_locking': 'true'},
92 {'repo_enable_downloads': 'true'},
92 {'repo_enable_downloads': 'true'},
93 ])
93 ])
94 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
94 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
95 repo = user_util.create_repo(repo_type=backend.alias)
95 repo = user_util.create_repo(repo_type=backend.alias)
96 repo_name = repo.repo_name
96 repo_name = repo.repo_name
97
97
98 params = fixture._get_repo_create_params(
98 params = fixture._get_repo_create_params(
99 csrf_token=csrf_token,
99 csrf_token=csrf_token,
100 repo_name=repo_name,
100 repo_name=repo_name,
101 repo_type=backend.alias,
101 repo_type=backend.alias,
102 repo_owner=TEST_USER_ADMIN_LOGIN,
102 repo_owner=TEST_USER_ADMIN_LOGIN,
103 repo_description='DESC',
103 repo_description='DESC',
104
104
105 repo_private='false',
105 repo_private='false',
106 repo_enable_locking='false',
106 repo_enable_locking='false',
107 repo_enable_downloads='false')
107 repo_enable_downloads='false')
108 params.update(update_settings)
108 params.update(update_settings)
109 self.app.post(
109 self.app.post(
110 route_path('edit_repo', repo_name=repo_name),
110 route_path('edit_repo', repo_name=repo_name),
111 params=params, status=302)
111 params=params, status=302)
112
112
113 repo = Repository.get_by_repo_name(repo_name)
113 repo = Repository.get_by_repo_name(repo_name)
114 assert repo.user.username == \
114 assert repo.user.username == \
115 update_settings.get('repo_owner', repo.user.username)
115 update_settings.get('repo_owner', repo.user.username)
116
116
117 assert repo.description == \
117 assert repo.description == \
118 update_settings.get('repo_description', repo.description)
118 update_settings.get('repo_description', repo.description)
119
119
120 assert repo.private == \
120 assert repo.private == \
121 str2bool(update_settings.get(
121 str2bool(update_settings.get(
122 'repo_private', repo.private))
122 'repo_private', repo.private))
123
123
124 assert repo.enable_locking == \
124 assert repo.enable_locking == \
125 str2bool(update_settings.get(
125 str2bool(update_settings.get(
126 'repo_enable_locking', repo.enable_locking))
126 'repo_enable_locking', repo.enable_locking))
127
127
128 assert repo.enable_downloads == \
128 assert repo.enable_downloads == \
129 str2bool(update_settings.get(
129 str2bool(update_settings.get(
130 'repo_enable_downloads', repo.enable_downloads))
130 'repo_enable_downloads', repo.enable_downloads))
131
131
132 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
132 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
133 repo = user_util.create_repo(repo_type=backend.alias)
133 repo = user_util.create_repo(repo_type=backend.alias)
134 repo_name = repo.repo_name
134 repo_name = repo.repo_name
135
135
136 repo_group = user_util.create_repo_group()
136 repo_group = user_util.create_repo_group()
137 repo_group_name = repo_group.group_name
137 repo_group_name = repo_group.group_name
138 new_name = repo_group_name + '_' + repo_name
138 new_name = repo_group_name + '_' + repo_name
139
139
140 params = fixture._get_repo_create_params(
140 params = fixture._get_repo_create_params(
141 csrf_token=csrf_token,
141 csrf_token=csrf_token,
142 repo_name=new_name,
142 repo_name=new_name,
143 repo_type=backend.alias,
143 repo_type=backend.alias,
144 repo_owner=TEST_USER_ADMIN_LOGIN,
144 repo_owner=TEST_USER_ADMIN_LOGIN,
145 repo_description='DESC',
145 repo_description='DESC',
146 repo_private='false',
146 repo_private='false',
147 repo_enable_locking='false',
147 repo_enable_locking='false',
148 repo_enable_downloads='false')
148 repo_enable_downloads='false')
149 self.app.post(
149 self.app.post(
150 route_path('edit_repo', repo_name=repo_name),
150 route_path('edit_repo', repo_name=repo_name),
151 params=params, status=302)
151 params=params, status=302)
152 repo = Repository.get_by_repo_name(new_name)
152 repo = Repository.get_by_repo_name(new_name)
153 assert repo.repo_name == new_name
153 assert repo.repo_name == new_name
154
154
155 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
155 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
156 repo = user_util.create_repo(repo_type=backend.alias)
156 repo = user_util.create_repo(repo_type=backend.alias)
157 repo_name = repo.repo_name
157 repo_name = repo.repo_name
158
158
159 repo_group = user_util.create_repo_group()
159 repo_group = user_util.create_repo_group()
160 repo_group_name = repo_group.group_name
160 repo_group_name = repo_group.group_name
161 repo_group_id = repo_group.group_id
161 repo_group_id = repo_group.group_id
162
162
163 new_name = repo_group_name + '/' + repo_name
163 new_name = repo_group_name + '/' + repo_name
164 params = fixture._get_repo_create_params(
164 params = fixture._get_repo_create_params(
165 csrf_token=csrf_token,
165 csrf_token=csrf_token,
166 repo_name=repo_name,
166 repo_name=repo_name,
167 repo_type=backend.alias,
167 repo_type=backend.alias,
168 repo_owner=TEST_USER_ADMIN_LOGIN,
168 repo_owner=TEST_USER_ADMIN_LOGIN,
169 repo_description='DESC',
169 repo_description='DESC',
170 repo_group=repo_group_id,
170 repo_group=repo_group_id,
171 repo_private='false',
171 repo_private='false',
172 repo_enable_locking='false',
172 repo_enable_locking='false',
173 repo_enable_downloads='false')
173 repo_enable_downloads='false')
174 self.app.post(
174 self.app.post(
175 route_path('edit_repo', repo_name=repo_name),
175 route_path('edit_repo', repo_name=repo_name),
176 params=params, status=302)
176 params=params, status=302)
177 repo = Repository.get_by_repo_name(new_name)
177 repo = Repository.get_by_repo_name(new_name)
178 assert repo.repo_name == new_name
178 assert repo.repo_name == new_name
179
179
180 def test_set_private_flag_sets_default_user_permissions_to_none(
180 def test_set_private_flag_sets_default_user_permissions_to_none(
181 self, autologin_user, backend, csrf_token):
181 self, autologin_user, backend, csrf_token):
182
182
183 # initially repository perm should be read
183 # initially repository perm should be read
184 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
184 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
185 assert len(perm) == 1
185 assert len(perm) == 1
186 assert perm[0].permission.permission_name == 'repository.read'
186 assert perm[0].permission.permission_name == 'repository.read'
187 assert not backend.repo.private
187 assert not backend.repo.private
188
188
189 response = self.app.post(
189 response = self.app.post(
190 route_path('edit_repo', repo_name=backend.repo_name),
190 route_path('edit_repo', repo_name=backend.repo_name),
191 params=fixture._get_repo_create_params(
191 params=fixture._get_repo_create_params(
192 repo_private='true',
192 repo_private='true',
193 repo_name=backend.repo_name,
193 repo_name=backend.repo_name,
194 repo_type=backend.alias,
194 repo_type=backend.alias,
195 repo_owner=TEST_USER_ADMIN_LOGIN,
195 repo_owner=TEST_USER_ADMIN_LOGIN,
196 csrf_token=csrf_token), status=302)
196 csrf_token=csrf_token), status=302)
197
197
198 assert_session_flash(
198 assert_session_flash(
199 response,
199 response,
200 msg='Repository %s updated successfully' % (backend.repo_name))
200 msg='Repository `%s` updated successfully' % (backend.repo_name))
201
201
202 repo = Repository.get_by_repo_name(backend.repo_name)
202 repo = Repository.get_by_repo_name(backend.repo_name)
203 assert repo.private is True
203 assert repo.private is True
204
204
205 # now the repo default permission should be None
205 # now the repo default permission should be None
206 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
206 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
207 assert len(perm) == 1
207 assert len(perm) == 1
208 assert perm[0].permission.permission_name == 'repository.none'
208 assert perm[0].permission.permission_name == 'repository.none'
209
209
210 response = self.app.post(
210 response = self.app.post(
211 route_path('edit_repo', repo_name=backend.repo_name),
211 route_path('edit_repo', repo_name=backend.repo_name),
212 params=fixture._get_repo_create_params(
212 params=fixture._get_repo_create_params(
213 repo_private='false',
213 repo_private='false',
214 repo_name=backend.repo_name,
214 repo_name=backend.repo_name,
215 repo_type=backend.alias,
215 repo_type=backend.alias,
216 repo_owner=TEST_USER_ADMIN_LOGIN,
216 repo_owner=TEST_USER_ADMIN_LOGIN,
217 csrf_token=csrf_token), status=302)
217 csrf_token=csrf_token), status=302)
218
218
219 assert_session_flash(
219 assert_session_flash(
220 response,
220 response,
221 msg='Repository %s updated successfully' % (backend.repo_name))
221 msg='Repository `%s` updated successfully' % (backend.repo_name))
222 assert backend.repo.private is False
222 assert backend.repo.private is False
223
223
224 # we turn off private now the repo default permission should stay None
224 # we turn off private now the repo default permission should stay None
225 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
225 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
226 assert len(perm) == 1
226 assert len(perm) == 1
227 assert perm[0].permission.permission_name == 'repository.none'
227 assert perm[0].permission.permission_name == 'repository.none'
228
228
229 # update this permission back
229 # update this permission back
230 perm[0].permission = Permission.get_by_key('repository.read')
230 perm[0].permission = Permission.get_by_key('repository.read')
231 Session().add(perm[0])
231 Session().add(perm[0])
232 Session().commit()
232 Session().commit()
@@ -1,251 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-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 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import RepoGroupList, ScmModel
36 from rhodecode.model.scm import RepoGroupList, ScmModel
37 from rhodecode.model.validation_schema.schemas import repo_schema
37 from rhodecode.model.validation_schema.schemas import repo_schema
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoSettingsView(RepoAppView):
42 class RepoSettingsView(RepoAppView):
43
43
44 def load_default_context(self):
44 def load_default_context(self):
45 c = self._get_local_tmpl_context()
45 c = self._get_local_tmpl_context()
46
46
47 acl_groups = RepoGroupList(
47 acl_groups = RepoGroupList(
48 RepoGroup.query().all(),
48 RepoGroup.query().all(),
49 perm_set=['group.write', 'group.admin'])
49 perm_set=['group.write', 'group.admin'])
50 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
50 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
51 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
51 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
52
52
53 # in case someone no longer have a group.write access to a repository
53 # in case someone no longer have a group.write access to a repository
54 # pre fill the list with this entry, we don't care if this is the same
54 # pre fill the list with this entry, we don't care if this is the same
55 # but it will allow saving repo data properly.
55 # but it will allow saving repo data properly.
56 repo_group = self.db_repo.group
56 repo_group = self.db_repo.group
57 if repo_group and repo_group.group_id not in c.repo_groups_choices:
57 if repo_group and repo_group.group_id not in c.repo_groups_choices:
58 c.repo_groups_choices.append(repo_group.group_id)
58 c.repo_groups_choices.append(repo_group.group_id)
59 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
59 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
60
60
61 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
61 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
62 # we might be in missing requirement state, so we load things
62 # we might be in missing requirement state, so we load things
63 # without touching scm_instance()
63 # without touching scm_instance()
64 c.landing_revs_choices, c.landing_revs = \
64 c.landing_revs_choices, c.landing_revs = \
65 ScmModel().get_repo_landing_revs()
65 ScmModel().get_repo_landing_revs()
66 else:
66 else:
67 c.landing_revs_choices, c.landing_revs = \
67 c.landing_revs_choices, c.landing_revs = \
68 ScmModel().get_repo_landing_revs(self.db_repo)
68 ScmModel().get_repo_landing_revs(self.db_repo)
69
69
70 c.personal_repo_group = c.auth_user.personal_repo_group
70 c.personal_repo_group = c.auth_user.personal_repo_group
71 c.repo_fields = RepositoryField.query()\
71 c.repo_fields = RepositoryField.query()\
72 .filter(RepositoryField.repository == self.db_repo).all()
72 .filter(RepositoryField.repository == self.db_repo).all()
73
73
74 self._register_global_c(c)
74 self._register_global_c(c)
75 return c
75 return c
76
76
77 def _get_schema(self, c, old_values=None):
77 def _get_schema(self, c, old_values=None):
78 return repo_schema.RepoSettingsSchema().bind(
78 return repo_schema.RepoSettingsSchema().bind(
79 repo_type=self.db_repo.repo_type,
79 repo_type=self.db_repo.repo_type,
80 repo_type_options=[self.db_repo.repo_type],
80 repo_type_options=[self.db_repo.repo_type],
81 repo_ref_options=c.landing_revs_choices,
81 repo_ref_options=c.landing_revs_choices,
82 repo_ref_items=c.landing_revs,
82 repo_ref_items=c.landing_revs,
83 repo_repo_group_options=c.repo_groups_choices,
83 repo_repo_group_options=c.repo_groups_choices,
84 repo_repo_group_items=c.repo_groups,
84 repo_repo_group_items=c.repo_groups,
85 # user caller
85 # user caller
86 user=self._rhodecode_user,
86 user=self._rhodecode_user,
87 old_values=old_values
87 old_values=old_values
88 )
88 )
89
89
90 @LoginRequired()
90 @LoginRequired()
91 @HasRepoPermissionAnyDecorator('repository.admin')
91 @HasRepoPermissionAnyDecorator('repository.admin')
92 @view_config(
92 @view_config(
93 route_name='edit_repo', request_method='GET',
93 route_name='edit_repo', request_method='GET',
94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
95 def edit_settings(self):
95 def edit_settings(self):
96 c = self.load_default_context()
96 c = self.load_default_context()
97 c.active = 'settings'
97 c.active = 'settings'
98
98
99 defaults = RepoModel()._get_defaults(self.db_repo_name)
99 defaults = RepoModel()._get_defaults(self.db_repo_name)
100 defaults['repo_owner'] = defaults['user']
100 defaults['repo_owner'] = defaults['user']
101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
102
102
103 schema = self._get_schema(c)
103 schema = self._get_schema(c)
104 c.form = RcForm(schema, appstruct=defaults)
104 c.form = RcForm(schema, appstruct=defaults)
105 return self._get_template_context(c)
105 return self._get_template_context(c)
106
106
107 @LoginRequired()
107 @LoginRequired()
108 @HasRepoPermissionAnyDecorator('repository.admin')
108 @HasRepoPermissionAnyDecorator('repository.admin')
109 @CSRFRequired()
109 @CSRFRequired()
110 @view_config(
110 @view_config(
111 route_name='edit_repo', request_method='POST',
111 route_name='edit_repo', request_method='POST',
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 def edit_settings_update(self):
113 def edit_settings_update(self):
114 _ = self.request.translate
114 _ = self.request.translate
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'settings'
116 c.active = 'settings'
117 old_repo_name = self.db_repo_name
117 old_repo_name = self.db_repo_name
118
118
119 old_values = self.db_repo.get_api_data()
119 old_values = self.db_repo.get_api_data()
120 schema = self._get_schema(c, old_values=old_values)
120 schema = self._get_schema(c, old_values=old_values)
121
121
122 c.form = RcForm(schema)
122 c.form = RcForm(schema)
123 pstruct = self.request.POST.items()
123 pstruct = self.request.POST.items()
124 pstruct.append(('repo_type', self.db_repo.repo_type))
124 pstruct.append(('repo_type', self.db_repo.repo_type))
125 try:
125 try:
126 schema_data = c.form.validate(pstruct)
126 schema_data = c.form.validate(pstruct)
127 except deform.ValidationFailure as err_form:
127 except deform.ValidationFailure as err_form:
128 return self._get_template_context(c)
128 return self._get_template_context(c)
129
129
130 # data is now VALID, proceed with updates
130 # data is now VALID, proceed with updates
131 # save validated data back into the updates dict
131 # save validated data back into the updates dict
132 validated_updates = dict(
132 validated_updates = dict(
133 repo_name=schema_data['repo_group']['repo_name_without_group'],
133 repo_name=schema_data['repo_group']['repo_name_without_group'],
134 repo_group=schema_data['repo_group']['repo_group_id'],
134 repo_group=schema_data['repo_group']['repo_group_id'],
135
135
136 user=schema_data['repo_owner'],
136 user=schema_data['repo_owner'],
137 repo_description=schema_data['repo_description'],
137 repo_description=schema_data['repo_description'],
138 repo_private=schema_data['repo_private'],
138 repo_private=schema_data['repo_private'],
139 clone_uri=schema_data['repo_clone_uri'],
139 clone_uri=schema_data['repo_clone_uri'],
140 repo_landing_rev=schema_data['repo_landing_commit_ref'],
140 repo_landing_rev=schema_data['repo_landing_commit_ref'],
141 repo_enable_statistics=schema_data['repo_enable_statistics'],
141 repo_enable_statistics=schema_data['repo_enable_statistics'],
142 repo_enable_locking=schema_data['repo_enable_locking'],
142 repo_enable_locking=schema_data['repo_enable_locking'],
143 repo_enable_downloads=schema_data['repo_enable_downloads'],
143 repo_enable_downloads=schema_data['repo_enable_downloads'],
144 )
144 )
145 # detect if CLONE URI changed, if we get OLD means we keep old values
145 # detect if CLONE URI changed, if we get OLD means we keep old values
146 if schema_data['repo_clone_uri_change'] == 'OLD':
146 if schema_data['repo_clone_uri_change'] == 'OLD':
147 validated_updates['clone_uri'] = self.db_repo.clone_uri
147 validated_updates['clone_uri'] = self.db_repo.clone_uri
148
148
149 # use the new full name for redirect
149 # use the new full name for redirect
150 new_repo_name = schema_data['repo_group']['repo_name_with_group']
150 new_repo_name = schema_data['repo_group']['repo_name_with_group']
151
151
152 # save extra fields into our validated data
152 # save extra fields into our validated data
153 for key, value in pstruct:
153 for key, value in pstruct:
154 if key.startswith(RepositoryField.PREFIX):
154 if key.startswith(RepositoryField.PREFIX):
155 validated_updates[key] = value
155 validated_updates[key] = value
156
156
157 try:
157 try:
158 RepoModel().update(self.db_repo, **validated_updates)
158 RepoModel().update(self.db_repo, **validated_updates)
159 ScmModel().mark_for_invalidation(new_repo_name)
159 ScmModel().mark_for_invalidation(new_repo_name)
160
160
161 audit_logger.store_web(
161 audit_logger.store_web(
162 'repo.edit', action_data={'old_data': old_values},
162 'repo.edit', action_data={'old_data': old_values},
163 user=self._rhodecode_user, repo=self.db_repo)
163 user=self._rhodecode_user, repo=self.db_repo)
164
164
165 Session().commit()
165 Session().commit()
166
166
167 h.flash(_('Repository {} updated successfully').format(
167 h.flash(_('Repository `{}` updated successfully').format(
168 old_repo_name), category='success')
168 old_repo_name), category='success')
169 except Exception:
169 except Exception:
170 log.exception("Exception during update of repository")
170 log.exception("Exception during update of repository")
171 h.flash(_('Error occurred during update of repository {}').format(
171 h.flash(_('Error occurred during update of repository {}').format(
172 old_repo_name), category='error')
172 old_repo_name), category='error')
173
173
174 raise HTTPFound(
174 raise HTTPFound(
175 h.route_path('edit_repo', repo_name=new_repo_name))
175 h.route_path('edit_repo', repo_name=new_repo_name))
176
176
177 @LoginRequired()
177 @LoginRequired()
178 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
178 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
179 @view_config(
179 @view_config(
180 route_name='repo_edit_toggle_locking', request_method='GET',
180 route_name='repo_edit_toggle_locking', request_method='GET',
181 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
181 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
182 def toggle_locking(self):
182 def toggle_locking(self):
183 """
183 """
184 Toggle locking of repository by simple GET call to url
184 Toggle locking of repository by simple GET call to url
185 """
185 """
186 _ = self.request.translate
186 _ = self.request.translate
187 repo = self.db_repo
187 repo = self.db_repo
188
188
189 try:
189 try:
190 if repo.enable_locking:
190 if repo.enable_locking:
191 if repo.locked[0]:
191 if repo.locked[0]:
192 Repository.unlock(repo)
192 Repository.unlock(repo)
193 action = _('Unlocked')
193 action = _('Unlocked')
194 else:
194 else:
195 Repository.lock(
195 Repository.lock(
196 repo, self._rhodecode_user.user_id,
196 repo, self._rhodecode_user.user_id,
197 lock_reason=Repository.LOCK_WEB)
197 lock_reason=Repository.LOCK_WEB)
198 action = _('Locked')
198 action = _('Locked')
199
199
200 h.flash(_('Repository has been %s') % action,
200 h.flash(_('Repository has been %s') % action,
201 category='success')
201 category='success')
202 except Exception:
202 except Exception:
203 log.exception("Exception during unlocking")
203 log.exception("Exception during unlocking")
204 h.flash(_('An error occurred during unlocking'),
204 h.flash(_('An error occurred during unlocking'),
205 category='error')
205 category='error')
206 raise HTTPFound(
206 raise HTTPFound(
207 h.route_path('repo_summary', repo_name=self.db_repo_name))
207 h.route_path('repo_summary', repo_name=self.db_repo_name))
208
208
209 @LoginRequired()
209 @LoginRequired()
210 @HasRepoPermissionAnyDecorator('repository.admin')
210 @HasRepoPermissionAnyDecorator('repository.admin')
211 @view_config(
211 @view_config(
212 route_name='edit_repo_statistics', request_method='GET',
212 route_name='edit_repo_statistics', request_method='GET',
213 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
213 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
214 def edit_statistics_form(self):
214 def edit_statistics_form(self):
215 c = self.load_default_context()
215 c = self.load_default_context()
216
216
217 if self.db_repo.stats:
217 if self.db_repo.stats:
218 # this is on what revision we ended up so we add +1 for count
218 # this is on what revision we ended up so we add +1 for count
219 last_rev = self.db_repo.stats.stat_on_revision + 1
219 last_rev = self.db_repo.stats.stat_on_revision + 1
220 else:
220 else:
221 last_rev = 0
221 last_rev = 0
222
222
223 c.active = 'statistics'
223 c.active = 'statistics'
224 c.stats_revision = last_rev
224 c.stats_revision = last_rev
225 c.repo_last_rev = self.rhodecode_vcs_repo.count()
225 c.repo_last_rev = self.rhodecode_vcs_repo.count()
226
226
227 if last_rev == 0 or c.repo_last_rev == 0:
227 if last_rev == 0 or c.repo_last_rev == 0:
228 c.stats_percentage = 0
228 c.stats_percentage = 0
229 else:
229 else:
230 c.stats_percentage = '%.2f' % (
230 c.stats_percentage = '%.2f' % (
231 (float((last_rev)) / c.repo_last_rev) * 100)
231 (float((last_rev)) / c.repo_last_rev) * 100)
232 return self._get_template_context(c)
232 return self._get_template_context(c)
233
233
234 @LoginRequired()
234 @LoginRequired()
235 @HasRepoPermissionAnyDecorator('repository.admin')
235 @HasRepoPermissionAnyDecorator('repository.admin')
236 @CSRFRequired()
236 @CSRFRequired()
237 @view_config(
237 @view_config(
238 route_name='edit_repo_statistics_reset', request_method='POST',
238 route_name='edit_repo_statistics_reset', request_method='POST',
239 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
239 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
240 def repo_statistics_reset(self):
240 def repo_statistics_reset(self):
241 _ = self.request.translate
241 _ = self.request.translate
242
242
243 try:
243 try:
244 RepoModel().delete_stats(self.db_repo_name)
244 RepoModel().delete_stats(self.db_repo_name)
245 Session().commit()
245 Session().commit()
246 except Exception:
246 except Exception:
247 log.exception('Edit statistics failure')
247 log.exception('Edit statistics failure')
248 h.flash(_('An error occurred during deletion of repository stats'),
248 h.flash(_('An error occurred during deletion of repository stats'),
249 category='error')
249 category='error')
250 raise HTTPFound(
250 raise HTTPFound(
251 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
251 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,311 +1,264 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 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ADMIN REPOSITORY GROUPS ROUTES
176 with rmap.submapper(path_prefix=ADMIN_PREFIX,
177 controller='admin/repo_groups') as m:
178 m.connect('repo_groups', '/repo_groups',
179 action='create', conditions={'method': ['POST']})
180 m.connect('repo_groups', '/repo_groups',
181 action='index', conditions={'method': ['GET']})
182 m.connect('new_repo_group', '/repo_groups/new',
183 action='new', conditions={'method': ['GET']})
184 m.connect('update_repo_group', '/repo_groups/{group_name}',
185 action='update', conditions={'method': ['PUT'],
186 'function': check_group},
187 requirements=URL_NAME_REQUIREMENTS)
188
189 # EXTRAS REPO GROUP ROUTES
190 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
191 action='edit',
192 conditions={'method': ['GET'], 'function': check_group},
193 requirements=URL_NAME_REQUIREMENTS)
194 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
195 action='edit',
196 conditions={'method': ['PUT'], 'function': check_group},
197 requirements=URL_NAME_REQUIREMENTS)
198
199 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
200 action='edit_repo_group_advanced',
201 conditions={'method': ['GET'], 'function': check_group},
202 requirements=URL_NAME_REQUIREMENTS)
203 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
204 action='edit_repo_group_advanced',
205 conditions={'method': ['PUT'], 'function': check_group},
206 requirements=URL_NAME_REQUIREMENTS)
207
208 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
209 action='edit_repo_group_perms',
210 conditions={'method': ['GET'], 'function': check_group},
211 requirements=URL_NAME_REQUIREMENTS)
212 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
213 action='update_perms',
214 conditions={'method': ['PUT'], 'function': check_group},
215 requirements=URL_NAME_REQUIREMENTS)
216
217 m.connect('delete_repo_group', '/repo_groups/{group_name}',
218 action='delete', conditions={'method': ['DELETE'],
219 'function': check_group},
220 requirements=URL_NAME_REQUIREMENTS)
221
222 # ADMIN SETTINGS ROUTES
175 # ADMIN SETTINGS ROUTES
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
176 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 controller='admin/settings') as m:
177 controller='admin/settings') as m:
225
178
226 # default
179 # default
227 m.connect('admin_settings', '/settings',
180 m.connect('admin_settings', '/settings',
228 action='settings_global_update',
181 action='settings_global_update',
229 conditions={'method': ['POST']})
182 conditions={'method': ['POST']})
230 m.connect('admin_settings', '/settings',
183 m.connect('admin_settings', '/settings',
231 action='settings_global', conditions={'method': ['GET']})
184 action='settings_global', conditions={'method': ['GET']})
232
185
233 m.connect('admin_settings_vcs', '/settings/vcs',
186 m.connect('admin_settings_vcs', '/settings/vcs',
234 action='settings_vcs_update',
187 action='settings_vcs_update',
235 conditions={'method': ['POST']})
188 conditions={'method': ['POST']})
236 m.connect('admin_settings_vcs', '/settings/vcs',
189 m.connect('admin_settings_vcs', '/settings/vcs',
237 action='settings_vcs',
190 action='settings_vcs',
238 conditions={'method': ['GET']})
191 conditions={'method': ['GET']})
239 m.connect('admin_settings_vcs', '/settings/vcs',
192 m.connect('admin_settings_vcs', '/settings/vcs',
240 action='delete_svn_pattern',
193 action='delete_svn_pattern',
241 conditions={'method': ['DELETE']})
194 conditions={'method': ['DELETE']})
242
195
243 m.connect('admin_settings_mapping', '/settings/mapping',
196 m.connect('admin_settings_mapping', '/settings/mapping',
244 action='settings_mapping_update',
197 action='settings_mapping_update',
245 conditions={'method': ['POST']})
198 conditions={'method': ['POST']})
246 m.connect('admin_settings_mapping', '/settings/mapping',
199 m.connect('admin_settings_mapping', '/settings/mapping',
247 action='settings_mapping', conditions={'method': ['GET']})
200 action='settings_mapping', conditions={'method': ['GET']})
248
201
249 m.connect('admin_settings_global', '/settings/global',
202 m.connect('admin_settings_global', '/settings/global',
250 action='settings_global_update',
203 action='settings_global_update',
251 conditions={'method': ['POST']})
204 conditions={'method': ['POST']})
252 m.connect('admin_settings_global', '/settings/global',
205 m.connect('admin_settings_global', '/settings/global',
253 action='settings_global', conditions={'method': ['GET']})
206 action='settings_global', conditions={'method': ['GET']})
254
207
255 m.connect('admin_settings_visual', '/settings/visual',
208 m.connect('admin_settings_visual', '/settings/visual',
256 action='settings_visual_update',
209 action='settings_visual_update',
257 conditions={'method': ['POST']})
210 conditions={'method': ['POST']})
258 m.connect('admin_settings_visual', '/settings/visual',
211 m.connect('admin_settings_visual', '/settings/visual',
259 action='settings_visual', conditions={'method': ['GET']})
212 action='settings_visual', conditions={'method': ['GET']})
260
213
261 m.connect('admin_settings_issuetracker',
214 m.connect('admin_settings_issuetracker',
262 '/settings/issue-tracker', action='settings_issuetracker',
215 '/settings/issue-tracker', action='settings_issuetracker',
263 conditions={'method': ['GET']})
216 conditions={'method': ['GET']})
264 m.connect('admin_settings_issuetracker_save',
217 m.connect('admin_settings_issuetracker_save',
265 '/settings/issue-tracker/save',
218 '/settings/issue-tracker/save',
266 action='settings_issuetracker_save',
219 action='settings_issuetracker_save',
267 conditions={'method': ['POST']})
220 conditions={'method': ['POST']})
268 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
221 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
269 action='settings_issuetracker_test',
222 action='settings_issuetracker_test',
270 conditions={'method': ['POST']})
223 conditions={'method': ['POST']})
271 m.connect('admin_issuetracker_delete',
224 m.connect('admin_issuetracker_delete',
272 '/settings/issue-tracker/delete',
225 '/settings/issue-tracker/delete',
273 action='settings_issuetracker_delete',
226 action='settings_issuetracker_delete',
274 conditions={'method': ['DELETE']})
227 conditions={'method': ['DELETE']})
275
228
276 m.connect('admin_settings_email', '/settings/email',
229 m.connect('admin_settings_email', '/settings/email',
277 action='settings_email_update',
230 action='settings_email_update',
278 conditions={'method': ['POST']})
231 conditions={'method': ['POST']})
279 m.connect('admin_settings_email', '/settings/email',
232 m.connect('admin_settings_email', '/settings/email',
280 action='settings_email', conditions={'method': ['GET']})
233 action='settings_email', conditions={'method': ['GET']})
281
234
282 m.connect('admin_settings_hooks', '/settings/hooks',
235 m.connect('admin_settings_hooks', '/settings/hooks',
283 action='settings_hooks_update',
236 action='settings_hooks_update',
284 conditions={'method': ['POST', 'DELETE']})
237 conditions={'method': ['POST', 'DELETE']})
285 m.connect('admin_settings_hooks', '/settings/hooks',
238 m.connect('admin_settings_hooks', '/settings/hooks',
286 action='settings_hooks', conditions={'method': ['GET']})
239 action='settings_hooks', conditions={'method': ['GET']})
287
240
288 m.connect('admin_settings_search', '/settings/search',
241 m.connect('admin_settings_search', '/settings/search',
289 action='settings_search', conditions={'method': ['GET']})
242 action='settings_search', conditions={'method': ['GET']})
290
243
291 m.connect('admin_settings_supervisor', '/settings/supervisor',
244 m.connect('admin_settings_supervisor', '/settings/supervisor',
292 action='settings_supervisor', conditions={'method': ['GET']})
245 action='settings_supervisor', conditions={'method': ['GET']})
293 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
246 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
294 action='settings_supervisor_log', conditions={'method': ['GET']})
247 action='settings_supervisor_log', conditions={'method': ['GET']})
295
248
296 m.connect('admin_settings_labs', '/settings/labs',
249 m.connect('admin_settings_labs', '/settings/labs',
297 action='settings_labs_update',
250 action='settings_labs_update',
298 conditions={'method': ['POST']})
251 conditions={'method': ['POST']})
299 m.connect('admin_settings_labs', '/settings/labs',
252 m.connect('admin_settings_labs', '/settings/labs',
300 action='settings_labs', conditions={'method': ['GET']})
253 action='settings_labs', conditions={'method': ['GET']})
301
254
302 # ADMIN MY ACCOUNT
255 # ADMIN MY ACCOUNT
303 with rmap.submapper(path_prefix=ADMIN_PREFIX,
256 with rmap.submapper(path_prefix=ADMIN_PREFIX,
304 controller='admin/my_account') as m:
257 controller='admin/my_account') as m:
305
258
306 # NOTE(marcink): this needs to be kept for password force flag to be
259 # NOTE(marcink): this needs to be kept for password force flag to be
307 # handled in pylons controllers, remove after full migration to pyramid
260 # handled in pylons controllers, remove after full migration to pyramid
308 m.connect('my_account_password', '/my_account/password',
261 m.connect('my_account_password', '/my_account/password',
309 action='my_account_password', conditions={'method': ['GET']})
262 action='my_account_password', conditions={'method': ['GET']})
310
263
311 return rmap
264 return rmap
@@ -1,230 +1,230 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-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 rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
23 from rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
24 from rhodecode.lib.utils2 import safe_int
24 from rhodecode.lib.utils2 import safe_int
25 from rhodecode.model.db import Repository, Integration, RepoGroup
25 from rhodecode.model.db import Repository, Integration, RepoGroup
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def includeme(config):
31 def includeme(config):
32
32
33 # global integrations
33 # global integrations
34 config.add_route('global_integrations_new',
34 config.add_route('global_integrations_new',
35 ADMIN_PREFIX + '/integrations/new')
35 ADMIN_PREFIX + '/integrations/new')
36 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
36 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
37 attr='new_integration',
37 attr='new_integration',
38 renderer='rhodecode:templates/admin/integrations/new.mako',
38 renderer='rhodecode:templates/admin/integrations/new.mako',
39 request_method='GET',
39 request_method='GET',
40 route_name='global_integrations_new')
40 route_name='global_integrations_new')
41
41
42 config.add_route('global_integrations_home',
42 config.add_route('global_integrations_home',
43 ADMIN_PREFIX + '/integrations')
43 ADMIN_PREFIX + '/integrations')
44 config.add_route('global_integrations_list',
44 config.add_route('global_integrations_list',
45 ADMIN_PREFIX + '/integrations/{integration}')
45 ADMIN_PREFIX + '/integrations/{integration}')
46 for route_name in ['global_integrations_home', 'global_integrations_list']:
46 for route_name in ['global_integrations_home', 'global_integrations_list']:
47 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
47 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
48 attr='integration_list',
48 attr='integration_list',
49 renderer='rhodecode:templates/admin/integrations/list.mako',
49 renderer='rhodecode:templates/admin/integrations/list.mako',
50 request_method='GET',
50 request_method='GET',
51 route_name=route_name)
51 route_name=route_name)
52
52
53 config.add_route('global_integrations_create',
53 config.add_route('global_integrations_create',
54 ADMIN_PREFIX + '/integrations/{integration}/new',
54 ADMIN_PREFIX + '/integrations/{integration}/new',
55 custom_predicates=(valid_integration,))
55 custom_predicates=(valid_integration,))
56 config.add_route('global_integrations_edit',
56 config.add_route('global_integrations_edit',
57 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
57 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
58 custom_predicates=(valid_integration,))
58 custom_predicates=(valid_integration,))
59
59
60 for route_name in ['global_integrations_create', 'global_integrations_edit']:
60 for route_name in ['global_integrations_create', 'global_integrations_edit']:
61 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
61 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
62 attr='settings_get',
62 attr='settings_get',
63 renderer='rhodecode:templates/admin/integrations/form.mako',
63 renderer='rhodecode:templates/admin/integrations/form.mako',
64 request_method='GET',
64 request_method='GET',
65 route_name=route_name)
65 route_name=route_name)
66 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
66 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
67 attr='settings_post',
67 attr='settings_post',
68 renderer='rhodecode:templates/admin/integrations/form.mako',
68 renderer='rhodecode:templates/admin/integrations/form.mako',
69 request_method='POST',
69 request_method='POST',
70 route_name=route_name)
70 route_name=route_name)
71
71
72 # repo group integrations
72 # repo group integrations
73 config.add_route('repo_group_integrations_home',
73 config.add_route('repo_group_integrations_home',
74 add_route_requirements('/{repo_group_name}/settings/integrations'),
74 add_route_requirements('/{repo_group_name}/_settings/integrations'),
75 repo_group_route=True)
75 repo_group_route=True)
76
76
77 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
77 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
78 attr='integration_list',
78 attr='integration_list',
79 renderer='rhodecode:templates/admin/integrations/list.mako',
79 renderer='rhodecode:templates/admin/integrations/list.mako',
80 request_method='GET',
80 request_method='GET',
81 route_name='repo_group_integrations_home')
81 route_name='repo_group_integrations_home')
82
82
83 config.add_route('repo_group_integrations_new',
83 config.add_route('repo_group_integrations_new',
84 add_route_requirements('/{repo_group_name}/settings/integrations/new'),
84 add_route_requirements('/{repo_group_name}/_settings/integrations/new'),
85 repo_group_route=True)
85 repo_group_route=True)
86 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
86 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
87 attr='new_integration',
87 attr='new_integration',
88 renderer='rhodecode:templates/admin/integrations/new.mako',
88 renderer='rhodecode:templates/admin/integrations/new.mako',
89 request_method='GET',
89 request_method='GET',
90 route_name='repo_group_integrations_new')
90 route_name='repo_group_integrations_new')
91
91
92 config.add_route('repo_group_integrations_list',
92 config.add_route('repo_group_integrations_list',
93 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}'),
93 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}'),
94 repo_group_route=True,
94 repo_group_route=True,
95 custom_predicates=(valid_integration,))
95 custom_predicates=(valid_integration,))
96 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
96 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
97 attr='integration_list',
97 attr='integration_list',
98 renderer='rhodecode:templates/admin/integrations/list.mako',
98 renderer='rhodecode:templates/admin/integrations/list.mako',
99 request_method='GET',
99 request_method='GET',
100 route_name='repo_group_integrations_list')
100 route_name='repo_group_integrations_list')
101
101
102 config.add_route('repo_group_integrations_create',
102 config.add_route('repo_group_integrations_create',
103 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/new'),
103 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/new'),
104 repo_group_route=True,
104 repo_group_route=True,
105 custom_predicates=(valid_integration,))
105 custom_predicates=(valid_integration,))
106 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
106 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
107 attr='settings_get',
107 attr='settings_get',
108 renderer='rhodecode:templates/admin/integrations/form.mako',
108 renderer='rhodecode:templates/admin/integrations/form.mako',
109 request_method='GET',
109 request_method='GET',
110 route_name='repo_group_integrations_create')
110 route_name='repo_group_integrations_create')
111 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
111 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
112 attr='settings_post',
112 attr='settings_post',
113 renderer='rhodecode:templates/admin/integrations/form.mako',
113 renderer='rhodecode:templates/admin/integrations/form.mako',
114 request_method='POST',
114 request_method='POST',
115 route_name='repo_group_integrations_create')
115 route_name='repo_group_integrations_create')
116
116
117 config.add_route('repo_group_integrations_edit',
117 config.add_route('repo_group_integrations_edit',
118 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/{integration_id}'),
118 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/{integration_id}'),
119 repo_group_route=True,
119 repo_group_route=True,
120 custom_predicates=(valid_integration,))
120 custom_predicates=(valid_integration,))
121
121
122 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
122 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
123 attr='settings_get',
123 attr='settings_get',
124 renderer='rhodecode:templates/admin/integrations/form.mako',
124 renderer='rhodecode:templates/admin/integrations/form.mako',
125 request_method='GET',
125 request_method='GET',
126 route_name='repo_group_integrations_edit')
126 route_name='repo_group_integrations_edit')
127 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
127 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
128 attr='settings_post',
128 attr='settings_post',
129 renderer='rhodecode:templates/admin/integrations/form.mako',
129 renderer='rhodecode:templates/admin/integrations/form.mako',
130 request_method='POST',
130 request_method='POST',
131 route_name='repo_group_integrations_edit')
131 route_name='repo_group_integrations_edit')
132
132
133 # repo integrations
133 # repo integrations
134 config.add_route('repo_integrations_home',
134 config.add_route('repo_integrations_home',
135 add_route_requirements('/{repo_name}/settings/integrations'),
135 add_route_requirements('/{repo_name}/settings/integrations'),
136 repo_route=True)
136 repo_route=True)
137 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
137 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
138 attr='integration_list',
138 attr='integration_list',
139 request_method='GET',
139 request_method='GET',
140 renderer='rhodecode:templates/admin/integrations/list.mako',
140 renderer='rhodecode:templates/admin/integrations/list.mako',
141 route_name='repo_integrations_home')
141 route_name='repo_integrations_home')
142
142
143 config.add_route('repo_integrations_new',
143 config.add_route('repo_integrations_new',
144 add_route_requirements('/{repo_name}/settings/integrations/new'),
144 add_route_requirements('/{repo_name}/settings/integrations/new'),
145 repo_route=True)
145 repo_route=True)
146 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
146 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
147 attr='new_integration',
147 attr='new_integration',
148 renderer='rhodecode:templates/admin/integrations/new.mako',
148 renderer='rhodecode:templates/admin/integrations/new.mako',
149 request_method='GET',
149 request_method='GET',
150 route_name='repo_integrations_new')
150 route_name='repo_integrations_new')
151
151
152 config.add_route('repo_integrations_list',
152 config.add_route('repo_integrations_list',
153 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
153 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
154 repo_route=True,
154 repo_route=True,
155 custom_predicates=(valid_integration,))
155 custom_predicates=(valid_integration,))
156 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
156 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
157 attr='integration_list',
157 attr='integration_list',
158 request_method='GET',
158 request_method='GET',
159 renderer='rhodecode:templates/admin/integrations/list.mako',
159 renderer='rhodecode:templates/admin/integrations/list.mako',
160 route_name='repo_integrations_list')
160 route_name='repo_integrations_list')
161
161
162 config.add_route('repo_integrations_create',
162 config.add_route('repo_integrations_create',
163 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
163 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
164 repo_route=True,
164 repo_route=True,
165 custom_predicates=(valid_integration,))
165 custom_predicates=(valid_integration,))
166 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
166 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
167 attr='settings_get',
167 attr='settings_get',
168 renderer='rhodecode:templates/admin/integrations/form.mako',
168 renderer='rhodecode:templates/admin/integrations/form.mako',
169 request_method='GET',
169 request_method='GET',
170 route_name='repo_integrations_create')
170 route_name='repo_integrations_create')
171 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
171 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
172 attr='settings_post',
172 attr='settings_post',
173 renderer='rhodecode:templates/admin/integrations/form.mako',
173 renderer='rhodecode:templates/admin/integrations/form.mako',
174 request_method='POST',
174 request_method='POST',
175 route_name='repo_integrations_create')
175 route_name='repo_integrations_create')
176
176
177 config.add_route('repo_integrations_edit',
177 config.add_route('repo_integrations_edit',
178 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
178 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
179 repo_route=True,
179 repo_route=True,
180 custom_predicates=(valid_integration,))
180 custom_predicates=(valid_integration,))
181 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
181 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
182 attr='settings_get',
182 attr='settings_get',
183 renderer='rhodecode:templates/admin/integrations/form.mako',
183 renderer='rhodecode:templates/admin/integrations/form.mako',
184 request_method='GET',
184 request_method='GET',
185 route_name='repo_integrations_edit')
185 route_name='repo_integrations_edit')
186 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
186 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
187 attr='settings_post',
187 attr='settings_post',
188 renderer='rhodecode:templates/admin/integrations/form.mako',
188 renderer='rhodecode:templates/admin/integrations/form.mako',
189 request_method='POST',
189 request_method='POST',
190 route_name='repo_integrations_edit')
190 route_name='repo_integrations_edit')
191
191
192
192
193
193
194 def valid_integration(info, request):
194 def valid_integration(info, request):
195 integration_type = info['match']['integration']
195 integration_type = info['match']['integration']
196 integration_id = info['match'].get('integration_id')
196 integration_id = info['match'].get('integration_id')
197
197
198 if integration_type not in integration_type_registry:
198 if integration_type not in integration_type_registry:
199 return False
199 return False
200
200
201 if integration_id:
201 if integration_id:
202 if not safe_int(integration_id):
202 if not safe_int(integration_id):
203 return False
203 return False
204
204
205 integration = Integration.get(integration_id)
205 integration = Integration.get(integration_id)
206 if not integration:
206 if not integration:
207 return False
207 return False
208 if integration.integration_type != integration_type:
208 if integration.integration_type != integration_type:
209 return False
209 return False
210
210
211 # match types to repo or repo group
211 # match types to repo or repo group
212 repo_name = info['match'].get('repo_name')
212 repo_name = info['match'].get('repo_name')
213 repo_group_name = info['match'].get('repo_group_name')
213 repo_group_name = info['match'].get('repo_group_name')
214 repo, repo_group = None, None
214 repo, repo_group = None, None
215 if repo_name:
215 if repo_name:
216 repo = Repository.get_by_repo_name(repo_name)
216 repo = Repository.get_by_repo_name(repo_name)
217 if not repo:
217 if not repo:
218 return False
218 return False
219
219
220 if repo_group_name:
220 if repo_group_name:
221 repo_group = RepoGroup.get_by_group_name(repo_group_name)
221 repo_group = RepoGroup.get_by_group_name(repo_group_name)
222 if not repo_group:
222 if not repo_group:
223 return False
223 return False
224
224
225 if repo and repo.repo_id != integration.repo_id:
225 if repo and repo.repo_id != integration.repo_id:
226 return False
226 return False
227 if repo_group and repo_group.group_id != integration.repo_group_id:
227 if repo_group and repo_group.group_id != integration.repo_group_id:
228 return False
228 return False
229
229
230 return True
230 return True
@@ -1,269 +1,269 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.apps._base import ADMIN_PREFIX
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import Integration
24 from rhodecode.model.db import Integration
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27
27
28
28
29 def route_path(name, **kwargs):
29 def route_path(name, **kwargs):
30 return {
30 return {
31 'home': '/',
31 'home': '/',
32 }[name].format(**kwargs)
32 }[name].format(**kwargs)
33
33
34
34
35 @pytest.mark.usefixtures('app', 'autologin_user')
35 @pytest.mark.usefixtures('app', 'autologin_user')
36 class TestIntegrationsView(object):
36 class TestIntegrationsView(object):
37 pass
37 pass
38
38
39
39
40 class TestGlobalIntegrationsView(TestIntegrationsView):
40 class TestGlobalIntegrationsView(TestIntegrationsView):
41 def test_index_no_integrations(self):
41 def test_index_no_integrations(self):
42 url = ADMIN_PREFIX + '/integrations'
42 url = ADMIN_PREFIX + '/integrations'
43 response = self.app.get(url)
43 response = self.app.get(url)
44
44
45 assert response.status_code == 200
45 assert response.status_code == 200
46 response.mustcontain('exist yet')
46 response.mustcontain('exist yet')
47
47
48 def test_index_with_integrations(self, global_integration_stub):
48 def test_index_with_integrations(self, global_integration_stub):
49 url = ADMIN_PREFIX + '/integrations'
49 url = ADMIN_PREFIX + '/integrations'
50 response = self.app.get(url)
50 response = self.app.get(url)
51
51
52 assert response.status_code == 200
52 assert response.status_code == 200
53 response.mustcontain(no=['exist yet'])
53 response.mustcontain(no=['exist yet'])
54 response.mustcontain(global_integration_stub.name)
54 response.mustcontain(global_integration_stub.name)
55
55
56 @pytest.mark.parametrize(
56 @pytest.mark.parametrize(
57 'IntegrationType', integration_type_registry.values())
57 'IntegrationType', integration_type_registry.values())
58 def test_new_integration_page(self, IntegrationType):
58 def test_new_integration_page(self, IntegrationType):
59 url = ADMIN_PREFIX + '/integrations/new'
59 url = ADMIN_PREFIX + '/integrations/new'
60
60
61 response = self.app.get(url, status=200)
61 response = self.app.get(url, status=200)
62 if not IntegrationType.is_dummy:
62 if not IntegrationType.is_dummy:
63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
64 integration=IntegrationType.key)
64 integration=IntegrationType.key)
65 response.mustcontain(url)
65 response.mustcontain(url)
66
66
67 @pytest.mark.parametrize(
67 @pytest.mark.parametrize(
68 'IntegrationType', integration_type_registry.values())
68 'IntegrationType', integration_type_registry.values())
69 def test_get_create_integration_page(self, IntegrationType):
69 def test_get_create_integration_page(self, IntegrationType):
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
71 integration_key=IntegrationType.key)
71 integration_key=IntegrationType.key)
72 if IntegrationType.is_dummy:
72 if IntegrationType.is_dummy:
73 self.app.get(url, status=404)
73 self.app.get(url, status=404)
74 else:
74 else:
75 response = self.app.get(url, status=200)
75 response = self.app.get(url, status=200)
76 response.mustcontain(IntegrationType.display_name)
76 response.mustcontain(IntegrationType.display_name)
77
77
78 def test_post_integration_page(self, StubIntegrationType, csrf_token,
78 def test_post_integration_page(self, StubIntegrationType, csrf_token,
79 test_repo_group, backend_random):
79 test_repo_group, backend_random):
80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
81 integration_key=StubIntegrationType.key)
81 integration_key=StubIntegrationType.key)
82
82
83 _post_integration_test_helper(
83 _post_integration_test_helper(
84 self.app, url, csrf_token, admin_view=True,
84 self.app, url, csrf_token, admin_view=True,
85 repo=backend_random.repo, repo_group=test_repo_group)
85 repo=backend_random.repo, repo_group=test_repo_group)
86
86
87
87
88 class TestRepoIntegrationsView(TestIntegrationsView):
88 class TestRepoIntegrationsView(TestIntegrationsView):
89 def test_index_no_integrations(self, backend_random):
89 def test_index_no_integrations(self, backend_random):
90 url = '/{repo_name}/settings/integrations'.format(
90 url = '/{repo_name}/settings/integrations'.format(
91 repo_name=backend_random.repo.repo_name)
91 repo_name=backend_random.repo.repo_name)
92 response = self.app.get(url)
92 response = self.app.get(url)
93
93
94 assert response.status_code == 200
94 assert response.status_code == 200
95 response.mustcontain('exist yet')
95 response.mustcontain('exist yet')
96
96
97 def test_index_with_integrations(self, repo_integration_stub):
97 def test_index_with_integrations(self, repo_integration_stub):
98 url = '/{repo_name}/settings/integrations'.format(
98 url = '/{repo_name}/settings/integrations'.format(
99 repo_name=repo_integration_stub.repo.repo_name)
99 repo_name=repo_integration_stub.repo.repo_name)
100 stub_name = repo_integration_stub.name
100 stub_name = repo_integration_stub.name
101
101
102 response = self.app.get(url)
102 response = self.app.get(url)
103
103
104 assert response.status_code == 200
104 assert response.status_code == 200
105 response.mustcontain(stub_name)
105 response.mustcontain(stub_name)
106 response.mustcontain(no=['exist yet'])
106 response.mustcontain(no=['exist yet'])
107
107
108 @pytest.mark.parametrize(
108 @pytest.mark.parametrize(
109 'IntegrationType', integration_type_registry.values())
109 'IntegrationType', integration_type_registry.values())
110 def test_new_integration_page(self, backend_random, IntegrationType):
110 def test_new_integration_page(self, backend_random, IntegrationType):
111 repo_name = backend_random.repo.repo_name
111 repo_name = backend_random.repo.repo_name
112 url = '/{repo_name}/settings/integrations/new'.format(
112 url = '/{repo_name}/settings/integrations/new'.format(
113 repo_name=repo_name)
113 repo_name=repo_name)
114
114
115 response = self.app.get(url, status=200)
115 response = self.app.get(url, status=200)
116
116
117 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
117 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
118 repo_name=repo_name,
118 repo_name=repo_name,
119 integration=IntegrationType.key)
119 integration=IntegrationType.key)
120 if not IntegrationType.is_dummy:
120 if not IntegrationType.is_dummy:
121 response.mustcontain(url)
121 response.mustcontain(url)
122
122
123 @pytest.mark.parametrize(
123 @pytest.mark.parametrize(
124 'IntegrationType', integration_type_registry.values())
124 'IntegrationType', integration_type_registry.values())
125 def test_get_create_integration_page(self, backend_random, IntegrationType):
125 def test_get_create_integration_page(self, backend_random, IntegrationType):
126 repo_name = backend_random.repo.repo_name
126 repo_name = backend_random.repo.repo_name
127 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
127 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
128 repo_name=repo_name, integration_key=IntegrationType.key)
128 repo_name=repo_name, integration_key=IntegrationType.key)
129 if IntegrationType.is_dummy:
129 if IntegrationType.is_dummy:
130 self.app.get(url, status=404)
130 self.app.get(url, status=404)
131 else:
131 else:
132 response = self.app.get(url, status=200)
132 response = self.app.get(url, status=200)
133 response.mustcontain(IntegrationType.display_name)
133 response.mustcontain(IntegrationType.display_name)
134
134
135 def test_post_integration_page(self, backend_random, test_repo_group,
135 def test_post_integration_page(self, backend_random, test_repo_group,
136 StubIntegrationType, csrf_token):
136 StubIntegrationType, csrf_token):
137 repo_name = backend_random.repo.repo_name
137 repo_name = backend_random.repo.repo_name
138 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
138 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
139 repo_name=repo_name, integration_key=StubIntegrationType.key)
139 repo_name=repo_name, integration_key=StubIntegrationType.key)
140
140
141 _post_integration_test_helper(
141 _post_integration_test_helper(
142 self.app, url, csrf_token, admin_view=False,
142 self.app, url, csrf_token, admin_view=False,
143 repo=backend_random.repo, repo_group=test_repo_group)
143 repo=backend_random.repo, repo_group=test_repo_group)
144
144
145
145
146 class TestRepoGroupIntegrationsView(TestIntegrationsView):
146 class TestRepoGroupIntegrationsView(TestIntegrationsView):
147 def test_index_no_integrations(self, test_repo_group):
147 def test_index_no_integrations(self, test_repo_group):
148 url = '/{repo_group_name}/settings/integrations'.format(
148 url = '/{repo_group_name}/_settings/integrations'.format(
149 repo_group_name=test_repo_group.group_name)
149 repo_group_name=test_repo_group.group_name)
150 response = self.app.get(url)
150 response = self.app.get(url)
151
151
152 assert response.status_code == 200
152 assert response.status_code == 200
153 response.mustcontain('exist yet')
153 response.mustcontain('exist yet')
154
154
155 def test_index_with_integrations(
155 def test_index_with_integrations(
156 self, test_repo_group, repogroup_integration_stub):
156 self, test_repo_group, repogroup_integration_stub):
157
157
158 url = '/{repo_group_name}/settings/integrations'.format(
158 url = '/{repo_group_name}/_settings/integrations'.format(
159 repo_group_name=test_repo_group.group_name)
159 repo_group_name=test_repo_group.group_name)
160
160
161 stub_name = repogroup_integration_stub.name
161 stub_name = repogroup_integration_stub.name
162 response = self.app.get(url)
162 response = self.app.get(url)
163
163
164 assert response.status_code == 200
164 assert response.status_code == 200
165 response.mustcontain(no=['exist yet'])
165 response.mustcontain(no=['exist yet'])
166 response.mustcontain(stub_name)
166 response.mustcontain(stub_name)
167
167
168 def test_new_integration_page(self, test_repo_group):
168 def test_new_integration_page(self, test_repo_group):
169 repo_group_name = test_repo_group.group_name
169 repo_group_name = test_repo_group.group_name
170 url = '/{repo_group_name}/settings/integrations/new'.format(
170 url = '/{repo_group_name}/_settings/integrations/new'.format(
171 repo_group_name=test_repo_group.group_name)
171 repo_group_name=test_repo_group.group_name)
172
172
173 response = self.app.get(url)
173 response = self.app.get(url)
174
174
175 assert response.status_code == 200
175 assert response.status_code == 200
176
176
177 for integration_key, integration_obj in integration_type_registry.items():
177 for integration_key, integration_obj in integration_type_registry.items():
178 if not integration_obj.is_dummy:
178 if not integration_obj.is_dummy:
179 nurl = (
179 nurl = (
180 '/{repo_group_name}/settings/integrations/{integration}/new').format(
180 '/{repo_group_name}/_settings/integrations/{integration}/new').format(
181 repo_group_name=repo_group_name,
181 repo_group_name=repo_group_name,
182 integration=integration_key)
182 integration=integration_key)
183 response.mustcontain(nurl)
183 response.mustcontain(nurl)
184
184
185 @pytest.mark.parametrize(
185 @pytest.mark.parametrize(
186 'IntegrationType', integration_type_registry.values())
186 'IntegrationType', integration_type_registry.values())
187 def test_get_create_integration_page(
187 def test_get_create_integration_page(
188 self, test_repo_group, IntegrationType):
188 self, test_repo_group, IntegrationType):
189
189
190 repo_group_name = test_repo_group.group_name
190 repo_group_name = test_repo_group.group_name
191 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
191 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
192 ).format(repo_group_name=repo_group_name,
192 ).format(repo_group_name=repo_group_name,
193 integration_key=IntegrationType.key)
193 integration_key=IntegrationType.key)
194
194
195 if not IntegrationType.is_dummy:
195 if not IntegrationType.is_dummy:
196 response = self.app.get(url, status=200)
196 response = self.app.get(url, status=200)
197 response.mustcontain(IntegrationType.display_name)
197 response.mustcontain(IntegrationType.display_name)
198 else:
198 else:
199 self.app.get(url, status=404)
199 self.app.get(url, status=404)
200
200
201 def test_post_integration_page(self, test_repo_group, backend_random,
201 def test_post_integration_page(self, test_repo_group, backend_random,
202 StubIntegrationType, csrf_token):
202 StubIntegrationType, csrf_token):
203
203
204 repo_group_name = test_repo_group.group_name
204 repo_group_name = test_repo_group.group_name
205 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
205 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
206 ).format(repo_group_name=repo_group_name,
206 ).format(repo_group_name=repo_group_name,
207 integration_key=StubIntegrationType.key)
207 integration_key=StubIntegrationType.key)
208
208
209 _post_integration_test_helper(
209 _post_integration_test_helper(
210 self.app, url, csrf_token, admin_view=False,
210 self.app, url, csrf_token, admin_view=False,
211 repo=backend_random.repo, repo_group=test_repo_group)
211 repo=backend_random.repo, repo_group=test_repo_group)
212
212
213
213
214 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
214 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
215 admin_view):
215 admin_view):
216 """
216 """
217 Posts form data to create integration at the url given then deletes it and
217 Posts form data to create integration at the url given then deletes it and
218 checks if the redirect url is correct.
218 checks if the redirect url is correct.
219 """
219 """
220 repo_name = repo.repo_name
220 repo_name = repo.repo_name
221 repo_group_name = repo_group.group_name
221 repo_group_name = repo_group.group_name
222 app.post(url, params={}, status=403) # missing csrf check
222 app.post(url, params={}, status=403) # missing csrf check
223 response = app.post(url, params={'csrf_token': csrf_token})
223 response = app.post(url, params={'csrf_token': csrf_token})
224 assert response.status_code == 200
224 assert response.status_code == 200
225 response.mustcontain('Errors exist')
225 response.mustcontain('Errors exist')
226
226
227 scopes_destinations = [
227 scopes_destinations = [
228 ('global',
228 ('global',
229 ADMIN_PREFIX + '/integrations'),
229 ADMIN_PREFIX + '/integrations'),
230 ('root-repos',
230 ('root-repos',
231 ADMIN_PREFIX + '/integrations'),
231 ADMIN_PREFIX + '/integrations'),
232 ('repo:%s' % repo_name,
232 ('repo:%s' % repo_name,
233 '/%s/settings/integrations' % repo_name),
233 '/%s/settings/integrations' % repo_name),
234 ('repogroup:%s' % repo_group_name,
234 ('repogroup:%s' % repo_group_name,
235 '/%s/settings/integrations' % repo_group_name),
235 '/%s/_settings/integrations' % repo_group_name),
236 ('repogroup-recursive:%s' % repo_group_name,
236 ('repogroup-recursive:%s' % repo_group_name,
237 '/%s/settings/integrations' % repo_group_name),
237 '/%s/_settings/integrations' % repo_group_name),
238 ]
238 ]
239
239
240 for scope, destination in scopes_destinations:
240 for scope, destination in scopes_destinations:
241 if admin_view:
241 if admin_view:
242 destination = ADMIN_PREFIX + '/integrations'
242 destination = ADMIN_PREFIX + '/integrations'
243
243
244 form_data = [
244 form_data = [
245 ('csrf_token', csrf_token),
245 ('csrf_token', csrf_token),
246 ('__start__', 'options:mapping'),
246 ('__start__', 'options:mapping'),
247 ('name', 'test integration'),
247 ('name', 'test integration'),
248 ('scope', scope),
248 ('scope', scope),
249 ('enabled', 'true'),
249 ('enabled', 'true'),
250 ('__end__', 'options:mapping'),
250 ('__end__', 'options:mapping'),
251 ('__start__', 'settings:mapping'),
251 ('__start__', 'settings:mapping'),
252 ('test_int_field', '34'),
252 ('test_int_field', '34'),
253 ('test_string_field', ''), # empty value on purpose as it's required
253 ('test_string_field', ''), # empty value on purpose as it's required
254 ('__end__', 'settings:mapping'),
254 ('__end__', 'settings:mapping'),
255 ]
255 ]
256 errors_response = app.post(url, form_data)
256 errors_response = app.post(url, form_data)
257 assert 'Errors exist' in errors_response.body
257 assert 'Errors exist' in errors_response.body
258
258
259 form_data[-2] = ('test_string_field', 'data!')
259 form_data[-2] = ('test_string_field', 'data!')
260 assert Session().query(Integration).count() == 0
260 assert Session().query(Integration).count() == 0
261 created_response = app.post(url, form_data)
261 created_response = app.post(url, form_data)
262 assert Session().query(Integration).count() == 1
262 assert Session().query(Integration).count() == 1
263
263
264 delete_response = app.post(
264 delete_response = app.post(
265 created_response.location,
265 created_response.location,
266 params={'csrf_token': csrf_token, 'delete': 'delete'})
266 params={'csrf_token': csrf_token, 'delete': 'delete'})
267
267
268 assert Session().query(Integration).count() == 0
268 assert Session().query(Integration).count() == 0
269 assert delete_response.location.endswith(destination)
269 assert delete_response.location.endswith(destination)
@@ -1,738 +1,764 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-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 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (_hash_key,
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 name_key = _hash_key(repo_group_name)
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 return repo.scalar()
79 return repo.scalar()
80
80
81 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
82 value = SettingsModel().get_setting_by_name(
82 value = SettingsModel().get_setting_by_name(
83 'create_personal_repo_group')
83 'create_personal_repo_group')
84 return value.app_settings_value if value else None or False
84 return value.app_settings_value if value else None or False
85
85
86 def get_personal_group_name_pattern(self):
86 def get_personal_group_name_pattern(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'personal_repo_group_pattern')
88 'personal_repo_group_pattern')
89 val = value.app_settings_value if value else None
89 val = value.app_settings_value if value else None
90 group_template = val or self.PERSONAL_GROUP_PATTERN
90 group_template = val or self.PERSONAL_GROUP_PATTERN
91
91
92 group_template = group_template.lstrip('/')
92 group_template = group_template.lstrip('/')
93 return group_template
93 return group_template
94
94
95 def get_personal_group_name(self, user):
95 def get_personal_group_name(self, user):
96 template = self.get_personal_group_name_pattern()
96 template = self.get_personal_group_name_pattern()
97 return string.Template(template).safe_substitute(
97 return string.Template(template).safe_substitute(
98 username=user.username,
98 username=user.username,
99 user_id=user.user_id,
99 user_id=user.user_id,
100 )
100 )
101
101
102 def create_personal_repo_group(self, user, commit_early=True):
102 def create_personal_repo_group(self, user, commit_early=True):
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 personal_repo_group_name = self.get_personal_group_name(user)
104 personal_repo_group_name = self.get_personal_group_name(user)
105
105
106 # create a new one
106 # create a new one
107 RepoGroupModel().create(
107 RepoGroupModel().create(
108 group_name=personal_repo_group_name,
108 group_name=personal_repo_group_name,
109 group_description=desc,
109 group_description=desc,
110 owner=user.username,
110 owner=user.username,
111 personal=True,
111 personal=True,
112 commit_early=commit_early)
112 commit_early=commit_early)
113
113
114 def _create_default_perms(self, new_group):
114 def _create_default_perms(self, new_group):
115 # create default permission
115 # create default permission
116 default_perm = 'group.read'
116 default_perm = 'group.read'
117 def_user = User.get_default_user()
117 def_user = User.get_default_user()
118 for p in def_user.user_perms:
118 for p in def_user.user_perms:
119 if p.permission.permission_name.startswith('group.'):
119 if p.permission.permission_name.startswith('group.'):
120 default_perm = p.permission.permission_name
120 default_perm = p.permission.permission_name
121 break
121 break
122
122
123 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm = UserRepoGroupToPerm()
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125
125
126 repo_group_to_perm.group = new_group
126 repo_group_to_perm.group = new_group
127 repo_group_to_perm.user_id = def_user.user_id
127 repo_group_to_perm.user_id = def_user.user_id
128 return repo_group_to_perm
128 return repo_group_to_perm
129
129
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 get_object=False):
131 get_object=False):
132 """
132 """
133 Get's the group name and a parent group name from given group name.
133 Get's the group name and a parent group name from given group name.
134 If repo_in_path is set to truth, we asume the full path also includes
134 If repo_in_path is set to truth, we asume the full path also includes
135 repo name, in such case we clean the last element.
135 repo name, in such case we clean the last element.
136
136
137 :param group_name_full:
137 :param group_name_full:
138 """
138 """
139 split_paths = 1
139 split_paths = 1
140 if repo_in_path:
140 if repo_in_path:
141 split_paths = 2
141 split_paths = 2
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143
143
144 if repo_in_path and len(_parts) > 1:
144 if repo_in_path and len(_parts) > 1:
145 # such case last element is the repo_name
145 # such case last element is the repo_name
146 _parts.pop(-1)
146 _parts.pop(-1)
147 group_name_cleaned = _parts[-1] # just the group name
147 group_name_cleaned = _parts[-1] # just the group name
148 parent_repo_group_name = None
148 parent_repo_group_name = None
149
149
150 if len(_parts) > 1:
150 if len(_parts) > 1:
151 parent_repo_group_name = _parts[0]
151 parent_repo_group_name = _parts[0]
152
152
153 parent_group = None
153 parent_group = None
154 if parent_repo_group_name:
154 if parent_repo_group_name:
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156
156
157 if get_object:
157 if get_object:
158 return group_name_cleaned, parent_repo_group_name, parent_group
158 return group_name_cleaned, parent_repo_group_name, parent_group
159
159
160 return group_name_cleaned, parent_repo_group_name
160 return group_name_cleaned, parent_repo_group_name
161
161
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 create_path = os.path.join(self.repos_path, group_name)
163 create_path = os.path.join(self.repos_path, group_name)
164 log.debug('creating new group in %s', create_path)
164 log.debug('creating new group in %s', create_path)
165
165
166 if os.path.isdir(create_path):
166 if os.path.isdir(create_path):
167 if exc_on_failure:
167 if exc_on_failure:
168 abs_create_path = os.path.abspath(create_path)
168 abs_create_path = os.path.abspath(create_path)
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 return False
170 return False
171 return True
171 return True
172
172
173 def _create_group(self, group_name):
173 def _create_group(self, group_name):
174 """
174 """
175 makes repository group on filesystem
175 makes repository group on filesystem
176
176
177 :param repo_name:
177 :param repo_name:
178 :param parent_id:
178 :param parent_id:
179 """
179 """
180
180
181 self.check_exist_filesystem(group_name)
181 self.check_exist_filesystem(group_name)
182 create_path = os.path.join(self.repos_path, group_name)
182 create_path = os.path.join(self.repos_path, group_name)
183 log.debug('creating new group in %s', create_path)
183 log.debug('creating new group in %s', create_path)
184 os.makedirs(create_path, mode=0755)
184 os.makedirs(create_path, mode=0755)
185 log.debug('created group in %s', create_path)
185 log.debug('created group in %s', create_path)
186
186
187 def _rename_group(self, old, new):
187 def _rename_group(self, old, new):
188 """
188 """
189 Renames a group on filesystem
189 Renames a group on filesystem
190
190
191 :param group_name:
191 :param group_name:
192 """
192 """
193
193
194 if old == new:
194 if old == new:
195 log.debug('skipping group rename')
195 log.debug('skipping group rename')
196 return
196 return
197
197
198 log.debug('renaming repository group from %s to %s', old, new)
198 log.debug('renaming repository group from %s to %s', old, new)
199
199
200 old_path = os.path.join(self.repos_path, old)
200 old_path = os.path.join(self.repos_path, old)
201 new_path = os.path.join(self.repos_path, new)
201 new_path = os.path.join(self.repos_path, new)
202
202
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204
204
205 if os.path.isdir(new_path):
205 if os.path.isdir(new_path):
206 raise Exception('Was trying to rename to already '
206 raise Exception('Was trying to rename to already '
207 'existing dir %s' % new_path)
207 'existing dir %s' % new_path)
208 shutil.move(old_path, new_path)
208 shutil.move(old_path, new_path)
209
209
210 def _delete_filesystem_group(self, group, force_delete=False):
210 def _delete_filesystem_group(self, group, force_delete=False):
211 """
211 """
212 Deletes a group from a filesystem
212 Deletes a group from a filesystem
213
213
214 :param group: instance of group from database
214 :param group: instance of group from database
215 :param force_delete: use shutil rmtree to remove all objects
215 :param force_delete: use shutil rmtree to remove all objects
216 """
216 """
217 paths = group.full_path.split(RepoGroup.url_sep())
217 paths = group.full_path.split(RepoGroup.url_sep())
218 paths = os.sep.join(paths)
218 paths = os.sep.join(paths)
219
219
220 rm_path = os.path.join(self.repos_path, paths)
220 rm_path = os.path.join(self.repos_path, paths)
221 log.info("Removing group %s", rm_path)
221 log.info("Removing group %s", rm_path)
222 # delete only if that path really exists
222 # delete only if that path really exists
223 if os.path.isdir(rm_path):
223 if os.path.isdir(rm_path):
224 if force_delete:
224 if force_delete:
225 shutil.rmtree(rm_path)
225 shutil.rmtree(rm_path)
226 else:
226 else:
227 # archive that group`
227 # archive that group`
228 _now = datetime.datetime.now()
228 _now = datetime.datetime.now()
229 _ms = str(_now.microsecond).rjust(6, '0')
229 _ms = str(_now.microsecond).rjust(6, '0')
230 _d = 'rm__%s_GROUP_%s' % (
230 _d = 'rm__%s_GROUP_%s' % (
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233
233
234 def create(self, group_name, group_description, owner, just_db=False,
234 def create(self, group_name, group_description, owner, just_db=False,
235 copy_permissions=False, personal=None, commit_early=True):
235 copy_permissions=False, personal=None, commit_early=True):
236
236
237 (group_name_cleaned,
237 (group_name_cleaned,
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239
239
240 parent_group = None
240 parent_group = None
241 if parent_group_name:
241 if parent_group_name:
242 parent_group = self._get_repo_group(parent_group_name)
242 parent_group = self._get_repo_group(parent_group_name)
243 if not parent_group:
243 if not parent_group:
244 # we tried to create a nested group, but the parent is not
244 # we tried to create a nested group, but the parent is not
245 # existing
245 # existing
246 raise ValueError(
246 raise ValueError(
247 'Parent group `%s` given in `%s` group name '
247 'Parent group `%s` given in `%s` group name '
248 'is not yet existing.' % (parent_group_name, group_name))
248 'is not yet existing.' % (parent_group_name, group_name))
249
249
250 # because we are doing a cleanup, we need to check if such directory
250 # because we are doing a cleanup, we need to check if such directory
251 # already exists. If we don't do that we can accidentally delete
251 # already exists. If we don't do that we can accidentally delete
252 # existing directory via cleanup that can cause data issues, since
252 # existing directory via cleanup that can cause data issues, since
253 # delete does a folder rename to special syntax later cleanup
253 # delete does a folder rename to special syntax later cleanup
254 # functions can delete this
254 # functions can delete this
255 cleanup_group = self.check_exist_filesystem(group_name,
255 cleanup_group = self.check_exist_filesystem(group_name,
256 exc_on_failure=False)
256 exc_on_failure=False)
257 user = self._get_user(owner)
258 if not user:
259 raise ValueError('Owner %s not found as rhodecode user', owner)
260
257 try:
261 try:
258 user = self._get_user(owner)
259 new_repo_group = RepoGroup()
262 new_repo_group = RepoGroup()
260 new_repo_group.user = user
263 new_repo_group.user = user
261 new_repo_group.group_description = group_description or group_name
264 new_repo_group.group_description = group_description or group_name
262 new_repo_group.parent_group = parent_group
265 new_repo_group.parent_group = parent_group
263 new_repo_group.group_name = group_name
266 new_repo_group.group_name = group_name
264 new_repo_group.personal = personal
267 new_repo_group.personal = personal
265
268
266 self.sa.add(new_repo_group)
269 self.sa.add(new_repo_group)
267
270
268 # create an ADMIN permission for owner except if we're super admin,
271 # create an ADMIN permission for owner except if we're super admin,
269 # later owner should go into the owner field of groups
272 # later owner should go into the owner field of groups
270 if not user.is_admin:
273 if not user.is_admin:
271 self.grant_user_permission(repo_group=new_repo_group,
274 self.grant_user_permission(repo_group=new_repo_group,
272 user=owner, perm='group.admin')
275 user=owner, perm='group.admin')
273
276
274 if parent_group and copy_permissions:
277 if parent_group and copy_permissions:
275 # copy permissions from parent
278 # copy permissions from parent
276 user_perms = UserRepoGroupToPerm.query() \
279 user_perms = UserRepoGroupToPerm.query() \
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
280 .filter(UserRepoGroupToPerm.group == parent_group).all()
278
281
279 group_perms = UserGroupRepoGroupToPerm.query() \
282 group_perms = UserGroupRepoGroupToPerm.query() \
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
283 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281
284
282 for perm in user_perms:
285 for perm in user_perms:
283 # don't copy over the permission for user who is creating
286 # don't copy over the permission for user who is creating
284 # this group, if he is not super admin he get's admin
287 # this group, if he is not super admin he get's admin
285 # permission set above
288 # permission set above
286 if perm.user != user or user.is_admin:
289 if perm.user != user or user.is_admin:
287 UserRepoGroupToPerm.create(
290 UserRepoGroupToPerm.create(
288 perm.user, new_repo_group, perm.permission)
291 perm.user, new_repo_group, perm.permission)
289
292
290 for perm in group_perms:
293 for perm in group_perms:
291 UserGroupRepoGroupToPerm.create(
294 UserGroupRepoGroupToPerm.create(
292 perm.users_group, new_repo_group, perm.permission)
295 perm.users_group, new_repo_group, perm.permission)
293 else:
296 else:
294 perm_obj = self._create_default_perms(new_repo_group)
297 perm_obj = self._create_default_perms(new_repo_group)
295 self.sa.add(perm_obj)
298 self.sa.add(perm_obj)
296
299
297 # now commit the changes, earlier so we are sure everything is in
300 # now commit the changes, earlier so we are sure everything is in
298 # the database.
301 # the database.
299 if commit_early:
302 if commit_early:
300 self.sa.commit()
303 self.sa.commit()
301 if not just_db:
304 if not just_db:
302 self._create_group(new_repo_group.group_name)
305 self._create_group(new_repo_group.group_name)
303
306
304 # trigger the post hook
307 # trigger the post hook
305 from rhodecode.lib.hooks_base import log_create_repository_group
308 from rhodecode.lib.hooks_base import log_create_repository_group
306 repo_group = RepoGroup.get_by_group_name(group_name)
309 repo_group = RepoGroup.get_by_group_name(group_name)
307 log_create_repository_group(
310 log_create_repository_group(
308 created_by=user.username, **repo_group.get_dict())
311 created_by=user.username, **repo_group.get_dict())
309
312
310 # Trigger create event.
313 # Trigger create event.
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
314 events.trigger(events.RepoGroupCreateEvent(repo_group))
312
315
313 return new_repo_group
316 return new_repo_group
314 except Exception:
317 except Exception:
315 self.sa.rollback()
318 self.sa.rollback()
316 log.exception('Exception occurred when creating repository group, '
319 log.exception('Exception occurred when creating repository group, '
317 'doing cleanup...')
320 'doing cleanup...')
318 # rollback things manually !
321 # rollback things manually !
319 repo_group = RepoGroup.get_by_group_name(group_name)
322 repo_group = RepoGroup.get_by_group_name(group_name)
320 if repo_group:
323 if repo_group:
321 RepoGroup.delete(repo_group.group_id)
324 RepoGroup.delete(repo_group.group_id)
322 self.sa.commit()
325 self.sa.commit()
323 if cleanup_group:
326 if cleanup_group:
324 RepoGroupModel()._delete_filesystem_group(repo_group)
327 RepoGroupModel()._delete_filesystem_group(repo_group)
325 raise
328 raise
326
329
327 def update_permissions(
330 def update_permissions(
328 self, repo_group, perm_additions=None, perm_updates=None,
331 self, repo_group, perm_additions=None, perm_updates=None,
329 perm_deletions=None, recursive=None, check_perms=True,
332 perm_deletions=None, recursive=None, check_perms=True,
330 cur_user=None):
333 cur_user=None):
331 from rhodecode.model.repo import RepoModel
334 from rhodecode.model.repo import RepoModel
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
335 from rhodecode.lib.auth import HasUserGroupPermissionAny
333
336
334 if not perm_additions:
337 if not perm_additions:
335 perm_additions = []
338 perm_additions = []
336 if not perm_updates:
339 if not perm_updates:
337 perm_updates = []
340 perm_updates = []
338 if not perm_deletions:
341 if not perm_deletions:
339 perm_deletions = []
342 perm_deletions = []
340
343
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
344 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342
345
343 changes = {
346 changes = {
344 'added': [],
347 'added': [],
345 'updated': [],
348 'updated': [],
346 'deleted': []
349 'deleted': []
347 }
350 }
348
351
349 def _set_perm_user(obj, user, perm):
352 def _set_perm_user(obj, user, perm):
350 if isinstance(obj, RepoGroup):
353 if isinstance(obj, RepoGroup):
351 self.grant_user_permission(
354 self.grant_user_permission(
352 repo_group=obj, user=user, perm=perm)
355 repo_group=obj, user=user, perm=perm)
353 elif isinstance(obj, Repository):
356 elif isinstance(obj, Repository):
354 # private repos will not allow to change the default
357 # private repos will not allow to change the default
355 # permissions using recursive mode
358 # permissions using recursive mode
356 if obj.private and user == User.DEFAULT_USER:
359 if obj.private and user == User.DEFAULT_USER:
357 return
360 return
358
361
359 # we set group permission but we have to switch to repo
362 # we set group permission but we have to switch to repo
360 # permission
363 # permission
361 perm = perm.replace('group.', 'repository.')
364 perm = perm.replace('group.', 'repository.')
362 RepoModel().grant_user_permission(
365 RepoModel().grant_user_permission(
363 repo=obj, user=user, perm=perm)
366 repo=obj, user=user, perm=perm)
364
367
365 def _set_perm_group(obj, users_group, perm):
368 def _set_perm_group(obj, users_group, perm):
366 if isinstance(obj, RepoGroup):
369 if isinstance(obj, RepoGroup):
367 self.grant_user_group_permission(
370 self.grant_user_group_permission(
368 repo_group=obj, group_name=users_group, perm=perm)
371 repo_group=obj, group_name=users_group, perm=perm)
369 elif isinstance(obj, Repository):
372 elif isinstance(obj, Repository):
370 # we set group permission but we have to switch to repo
373 # we set group permission but we have to switch to repo
371 # permission
374 # permission
372 perm = perm.replace('group.', 'repository.')
375 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_group_permission(
376 RepoModel().grant_user_group_permission(
374 repo=obj, group_name=users_group, perm=perm)
377 repo=obj, group_name=users_group, perm=perm)
375
378
376 def _revoke_perm_user(obj, user):
379 def _revoke_perm_user(obj, user):
377 if isinstance(obj, RepoGroup):
380 if isinstance(obj, RepoGroup):
378 self.revoke_user_permission(repo_group=obj, user=user)
381 self.revoke_user_permission(repo_group=obj, user=user)
379 elif isinstance(obj, Repository):
382 elif isinstance(obj, Repository):
380 RepoModel().revoke_user_permission(repo=obj, user=user)
383 RepoModel().revoke_user_permission(repo=obj, user=user)
381
384
382 def _revoke_perm_group(obj, user_group):
385 def _revoke_perm_group(obj, user_group):
383 if isinstance(obj, RepoGroup):
386 if isinstance(obj, RepoGroup):
384 self.revoke_user_group_permission(
387 self.revoke_user_group_permission(
385 repo_group=obj, group_name=user_group)
388 repo_group=obj, group_name=user_group)
386 elif isinstance(obj, Repository):
389 elif isinstance(obj, Repository):
387 RepoModel().revoke_user_group_permission(
390 RepoModel().revoke_user_group_permission(
388 repo=obj, group_name=user_group)
391 repo=obj, group_name=user_group)
389
392
390 # start updates
393 # start updates
391 log.debug('Now updating permissions for %s in recursive mode:%s',
394 log.debug('Now updating permissions for %s in recursive mode:%s',
392 repo_group, recursive)
395 repo_group, recursive)
393
396
394 # initialize check function, we'll call that multiple times
397 # initialize check function, we'll call that multiple times
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
398 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396
399
397 for obj in repo_group.recursive_groups_and_repos():
400 for obj in repo_group.recursive_groups_and_repos():
398 # iterated obj is an instance of a repos group or repository in
401 # iterated obj is an instance of a repos group or repository in
399 # that group, recursive option can be: none, repos, groups, all
402 # that group, recursive option can be: none, repos, groups, all
400 if recursive == 'all':
403 if recursive == 'all':
401 obj = obj
404 obj = obj
402 elif recursive == 'repos':
405 elif recursive == 'repos':
403 # skip groups, other than this one
406 # skip groups, other than this one
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
407 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 continue
408 continue
406 elif recursive == 'groups':
409 elif recursive == 'groups':
407 # skip repos
410 # skip repos
408 if isinstance(obj, Repository):
411 if isinstance(obj, Repository):
409 continue
412 continue
410 else: # recursive == 'none':
413 else: # recursive == 'none':
411 # DEFAULT option - don't apply to iterated objects
414 # DEFAULT option - don't apply to iterated objects
412 # also we do a break at the end of this loop. if we are not
415 # also we do a break at the end of this loop. if we are not
413 # in recursive mode
416 # in recursive mode
414 obj = repo_group
417 obj = repo_group
415
418
416 change_obj = obj.get_api_data()
419 change_obj = obj.get_api_data()
417
420
418 # update permissions
421 # update permissions
419 for member_id, perm, member_type in perm_updates:
422 for member_id, perm, member_type in perm_updates:
420 member_id = int(member_id)
423 member_id = int(member_id)
421 if member_type == 'user':
424 if member_type == 'user':
422 member_name = User.get(member_id).username
425 member_name = User.get(member_id).username
423 # this updates also current one if found
426 # this updates also current one if found
424 _set_perm_user(obj, user=member_id, perm=perm)
427 _set_perm_user(obj, user=member_id, perm=perm)
425 else: # set for user group
428 else: # set for user group
426 member_name = UserGroup.get(member_id).users_group_name
429 member_name = UserGroup.get(member_id).users_group_name
427 if not check_perms or has_group_perm(member_name,
430 if not check_perms or has_group_perm(member_name,
428 user=cur_user):
431 user=cur_user):
429 _set_perm_group(obj, users_group=member_id, perm=perm)
432 _set_perm_group(obj, users_group=member_id, perm=perm)
430
433
431 changes['updated'].append(
434 changes['updated'].append(
432 {'change_obj': change_obj, 'type': member_type,
435 {'change_obj': change_obj, 'type': member_type,
433 'id': member_id, 'name': member_name, 'new_perm': perm})
436 'id': member_id, 'name': member_name, 'new_perm': perm})
434
437
435 # set new permissions
438 # set new permissions
436 for member_id, perm, member_type in perm_additions:
439 for member_id, perm, member_type in perm_additions:
437 member_id = int(member_id)
440 member_id = int(member_id)
438 if member_type == 'user':
441 if member_type == 'user':
439 member_name = User.get(member_id).username
442 member_name = User.get(member_id).username
440 _set_perm_user(obj, user=member_id, perm=perm)
443 _set_perm_user(obj, user=member_id, perm=perm)
441 else: # set for user group
444 else: # set for user group
442 # check if we have permissions to alter this usergroup
445 # check if we have permissions to alter this usergroup
443 member_name = UserGroup.get(member_id).users_group_name
446 member_name = UserGroup.get(member_id).users_group_name
444 if not check_perms or has_group_perm(member_name,
447 if not check_perms or has_group_perm(member_name,
445 user=cur_user):
448 user=cur_user):
446 _set_perm_group(obj, users_group=member_id, perm=perm)
449 _set_perm_group(obj, users_group=member_id, perm=perm)
447
450
448 changes['added'].append(
451 changes['added'].append(
449 {'change_obj': change_obj, 'type': member_type,
452 {'change_obj': change_obj, 'type': member_type,
450 'id': member_id, 'name': member_name, 'new_perm': perm})
453 'id': member_id, 'name': member_name, 'new_perm': perm})
451
454
452 # delete permissions
455 # delete permissions
453 for member_id, perm, member_type in perm_deletions:
456 for member_id, perm, member_type in perm_deletions:
454 member_id = int(member_id)
457 member_id = int(member_id)
455 if member_type == 'user':
458 if member_type == 'user':
456 member_name = User.get(member_id).username
459 member_name = User.get(member_id).username
457 _revoke_perm_user(obj, user=member_id)
460 _revoke_perm_user(obj, user=member_id)
458 else: # set for user group
461 else: # set for user group
459 # check if we have permissions to alter this usergroup
462 # check if we have permissions to alter this usergroup
460 member_name = UserGroup.get(member_id).users_group_name
463 member_name = UserGroup.get(member_id).users_group_name
461 if not check_perms or has_group_perm(member_name,
464 if not check_perms or has_group_perm(member_name,
462 user=cur_user):
465 user=cur_user):
463 _revoke_perm_group(obj, user_group=member_id)
466 _revoke_perm_group(obj, user_group=member_id)
464
467
465 changes['deleted'].append(
468 changes['deleted'].append(
466 {'change_obj': change_obj, 'type': member_type,
469 {'change_obj': change_obj, 'type': member_type,
467 'id': member_id, 'name': member_name, 'new_perm': perm})
470 'id': member_id, 'name': member_name, 'new_perm': perm})
468
471
469 # if it's not recursive call for all,repos,groups
472 # if it's not recursive call for all,repos,groups
470 # break the loop and don't proceed with other changes
473 # break the loop and don't proceed with other changes
471 if recursive not in ['all', 'repos', 'groups']:
474 if recursive not in ['all', 'repos', 'groups']:
472 break
475 break
473
476
474 return changes
477 return changes
475
478
476 def update(self, repo_group, form_data):
479 def update(self, repo_group, form_data):
477 try:
480 try:
478 repo_group = self._get_repo_group(repo_group)
481 repo_group = self._get_repo_group(repo_group)
479 old_path = repo_group.full_path
482 old_path = repo_group.full_path
480
483
481 # change properties
484 # change properties
482 if 'group_description' in form_data:
485 if 'group_description' in form_data:
483 repo_group.group_description = form_data['group_description']
486 repo_group.group_description = form_data['group_description']
484
487
485 if 'enable_locking' in form_data:
488 if 'enable_locking' in form_data:
486 repo_group.enable_locking = form_data['enable_locking']
489 repo_group.enable_locking = form_data['enable_locking']
487
490
488 if 'group_parent_id' in form_data:
491 if 'group_parent_id' in form_data:
489 parent_group = (
492 parent_group = (
490 self._get_repo_group(form_data['group_parent_id']))
493 self._get_repo_group(form_data['group_parent_id']))
491 repo_group.group_parent_id = (
494 repo_group.group_parent_id = (
492 parent_group.group_id if parent_group else None)
495 parent_group.group_id if parent_group else None)
493 repo_group.parent_group = parent_group
496 repo_group.parent_group = parent_group
494
497
495 # mikhail: to update the full_path, we have to explicitly
498 # mikhail: to update the full_path, we have to explicitly
496 # update group_name
499 # update group_name
497 group_name = form_data.get('group_name', repo_group.name)
500 group_name = form_data.get('group_name', repo_group.name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
501 repo_group.group_name = repo_group.get_new_name(group_name)
499
502
500 new_path = repo_group.full_path
503 new_path = repo_group.full_path
501
504
502 if 'user' in form_data:
505 if 'user' in form_data:
503 repo_group.user = User.get_by_username(form_data['user'])
506 repo_group.user = User.get_by_username(form_data['user'])
504 repo_group.updated_on = datetime.datetime.now()
507 repo_group.updated_on = datetime.datetime.now()
505 self.sa.add(repo_group)
508 self.sa.add(repo_group)
506
509
507 # iterate over all members of this groups and do fixes
510 # iterate over all members of this groups and do fixes
508 # set locking if given
511 # set locking if given
509 # if obj is a repoGroup also fix the name of the group according
512 # if obj is a repoGroup also fix the name of the group according
510 # to the parent
513 # to the parent
511 # if obj is a Repo fix it's name
514 # if obj is a Repo fix it's name
512 # this can be potentially heavy operation
515 # this can be potentially heavy operation
513 for obj in repo_group.recursive_groups_and_repos():
516 for obj in repo_group.recursive_groups_and_repos():
514 # set the value from it's parent
517 # set the value from it's parent
515 obj.enable_locking = repo_group.enable_locking
518 obj.enable_locking = repo_group.enable_locking
516 if isinstance(obj, RepoGroup):
519 if isinstance(obj, RepoGroup):
517 new_name = obj.get_new_name(obj.name)
520 new_name = obj.get_new_name(obj.name)
518 log.debug('Fixing group %s to new name %s',
521 log.debug('Fixing group %s to new name %s',
519 obj.group_name, new_name)
522 obj.group_name, new_name)
520 obj.group_name = new_name
523 obj.group_name = new_name
521 obj.updated_on = datetime.datetime.now()
524 obj.updated_on = datetime.datetime.now()
522 elif isinstance(obj, Repository):
525 elif isinstance(obj, Repository):
523 # we need to get all repositories from this new group and
526 # we need to get all repositories from this new group and
524 # rename them accordingly to new group path
527 # rename them accordingly to new group path
525 new_name = obj.get_new_name(obj.just_name)
528 new_name = obj.get_new_name(obj.just_name)
526 log.debug('Fixing repo %s to new name %s',
529 log.debug('Fixing repo %s to new name %s',
527 obj.repo_name, new_name)
530 obj.repo_name, new_name)
528 obj.repo_name = new_name
531 obj.repo_name = new_name
529 obj.updated_on = datetime.datetime.now()
532 obj.updated_on = datetime.datetime.now()
530 self.sa.add(obj)
533 self.sa.add(obj)
531
534
532 self._rename_group(old_path, new_path)
535 self._rename_group(old_path, new_path)
533
536
534 # Trigger update event.
537 # Trigger update event.
535 events.trigger(events.RepoGroupUpdateEvent(repo_group))
538 events.trigger(events.RepoGroupUpdateEvent(repo_group))
536
539
537 return repo_group
540 return repo_group
538 except Exception:
541 except Exception:
539 log.error(traceback.format_exc())
542 log.error(traceback.format_exc())
540 raise
543 raise
541
544
542 def delete(self, repo_group, force_delete=False, fs_remove=True):
545 def delete(self, repo_group, force_delete=False, fs_remove=True):
543 repo_group = self._get_repo_group(repo_group)
546 repo_group = self._get_repo_group(repo_group)
544 if not repo_group:
547 if not repo_group:
545 return False
548 return False
546 try:
549 try:
547 self.sa.delete(repo_group)
550 self.sa.delete(repo_group)
548 if fs_remove:
551 if fs_remove:
549 self._delete_filesystem_group(repo_group, force_delete)
552 self._delete_filesystem_group(repo_group, force_delete)
550 else:
553 else:
551 log.debug('skipping removal from filesystem')
554 log.debug('skipping removal from filesystem')
552
555
553 # Trigger delete event.
556 # Trigger delete event.
554 events.trigger(events.RepoGroupDeleteEvent(repo_group))
557 events.trigger(events.RepoGroupDeleteEvent(repo_group))
555 return True
558 return True
556
559
557 except Exception:
560 except Exception:
558 log.error('Error removing repo_group %s', repo_group)
561 log.error('Error removing repo_group %s', repo_group)
559 raise
562 raise
560
563
561 def grant_user_permission(self, repo_group, user, perm):
564 def grant_user_permission(self, repo_group, user, perm):
562 """
565 """
563 Grant permission for user on given repository group, or update
566 Grant permission for user on given repository group, or update
564 existing one if found
567 existing one if found
565
568
566 :param repo_group: Instance of RepoGroup, repositories_group_id,
569 :param repo_group: Instance of RepoGroup, repositories_group_id,
567 or repositories_group name
570 or repositories_group name
568 :param user: Instance of User, user_id or username
571 :param user: Instance of User, user_id or username
569 :param perm: Instance of Permission, or permission_name
572 :param perm: Instance of Permission, or permission_name
570 """
573 """
571
574
572 repo_group = self._get_repo_group(repo_group)
575 repo_group = self._get_repo_group(repo_group)
573 user = self._get_user(user)
576 user = self._get_user(user)
574 permission = self._get_perm(perm)
577 permission = self._get_perm(perm)
575
578
576 # check if we have that permission already
579 # check if we have that permission already
577 obj = self.sa.query(UserRepoGroupToPerm)\
580 obj = self.sa.query(UserRepoGroupToPerm)\
578 .filter(UserRepoGroupToPerm.user == user)\
581 .filter(UserRepoGroupToPerm.user == user)\
579 .filter(UserRepoGroupToPerm.group == repo_group)\
582 .filter(UserRepoGroupToPerm.group == repo_group)\
580 .scalar()
583 .scalar()
581 if obj is None:
584 if obj is None:
582 # create new !
585 # create new !
583 obj = UserRepoGroupToPerm()
586 obj = UserRepoGroupToPerm()
584 obj.group = repo_group
587 obj.group = repo_group
585 obj.user = user
588 obj.user = user
586 obj.permission = permission
589 obj.permission = permission
587 self.sa.add(obj)
590 self.sa.add(obj)
588 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
591 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
589 action_logger_generic(
592 action_logger_generic(
590 'granted permission: {} to user: {} on repogroup: {}'.format(
593 'granted permission: {} to user: {} on repogroup: {}'.format(
591 perm, user, repo_group), namespace='security.repogroup')
594 perm, user, repo_group), namespace='security.repogroup')
592 return obj
595 return obj
593
596
594 def revoke_user_permission(self, repo_group, user):
597 def revoke_user_permission(self, repo_group, user):
595 """
598 """
596 Revoke permission for user on given repository group
599 Revoke permission for user on given repository group
597
600
598 :param repo_group: Instance of RepoGroup, repositories_group_id,
601 :param repo_group: Instance of RepoGroup, repositories_group_id,
599 or repositories_group name
602 or repositories_group name
600 :param user: Instance of User, user_id or username
603 :param user: Instance of User, user_id or username
601 """
604 """
602
605
603 repo_group = self._get_repo_group(repo_group)
606 repo_group = self._get_repo_group(repo_group)
604 user = self._get_user(user)
607 user = self._get_user(user)
605
608
606 obj = self.sa.query(UserRepoGroupToPerm)\
609 obj = self.sa.query(UserRepoGroupToPerm)\
607 .filter(UserRepoGroupToPerm.user == user)\
610 .filter(UserRepoGroupToPerm.user == user)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
611 .filter(UserRepoGroupToPerm.group == repo_group)\
609 .scalar()
612 .scalar()
610 if obj:
613 if obj:
611 self.sa.delete(obj)
614 self.sa.delete(obj)
612 log.debug('Revoked perm on %s on %s', repo_group, user)
615 log.debug('Revoked perm on %s on %s', repo_group, user)
613 action_logger_generic(
616 action_logger_generic(
614 'revoked permission from user: {} on repogroup: {}'.format(
617 'revoked permission from user: {} on repogroup: {}'.format(
615 user, repo_group), namespace='security.repogroup')
618 user, repo_group), namespace='security.repogroup')
616
619
617 def grant_user_group_permission(self, repo_group, group_name, perm):
620 def grant_user_group_permission(self, repo_group, group_name, perm):
618 """
621 """
619 Grant permission for user group on given repository group, or update
622 Grant permission for user group on given repository group, or update
620 existing one if found
623 existing one if found
621
624
622 :param repo_group: Instance of RepoGroup, repositories_group_id,
625 :param repo_group: Instance of RepoGroup, repositories_group_id,
623 or repositories_group name
626 or repositories_group name
624 :param group_name: Instance of UserGroup, users_group_id,
627 :param group_name: Instance of UserGroup, users_group_id,
625 or user group name
628 or user group name
626 :param perm: Instance of Permission, or permission_name
629 :param perm: Instance of Permission, or permission_name
627 """
630 """
628 repo_group = self._get_repo_group(repo_group)
631 repo_group = self._get_repo_group(repo_group)
629 group_name = self._get_user_group(group_name)
632 group_name = self._get_user_group(group_name)
630 permission = self._get_perm(perm)
633 permission = self._get_perm(perm)
631
634
632 # check if we have that permission already
635 # check if we have that permission already
633 obj = self.sa.query(UserGroupRepoGroupToPerm)\
636 obj = self.sa.query(UserGroupRepoGroupToPerm)\
634 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
637 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
635 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
638 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
636 .scalar()
639 .scalar()
637
640
638 if obj is None:
641 if obj is None:
639 # create new
642 # create new
640 obj = UserGroupRepoGroupToPerm()
643 obj = UserGroupRepoGroupToPerm()
641
644
642 obj.group = repo_group
645 obj.group = repo_group
643 obj.users_group = group_name
646 obj.users_group = group_name
644 obj.permission = permission
647 obj.permission = permission
645 self.sa.add(obj)
648 self.sa.add(obj)
646 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
649 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
647 action_logger_generic(
650 action_logger_generic(
648 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
651 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
649 perm, group_name, repo_group), namespace='security.repogroup')
652 perm, group_name, repo_group), namespace='security.repogroup')
650 return obj
653 return obj
651
654
652 def revoke_user_group_permission(self, repo_group, group_name):
655 def revoke_user_group_permission(self, repo_group, group_name):
653 """
656 """
654 Revoke permission for user group on given repository group
657 Revoke permission for user group on given repository group
655
658
656 :param repo_group: Instance of RepoGroup, repositories_group_id,
659 :param repo_group: Instance of RepoGroup, repositories_group_id,
657 or repositories_group name
660 or repositories_group name
658 :param group_name: Instance of UserGroup, users_group_id,
661 :param group_name: Instance of UserGroup, users_group_id,
659 or user group name
662 or user group name
660 """
663 """
661 repo_group = self._get_repo_group(repo_group)
664 repo_group = self._get_repo_group(repo_group)
662 group_name = self._get_user_group(group_name)
665 group_name = self._get_user_group(group_name)
663
666
664 obj = self.sa.query(UserGroupRepoGroupToPerm)\
667 obj = self.sa.query(UserGroupRepoGroupToPerm)\
665 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
668 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
666 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
669 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
667 .scalar()
670 .scalar()
668 if obj:
671 if obj:
669 self.sa.delete(obj)
672 self.sa.delete(obj)
670 log.debug('Revoked perm to %s on %s', repo_group, group_name)
673 log.debug('Revoked perm to %s on %s', repo_group, group_name)
671 action_logger_generic(
674 action_logger_generic(
672 'revoked permission from usergroup: {} on repogroup: {}'.format(
675 'revoked permission from usergroup: {} on repogroup: {}'.format(
673 group_name, repo_group), namespace='security.repogroup')
676 group_name, repo_group), namespace='security.repogroup')
674
677
675 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
678 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
676 super_user_actions=False):
679 super_user_actions=False):
677
680
678 from pyramid.threadlocal import get_current_request
681 from pyramid.threadlocal import get_current_request
679 _render = get_current_request().get_partial_renderer(
682 _render = get_current_request().get_partial_renderer(
680 'data_table/_dt_elements.mako')
683 'data_table/_dt_elements.mako')
681 c = _render.get_call_context()
684 c = _render.get_call_context()
682 h = _render.get_helpers()
685 h = _render.get_helpers()
683
686
684 def quick_menu(repo_group_name):
687 def quick_menu(repo_group_name):
685 return _render('quick_repo_group_menu', repo_group_name)
688 return _render('quick_repo_group_menu', repo_group_name)
686
689
687 def repo_group_lnk(repo_group_name):
690 def repo_group_lnk(repo_group_name):
688 return _render('repo_group_name', repo_group_name)
691 return _render('repo_group_name', repo_group_name)
689
692
690 def last_change(last_change):
693 def last_change(last_change):
691 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
694 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
692 last_change = last_change + datetime.timedelta(seconds=
695 last_change = last_change + datetime.timedelta(seconds=
693 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
696 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
694 return _render("last_change", last_change)
697 return _render("last_change", last_change)
695
698
696 def desc(desc, personal):
699 def desc(desc, personal):
697 return _render(
700 return _render(
698 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
701 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
699
702
700 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
703 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
701 return _render(
704 return _render(
702 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
705 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
703
706
704 def repo_group_name(repo_group_name, children_groups):
707 def repo_group_name(repo_group_name, children_groups):
705 return _render("repo_group_name", repo_group_name, children_groups)
708 return _render("repo_group_name", repo_group_name, children_groups)
706
709
707 def user_profile(username):
710 def user_profile(username):
708 return _render('user_profile', username)
711 return _render('user_profile', username)
709
712
710 repo_group_data = []
713 repo_group_data = []
711 for group in repo_group_list:
714 for group in repo_group_list:
712
715
713 row = {
716 row = {
714 "menu": quick_menu(group.group_name),
717 "menu": quick_menu(group.group_name),
715 "name": repo_group_lnk(group.group_name),
718 "name": repo_group_lnk(group.group_name),
716 "name_raw": group.group_name,
719 "name_raw": group.group_name,
717 "last_change": last_change(group.last_db_change),
720 "last_change": last_change(group.last_db_change),
718 "last_change_raw": datetime_to_time(group.last_db_change),
721 "last_change_raw": datetime_to_time(group.last_db_change),
719 "desc": desc(group.description_safe, group.personal),
722 "desc": desc(group.description_safe, group.personal),
720 "top_level_repos": 0,
723 "top_level_repos": 0,
721 "owner": user_profile(group.user.username)
724 "owner": user_profile(group.user.username)
722 }
725 }
723 if admin:
726 if admin:
724 repo_count = group.repositories.count()
727 repo_count = group.repositories.count()
725 children_groups = map(
728 children_groups = map(
726 h.safe_unicode,
729 h.safe_unicode,
727 itertools.chain((g.name for g in group.parents),
730 itertools.chain((g.name for g in group.parents),
728 (x.name for x in [group])))
731 (x.name for x in [group])))
729 row.update({
732 row.update({
730 "action": repo_group_actions(
733 "action": repo_group_actions(
731 group.group_id, group.group_name, repo_count),
734 group.group_id, group.group_name, repo_count),
732 "top_level_repos": repo_count,
735 "top_level_repos": repo_count,
733 "name": repo_group_name(group.group_name, children_groups),
736 "name": repo_group_name(group.group_name, children_groups),
734
737
735 })
738 })
736 repo_group_data.append(row)
739 repo_group_data.append(row)
737
740
738 return repo_group_data
741 return repo_group_data
742
743 def _get_defaults(self, repo_group_name):
744 repo_group = RepoGroup.get_by_group_name(repo_group_name)
745
746 if repo_group is None:
747 return None
748
749 defaults = repo_group.get_dict()
750 defaults['repo_group_name'] = repo_group.name
751 defaults['repo_group_description'] = repo_group.group_description
752 defaults['repo_group_enable_locking'] = repo_group.enable_locking
753
754 # we use -1 as this is how in HTML, we mark an empty group
755 defaults['repo_group'] = defaults['group_parent_id'] or -1
756
757 # fill owner
758 if repo_group.user:
759 defaults.update({'user': repo_group.user.username})
760 else:
761 replacement_user = User.get_first_super_admin().username
762 defaults.update({'user': replacement_user})
763
764 return defaults
@@ -1,240 +1,284 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 import colander
22 import colander
23 import deform.widget
23
24
24 from rhodecode.translation import _
25 from rhodecode.translation import _
25 from rhodecode.model.validation_schema import validators, preparers, types
26 from rhodecode.model.validation_schema import validators, preparers, types
26
27
27
28
28 def get_group_and_repo(repo_name):
29 def get_group_and_repo(repo_name):
29 from rhodecode.model.repo_group import RepoGroupModel
30 from rhodecode.model.repo_group import RepoGroupModel
30 return RepoGroupModel()._get_group_name_and_parent(
31 return RepoGroupModel()._get_group_name_and_parent(
31 repo_name, get_object=True)
32 repo_name, get_object=True)
32
33
33
34
34 @colander.deferred
35 @colander.deferred
35 def deferred_can_write_to_group_validator(node, kw):
36 def deferred_can_write_to_group_validator(node, kw):
36 old_values = kw.get('old_values') or {}
37 old_values = kw.get('old_values') or {}
37 request_user = kw.get('user')
38 request_user = kw.get('user')
38
39
39 def can_write_group_validator(node, value):
40 def can_write_group_validator(node, value):
40 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
41 HasPermissionAny, HasRepoGroupPermissionAny)
42 HasPermissionAny, HasRepoGroupPermissionAny)
42 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
43
44
44 messages = {
45 messages = {
45 'invalid_parent_repo_group':
46 'invalid_parent_repo_group':
46 _(u"Parent repository group `{}` does not exist"),
47 _(u"Parent repository group `{}` does not exist"),
47 # permissions denied we expose as not existing, to prevent
48 # permissions denied we expose as not existing, to prevent
48 # resource discovery
49 # resource discovery
49 'permission_denied_parent_group':
50 'permission_denied_parent_group':
50 _(u"Parent repository group `{}` does not exist"),
51 _(u"Parent repository group `{}` does not exist"),
51 'permission_denied_root':
52 'permission_denied_root':
52 _(u"You do not have the permission to store "
53 _(u"You do not have the permission to store "
53 u"repository groups in the root location.")
54 u"repository groups in the root location.")
54 }
55 }
55
56
56 value = value['repo_group_name']
57 value = value['repo_group_name']
57 parent_group_name = value
58 parent_group_name = value
58
59
59 is_root_location = value is types.RootLocation
60 is_root_location = value is types.RootLocation
60
61
61 # NOT initialized validators, we must call them
62 # NOT initialized validators, we must call them
62 can_create_repo_groups_at_root = HasPermissionAny(
63 can_create_repo_groups_at_root = HasPermissionAny(
63 'hg.admin', 'hg.repogroup.create.true')
64 'hg.admin', 'hg.repogroup.create.true')
64
65
65 if is_root_location:
66 if is_root_location:
66 if can_create_repo_groups_at_root(user=request_user):
67 if can_create_repo_groups_at_root(user=request_user):
67 # we can create repo group inside tool-level. No more checks
68 # we can create repo group inside tool-level. No more checks
68 # are required
69 # are required
69 return
70 return
70 else:
71 else:
71 raise colander.Invalid(node, messages['permission_denied_root'])
72 raise colander.Invalid(node, messages['permission_denied_root'])
72
73
73 # check if the parent repo group actually exists
74 # check if the parent repo group actually exists
74 parent_group = None
75 parent_group = None
75 if parent_group_name:
76 if parent_group_name:
76 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
77 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
77 if value and not parent_group:
78 if value and not parent_group:
78 raise colander.Invalid(
79 raise colander.Invalid(
79 node, messages['invalid_parent_repo_group'].format(
80 node, messages['invalid_parent_repo_group'].format(
80 parent_group_name))
81 parent_group_name))
81
82
82 # check if we have permissions to create new groups under
83 # check if we have permissions to create new groups under
83 # parent repo group
84 # parent repo group
84 # create repositories with write permission on group is set to true
85 # create repositories with write permission on group is set to true
85 create_on_write = HasPermissionAny(
86 create_on_write = HasPermissionAny(
86 'hg.create.write_on_repogroup.true')(user=request_user)
87 'hg.create.write_on_repogroup.true')(user=request_user)
87
88
88 group_admin = HasRepoGroupPermissionAny('group.admin')(
89 group_admin = HasRepoGroupPermissionAny('group.admin')(
89 parent_group_name, 'can write into group validator', user=request_user)
90 parent_group_name, 'can write into group validator', user=request_user)
90 group_write = HasRepoGroupPermissionAny('group.write')(
91 group_write = HasRepoGroupPermissionAny('group.write')(
91 parent_group_name, 'can write into group validator', user=request_user)
92 parent_group_name, 'can write into group validator', user=request_user)
92
93
93 # creation by write access is currently disabled. Needs thinking if
94 # creation by write access is currently disabled. Needs thinking if
94 # we want to allow this...
95 # we want to allow this...
95 forbidden = not (group_admin or (group_write and create_on_write and 0))
96 forbidden = not (group_admin or (group_write and create_on_write and 0))
96
97
97 if parent_group and forbidden:
98 if parent_group and forbidden:
98 msg = messages['permission_denied_parent_group'].format(
99 msg = messages['permission_denied_parent_group'].format(
99 parent_group_name)
100 parent_group_name)
100 raise colander.Invalid(node, msg)
101 raise colander.Invalid(node, msg)
101
102
102 return can_write_group_validator
103 return can_write_group_validator
103
104
104
105
105 @colander.deferred
106 @colander.deferred
106 def deferred_repo_group_owner_validator(node, kw):
107 def deferred_repo_group_owner_validator(node, kw):
107
108
108 def repo_owner_validator(node, value):
109 def repo_owner_validator(node, value):
109 from rhodecode.model.db import User
110 from rhodecode.model.db import User
110 existing = User.get_by_username(value)
111 existing = User.get_by_username(value)
111 if not existing:
112 if not existing:
112 msg = _(u'Repo group owner with id `{}` does not exists').format(
113 msg = _(u'Repo group owner with id `{}` does not exists').format(
113 value)
114 value)
114 raise colander.Invalid(node, msg)
115 raise colander.Invalid(node, msg)
115
116
116 return repo_owner_validator
117 return repo_owner_validator
117
118
118
119
119 @colander.deferred
120 @colander.deferred
120 def deferred_unique_name_validator(node, kw):
121 def deferred_unique_name_validator(node, kw):
121 request_user = kw.get('user')
122 request_user = kw.get('user')
122 old_values = kw.get('old_values') or {}
123 old_values = kw.get('old_values') or {}
123
124
124 def unique_name_validator(node, value):
125 def unique_name_validator(node, value):
125 from rhodecode.model.db import Repository, RepoGroup
126 from rhodecode.model.db import Repository, RepoGroup
126 name_changed = value != old_values.get('group_name')
127 name_changed = value != old_values.get('group_name')
127
128
128 existing = Repository.get_by_repo_name(value)
129 existing = Repository.get_by_repo_name(value)
129 if name_changed and existing:
130 if name_changed and existing:
130 msg = _(u'Repository with name `{}` already exists').format(value)
131 msg = _(u'Repository with name `{}` already exists').format(value)
131 raise colander.Invalid(node, msg)
132 raise colander.Invalid(node, msg)
132
133
133 existing_group = RepoGroup.get_by_group_name(value)
134 existing_group = RepoGroup.get_by_group_name(value)
134 if name_changed and existing_group:
135 if name_changed and existing_group:
135 msg = _(u'Repository group with name `{}` already exists').format(
136 msg = _(u'Repository group with name `{}` already exists').format(
136 value)
137 value)
137 raise colander.Invalid(node, msg)
138 raise colander.Invalid(node, msg)
138 return unique_name_validator
139 return unique_name_validator
139
140
140
141
141 @colander.deferred
142 @colander.deferred
142 def deferred_repo_group_name_validator(node, kw):
143 def deferred_repo_group_name_validator(node, kw):
143 return validators.valid_name_validator
144 return validators.valid_name_validator
144
145
145
146
147 @colander.deferred
148 def deferred_repo_group_validator(node, kw):
149 options = kw.get(
150 'repo_group_repo_group_options')
151 return colander.OneOf([x for x in options])
152
153
154 @colander.deferred
155 def deferred_repo_group_widget(node, kw):
156 items = kw.get('repo_group_repo_group_items')
157 return deform.widget.Select2Widget(values=items)
158
159
146 class GroupType(colander.Mapping):
160 class GroupType(colander.Mapping):
147 def _validate(self, node, value):
161 def _validate(self, node, value):
148 try:
162 try:
149 return dict(repo_group_name=value)
163 return dict(repo_group_name=value)
150 except Exception as e:
164 except Exception as e:
151 raise colander.Invalid(
165 raise colander.Invalid(
152 node, '"${val}" is not a mapping type: ${err}'.format(
166 node, '"${val}" is not a mapping type: ${err}'.format(
153 val=value, err=e))
167 val=value, err=e))
154
168
155 def deserialize(self, node, cstruct):
169 def deserialize(self, node, cstruct):
156 if cstruct is colander.null:
170 if cstruct is colander.null:
157 return cstruct
171 return cstruct
158
172
159 appstruct = super(GroupType, self).deserialize(node, cstruct)
173 appstruct = super(GroupType, self).deserialize(node, cstruct)
160 validated_name = appstruct['repo_group_name']
174 validated_name = appstruct['repo_group_name']
161
175
162 # inject group based on once deserialized data
176 # inject group based on once deserialized data
163 (repo_group_name_without_group,
177 (repo_group_name_without_group,
164 parent_group_name,
178 parent_group_name,
165 parent_group) = get_group_and_repo(validated_name)
179 parent_group) = get_group_and_repo(validated_name)
166
180
167 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
181 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
168 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
182 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
169 if parent_group:
183 if parent_group:
170 appstruct['repo_group_id'] = parent_group.group_id
184 appstruct['repo_group_id'] = parent_group.group_id
171
185
172 return appstruct
186 return appstruct
173
187
174
188
175 class GroupSchema(colander.SchemaNode):
189 class GroupSchema(colander.SchemaNode):
176 schema_type = GroupType
190 schema_type = GroupType
177 validator = deferred_can_write_to_group_validator
191 validator = deferred_can_write_to_group_validator
178 missing = colander.null
192 missing = colander.null
179
193
180
194
181 class RepoGroup(GroupSchema):
195 class RepoGroup(GroupSchema):
182 repo_group_name = colander.SchemaNode(
196 repo_group_name = colander.SchemaNode(
183 types.GroupNameType())
197 types.GroupNameType())
184 repo_group_id = colander.SchemaNode(
198 repo_group_id = colander.SchemaNode(
185 colander.String(), missing=None)
199 colander.String(), missing=None)
186 repo_group_name_without_group = colander.SchemaNode(
200 repo_group_name_without_group = colander.SchemaNode(
187 colander.String(), missing=None)
201 colander.String(), missing=None)
188
202
189
203
190 class RepoGroupAccessSchema(colander.MappingSchema):
204 class RepoGroupAccessSchema(colander.MappingSchema):
191 repo_group = RepoGroup()
205 repo_group = RepoGroup()
192
206
193
207
194 class RepoGroupNameUniqueSchema(colander.MappingSchema):
208 class RepoGroupNameUniqueSchema(colander.MappingSchema):
195 unique_repo_group_name = colander.SchemaNode(
209 unique_repo_group_name = colander.SchemaNode(
196 colander.String(),
210 colander.String(),
197 validator=deferred_unique_name_validator)
211 validator=deferred_unique_name_validator)
198
212
199
213
200 class RepoGroupSchema(colander.Schema):
214 class RepoGroupSchema(colander.Schema):
201
215
202 repo_group_name = colander.SchemaNode(
216 repo_group_name = colander.SchemaNode(
203 types.GroupNameType(),
217 types.GroupNameType(),
204 validator=deferred_repo_group_name_validator)
218 validator=deferred_repo_group_name_validator)
205
219
206 repo_group_owner = colander.SchemaNode(
220 repo_group_owner = colander.SchemaNode(
207 colander.String(),
221 colander.String(),
208 validator=deferred_repo_group_owner_validator)
222 validator=deferred_repo_group_owner_validator)
209
223
210 repo_group_description = colander.SchemaNode(
224 repo_group_description = colander.SchemaNode(
211 colander.String(), missing='')
225 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
212
226
213 repo_group_copy_permissions = colander.SchemaNode(
227 repo_group_copy_permissions = colander.SchemaNode(
214 types.StringBooleanType(),
228 types.StringBooleanType(),
215 missing=False)
229 missing=False, widget=deform.widget.CheckboxWidget())
216
230
217 repo_group_enable_locking = colander.SchemaNode(
231 repo_group_enable_locking = colander.SchemaNode(
218 types.StringBooleanType(),
232 types.StringBooleanType(),
219 missing=False)
233 missing=False, widget=deform.widget.CheckboxWidget())
220
234
221 def deserialize(self, cstruct):
235 def deserialize(self, cstruct):
222 """
236 """
223 Custom deserialize that allows to chain validation, and verify
237 Custom deserialize that allows to chain validation, and verify
224 permissions, and as last step uniqueness
238 permissions, and as last step uniqueness
225 """
239 """
226
240
227 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
241 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
228 validated_name = appstruct['repo_group_name']
242 validated_name = appstruct['repo_group_name']
229
243
230 # second pass to validate permissions to repo_group
244 # second pass to validate permissions to repo_group
231 second = RepoGroupAccessSchema().bind(**self.bindings)
245 second = RepoGroupAccessSchema().bind(**self.bindings)
232 appstruct_second = second.deserialize({'repo_group': validated_name})
246 appstruct_second = second.deserialize({'repo_group': validated_name})
233 # save result
247 # save result
234 appstruct['repo_group'] = appstruct_second['repo_group']
248 appstruct['repo_group'] = appstruct_second['repo_group']
235
249
236 # thirds to validate uniqueness
250 # thirds to validate uniqueness
237 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
251 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
238 third.deserialize({'unique_repo_group_name': validated_name})
252 third.deserialize({'unique_repo_group_name': validated_name})
239
253
240 return appstruct
254 return appstruct
255
256
257 class RepoGroupSettingsSchema(RepoGroupSchema):
258 repo_group = colander.SchemaNode(
259 colander.Integer(),
260 validator=deferred_repo_group_validator,
261 widget=deferred_repo_group_widget,
262 missing='')
263
264 def deserialize(self, cstruct):
265 """
266 Custom deserialize that allows to chain validation, and verify
267 permissions, and as last step uniqueness
268 """
269
270 # first pass, to validate given data
271 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
272 validated_name = appstruct['repo_group_name']
273
274 # second pass to validate permissions to repo_group
275 second = RepoGroupAccessSchema().bind(**self.bindings)
276 appstruct_second = second.deserialize({'repo_group': validated_name})
277 # save result
278 appstruct['repo_group'] = appstruct_second['repo_group']
279
280 # thirds to validate uniqueness
281 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
282 third.deserialize({'unique_repo_group_name': validated_name})
283
284 return appstruct
@@ -1,285 +1,293 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
52 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
53 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
53 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
54 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
54 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
55 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
55 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
56 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
56 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
57 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
57 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
58 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
58 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
59 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
59 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
60 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
60 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
61 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
61 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
62 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
62 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
63 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
63 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
64 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
64 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
65 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
65 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
66 pyroutes.register('users', '/_admin/users', []);
66 pyroutes.register('users', '/_admin/users', []);
67 pyroutes.register('users_data', '/_admin/users_data', []);
67 pyroutes.register('users_data', '/_admin/users_data', []);
68 pyroutes.register('users_create', '/_admin/users/create', []);
68 pyroutes.register('users_create', '/_admin/users/create', []);
69 pyroutes.register('users_new', '/_admin/users/new', []);
69 pyroutes.register('users_new', '/_admin/users/new', []);
70 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
70 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
71 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
71 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
72 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
72 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
73 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
73 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
74 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
74 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
75 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
75 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
76 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
76 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
77 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
77 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
78 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
78 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
79 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
80 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
81 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
81 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
82 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
82 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
83 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
83 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
84 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
84 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
85 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
85 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
86 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
86 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
87 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
87 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
88 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
88 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
89 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
89 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
90 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
90 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
91 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
91 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
92 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
92 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
93 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
93 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
94 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
94 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
95 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
95 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
96 pyroutes.register('user_groups', '/_admin/user_groups', []);
96 pyroutes.register('user_groups', '/_admin/user_groups', []);
97 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
97 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
98 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
98 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
99 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
99 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
100 pyroutes.register('repos', '/_admin/repos', []);
100 pyroutes.register('repos', '/_admin/repos', []);
101 pyroutes.register('repo_new', '/_admin/repos/new', []);
101 pyroutes.register('repo_new', '/_admin/repos/new', []);
102 pyroutes.register('repo_create', '/_admin/repos/create', []);
102 pyroutes.register('repo_create', '/_admin/repos/create', []);
103 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
104 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
105 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
103 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
106 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
104 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
107 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
105 pyroutes.register('channelstream_proxy', '/_channelstream', []);
108 pyroutes.register('channelstream_proxy', '/_channelstream', []);
106 pyroutes.register('login', '/_admin/login', []);
109 pyroutes.register('login', '/_admin/login', []);
107 pyroutes.register('logout', '/_admin/logout', []);
110 pyroutes.register('logout', '/_admin/logout', []);
108 pyroutes.register('register', '/_admin/register', []);
111 pyroutes.register('register', '/_admin/register', []);
109 pyroutes.register('reset_password', '/_admin/password_reset', []);
112 pyroutes.register('reset_password', '/_admin/password_reset', []);
110 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
113 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
111 pyroutes.register('home', '/', []);
114 pyroutes.register('home', '/', []);
112 pyroutes.register('user_autocomplete_data', '/_users', []);
115 pyroutes.register('user_autocomplete_data', '/_users', []);
113 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
116 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
114 pyroutes.register('repo_list_data', '/_repos', []);
117 pyroutes.register('repo_list_data', '/_repos', []);
115 pyroutes.register('goto_switcher_data', '/_goto_data', []);
118 pyroutes.register('goto_switcher_data', '/_goto_data', []);
116 pyroutes.register('journal', '/_admin/journal', []);
119 pyroutes.register('journal', '/_admin/journal', []);
117 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
120 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
118 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
121 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
119 pyroutes.register('journal_public', '/_admin/public_journal', []);
122 pyroutes.register('journal_public', '/_admin/public_journal', []);
120 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
123 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
121 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
124 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
122 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
125 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
123 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
126 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
124 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
127 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
125 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
128 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
126 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
129 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
127 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
130 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
128 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
131 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
129 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
132 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
130 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
133 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
131 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
134 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
132 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
135 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
133 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
136 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
134 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
137 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
135 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
136 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
139 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
137 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
140 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
141 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
139 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
142 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
140 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
143 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
141 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
144 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
142 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
145 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
143 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
147 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
145 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
148 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
146 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
151 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
152 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
153 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
151 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
154 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
152 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
153 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
156 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
154 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
157 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
158 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
156 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
159 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
157 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
160 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
158 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
161 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
159 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
162 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
160 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
163 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
161 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
164 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
162 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
165 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
163 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
166 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
164 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
167 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
165 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
166 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
169 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
167 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
170 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
168 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
171 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
169 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
172 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
170 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
173 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
171 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
174 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
172 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
175 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
173 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
176 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
174 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
177 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
175 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
178 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
176 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
179 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
177 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
180 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
178 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
181 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
179 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
182 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
180 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
183 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
181 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
184 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
182 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
185 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
183 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
186 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
184 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
187 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
185 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
188 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
186 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
189 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
187 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
190 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
188 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
191 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
189 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
192 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
190 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
193 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
191 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
194 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
192 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
195 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
193 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
196 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
194 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
197 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
195 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
198 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
196 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
199 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
197 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
200 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
198 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
201 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
199 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
202 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
200 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
203 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
201 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
204 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
202 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
205 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
203 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
206 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
204 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
207 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
205 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
208 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
206 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
209 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
207 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
210 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
208 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
211 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
209 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
212 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
210 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
213 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
211 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
214 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
212 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
215 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
213 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
216 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
214 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
217 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
215 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
218 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
216 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
219 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
217 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
220 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
218 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
221 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
219 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
222 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
220 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
223 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
221 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
224 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
222 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
225 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
223 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
226 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
224 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
227 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
225 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
228 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
229 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
230 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
231 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
232 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
233 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
226 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
234 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
227 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
235 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
228 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
236 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
229 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
237 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
230 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
238 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
231 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
239 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
232 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
240 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
233 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
241 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
234 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
242 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
235 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
243 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
236 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
244 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
237 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
245 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
238 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
246 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
239 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
247 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
240 pyroutes.register('search', '/_admin/search', []);
248 pyroutes.register('search', '/_admin/search', []);
241 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
249 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
242 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
250 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
243 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
251 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
244 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
252 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
245 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
253 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
246 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
254 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
247 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
255 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
248 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
256 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
249 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
257 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
250 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
258 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
251 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
259 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
252 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
260 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
253 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
261 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
254 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
262 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
255 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
263 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
256 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
264 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
257 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
265 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
258 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
266 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
259 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
267 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
260 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
268 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
261 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
269 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
262 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
270 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
263 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
271 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
264 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
272 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
265 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
273 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
266 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
274 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
267 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
275 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
268 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
276 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
269 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
277 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
270 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
278 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
271 pyroutes.register('gists_show', '/_admin/gists', []);
279 pyroutes.register('gists_show', '/_admin/gists', []);
272 pyroutes.register('gists_new', '/_admin/gists/new', []);
280 pyroutes.register('gists_new', '/_admin/gists/new', []);
273 pyroutes.register('gists_create', '/_admin/gists/create', []);
281 pyroutes.register('gists_create', '/_admin/gists/create', []);
274 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
282 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
275 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
283 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
276 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
284 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
277 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
285 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
278 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
286 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
279 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
287 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
280 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
288 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
281 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
289 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
282 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
290 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
283 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
291 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
284 pyroutes.register('apiv2', '/_admin/api', []);
292 pyroutes.register('apiv2', '/_admin/api', []);
285 }
293 }
@@ -1,69 +1,69 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 &raquo;
7 &raquo;
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.current_IntegrationType.display_name,
10 ${h.link_to(c.current_IntegrationType.display_name,
11 request.route_url(route_name='repo_integrations_list',
11 request.route_url(route_name='repo_integrations_list',
12 repo_name=c.repo.repo_name,
12 repo_name=c.repo.repo_name,
13 integration=c.current_IntegrationType.key))}
13 integration=c.current_IntegrationType.key))}
14 %elif c.repo_group:
14 %elif c.repo_group:
15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
17 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
18 &raquo;
18 &raquo;
19 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
19 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
22 &raquo;
22 &raquo;
23 ${h.link_to(c.current_IntegrationType.display_name,
23 ${h.link_to(c.current_IntegrationType.display_name,
24 request.route_url(route_name='repo_group_integrations_list',
24 request.route_url(route_name='repo_group_integrations_list',
25 repo_group_name=c.repo_group.group_name,
25 repo_group_name=c.repo_group.group_name,
26 integration=c.current_IntegrationType.key))}
26 integration=c.current_IntegrationType.key))}
27 %else:
27 %else:
28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 &raquo;
29 &raquo;
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 &raquo;
31 &raquo;
32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
33 &raquo;
33 &raquo;
34 ${h.link_to(c.current_IntegrationType.display_name,
34 ${h.link_to(c.current_IntegrationType.display_name,
35 request.route_url(route_name='global_integrations_list',
35 request.route_url(route_name='global_integrations_list',
36 integration=c.current_IntegrationType.key))}
36 integration=c.current_IntegrationType.key))}
37 %endif
37 %endif
38
38
39 %if c.integration:
39 %if c.integration:
40 &raquo;
40 &raquo;
41 ${c.integration.name}
41 ${c.integration.name}
42 %elif c.current_IntegrationType:
42 %elif c.current_IntegrationType:
43 &raquo;
43 &raquo;
44 ${c.current_IntegrationType.display_name}
44 ${c.current_IntegrationType.display_name}
45 %endif
45 %endif
46 </%def>
46 </%def>
47
47
48 <style>
48 <style>
49 .control-inputs.item-options, .control-inputs.item-settings {
49 .control-inputs.item-options, .control-inputs.item-settings {
50 float: left;
50 float: left;
51 width: 100%;
51 width: 100%;
52 }
52 }
53 </style>
53 </style>
54 <div class="panel panel-default">
54 <div class="panel panel-default">
55 <div class="panel-heading">
55 <div class="panel-heading">
56 <h2 class="panel-title">
56 <h2 class="panel-title">
57 %if c.integration:
57 %if c.integration:
58 ${c.current_IntegrationType.display_name} - ${c.integration.name}
58 ${c.current_IntegrationType.display_name} - ${c.integration.name}
59 %else:
59 %else:
60 ${_('Create New %(integration_type)s Integration') % {
60 ${_('Create New %(integration_type)s Integration') % {
61 'integration_type': c.current_IntegrationType.display_name
61 'integration_type': c.current_IntegrationType.display_name
62 }}
62 }}
63 %endif
63 %endif
64 </h2>
64 </h2>
65 </div>
65 </div>
66 <div class="panel-body">
66 <div class="panel-body">
67 ${c.form.render() | n}
67 ${c.form.render() | n}
68 </div>
68 </div>
69 </div>
69 </div>
@@ -1,256 +1,256 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 %endif
17 %endif
18 %if c.current_IntegrationType:
18 %if c.current_IntegrationType:
19 &raquo;
19 &raquo;
20 %if c.repo:
20 %if c.repo:
21 ${h.link_to(_('Integrations'),
21 ${h.link_to(_('Integrations'),
22 request.route_path(route_name='repo_integrations_home',
22 request.route_path(route_name='repo_integrations_home',
23 repo_name=c.repo.repo_name))}
23 repo_name=c.repo.repo_name))}
24 %elif c.repo_group:
24 %elif c.repo_group:
25 ${h.link_to(_('Integrations'),
25 ${h.link_to(_('Integrations'),
26 request.route_path(route_name='repo_group_integrations_home',
26 request.route_path(route_name='repo_group_integrations_home',
27 repo_group_name=c.repo_group.group_name))}
27 repo_group_name=c.repo_group.group_name))}
28 %else:
28 %else:
29 ${h.link_to(_('Integrations'),
29 ${h.link_to(_('Integrations'),
30 request.route_path(route_name='global_integrations_home'))}
30 request.route_path(route_name='global_integrations_home'))}
31 %endif
31 %endif
32 &raquo;
32 &raquo;
33 ${c.current_IntegrationType.display_name}
33 ${c.current_IntegrationType.display_name}
34 %else:
34 %else:
35 &raquo;
35 &raquo;
36 ${_('Integrations')}
36 ${_('Integrations')}
37 %endif
37 %endif
38 </%def>
38 </%def>
39
39
40 <div class="panel panel-default">
40 <div class="panel panel-default">
41 <div class="panel-heading">
41 <div class="panel-heading">
42 <h3 class="panel-title">
42 <h3 class="panel-title">
43 %if c.repo:
43 %if c.repo:
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 %elif c.repo_group:
45 %elif c.repo_group:
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 %else:
47 %else:
48 ${_('Current Integrations')}
48 ${_('Current Integrations')}
49 %endif
49 %endif
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="panel-body">
52 <div class="panel-body">
53 <%
53 <%
54 if c.repo:
54 if c.repo:
55 home_url = request.route_path('repo_integrations_home',
55 home_url = request.route_path('repo_integrations_home',
56 repo_name=c.repo.repo_name)
56 repo_name=c.repo.repo_name)
57 elif c.repo_group:
57 elif c.repo_group:
58 home_url = request.route_path('repo_group_integrations_home',
58 home_url = request.route_path('repo_group_integrations_home',
59 repo_group_name=c.repo_group.group_name)
59 repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 home_url = request.route_path('global_integrations_home')
61 home_url = request.route_path('global_integrations_home')
62 %>
62 %>
63
63
64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65
65
66 %for integration_key, IntegrationType in c.available_integrations.items():
66 %for integration_key, IntegrationType in c.available_integrations.items():
67 % if not IntegrationType.is_dummy:
67 % if not IntegrationType.is_dummy:
68 <%
68 <%
69 if c.repo:
69 if c.repo:
70 list_url = request.route_path('repo_integrations_list',
70 list_url = request.route_path('repo_integrations_list',
71 repo_name=c.repo.repo_name,
71 repo_name=c.repo.repo_name,
72 integration=integration_key)
72 integration=integration_key)
73 elif c.repo_group:
73 elif c.repo_group:
74 list_url = request.route_path('repo_group_integrations_list',
74 list_url = request.route_path('repo_group_integrations_list',
75 repo_group_name=c.repo_group.group_name,
75 repo_group_name=c.repo_group.group_name,
76 integration=integration_key)
76 integration=integration_key)
77 else:
77 else:
78 list_url = request.route_path('global_integrations_list',
78 list_url = request.route_path('global_integrations_list',
79 integration=integration_key)
79 integration=integration_key)
80 %>
80 %>
81 <a href="${list_url}"
81 <a href="${list_url}"
82 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
82 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
83 ${IntegrationType.display_name}
83 ${IntegrationType.display_name}
84 </a>
84 </a>
85 % endif
85 % endif
86 %endfor
86 %endfor
87
87
88 <%
88 <%
89 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
89 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
90
90
91 if c.repo:
91 if c.repo:
92 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
92 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
93 elif c.repo_group:
93 elif c.repo_group:
94 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
94 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
95 else:
95 else:
96 create_url = h.route_path('global_integrations_new')
96 create_url = h.route_path('global_integrations_new')
97 %>
97 %>
98 <p class="pull-right">
98 <p class="pull-right">
99 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
99 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
100 </p>
100 </p>
101
101
102 <table class="rctable integrations">
102 <table class="rctable integrations">
103 <thead>
103 <thead>
104 <tr>
104 <tr>
105 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
105 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
106 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
106 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
107 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
107 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
108 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
108 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
109 <th>${_('Actions')}</th>
109 <th>${_('Actions')}</th>
110 <th></th>
110 <th></th>
111 </tr>
111 </tr>
112 </thead>
112 </thead>
113 <tbody>
113 <tbody>
114 %if not c.integrations_list:
114 %if not c.integrations_list:
115 <tr>
115 <tr>
116 <td colspan="7">
116 <td colspan="7">
117
117
118 %if c.repo:
118 %if c.repo:
119 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
119 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
120 %elif c.repo_group:
120 %elif c.repo_group:
121 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
121 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
122 %else:
122 %else:
123 ${_('No {type} integrations exist yet.').format(type=integration_type)}
123 ${_('No {type} integrations exist yet.').format(type=integration_type)}
124 %endif
124 %endif
125
125
126 %if c.current_IntegrationType:
126 %if c.current_IntegrationType:
127 <%
127 <%
128 if c.repo:
128 if c.repo:
129 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
129 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
130 elif c.repo_group:
130 elif c.repo_group:
131 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
131 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
132 else:
132 else:
133 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
133 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
134 %>
134 %>
135 %endif
135 %endif
136
136
137 <a href="${create_url}">${_(u'Create one')}</a>
137 <a href="${create_url}">${_(u'Create one')}</a>
138 </td>
138 </td>
139 </tr>
139 </tr>
140 %endif
140 %endif
141 %for IntegrationType, integration in c.integrations_list:
141 %for IntegrationType, integration in c.integrations_list:
142 <tr id="integration_${integration.integration_id}">
142 <tr id="integration_${integration.integration_id}">
143 <td class="td-enabled">
143 <td class="td-enabled">
144 %if integration.enabled:
144 %if integration.enabled:
145 <div class="flag_status approved pull-left"></div>
145 <div class="flag_status approved pull-left"></div>
146 %else:
146 %else:
147 <div class="flag_status rejected pull-left"></div>
147 <div class="flag_status rejected pull-left"></div>
148 %endif
148 %endif
149 </td>
149 </td>
150 <td class="td-description">
150 <td class="td-description">
151 ${integration.name}
151 ${integration.name}
152 </td>
152 </td>
153 <td class="td-icon">
153 <td class="td-icon">
154 %if integration.integration_type in c.available_integrations:
154 %if integration.integration_type in c.available_integrations:
155 <div class="integration-icon">
155 <div class="integration-icon">
156 ${c.available_integrations[integration.integration_type].icon|n}
156 ${c.available_integrations[integration.integration_type].icon|n}
157 </div>
157 </div>
158 %else:
158 %else:
159 ?
159 ?
160 %endif
160 %endif
161 </td>
161 </td>
162 <td class="td-type">
162 <td class="td-type">
163 ${integration.integration_type}
163 ${integration.integration_type}
164 </td>
164 </td>
165 <td class="td-scope">
165 <td class="td-scope">
166 %if integration.repo:
166 %if integration.repo:
167 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
167 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
168 ${_('repo')}:${integration.repo.repo_name}
168 ${_('repo')}:${integration.repo.repo_name}
169 </a>
169 </a>
170 %elif integration.repo_group:
170 %elif integration.repo_group:
171 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
171 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
172 ${_('repogroup')}:${integration.repo_group.group_name}
172 ${_('repogroup')}:${integration.repo_group.group_name}
173 %if integration.child_repos_only:
173 %if integration.child_repos_only:
174 ${_('child repos only')}
174 ${_('child repos only')}
175 %else:
175 %else:
176 ${_('cascade to all')}
176 ${_('cascade to all')}
177 %endif
177 %endif
178 </a>
178 </a>
179 %else:
179 %else:
180 %if integration.child_repos_only:
180 %if integration.child_repos_only:
181 ${_('top level repos only')}
181 ${_('top level repos only')}
182 %else:
182 %else:
183 ${_('global')}
183 ${_('global')}
184 %endif
184 %endif
185 </td>
185 </td>
186 %endif
186 %endif
187 <td class="td-action">
187 <td class="td-action">
188 %if not IntegrationType:
188 %if not IntegrationType:
189 ${_('unknown integration')}
189 ${_('unknown integration')}
190 %else:
190 %else:
191 <%
191 <%
192 if c.repo:
192 if c.repo:
193 edit_url = request.route_path('repo_integrations_edit',
193 edit_url = request.route_path('repo_integrations_edit',
194 repo_name=c.repo.repo_name,
194 repo_name=c.repo.repo_name,
195 integration=integration.integration_type,
195 integration=integration.integration_type,
196 integration_id=integration.integration_id)
196 integration_id=integration.integration_id)
197 elif c.repo_group:
197 elif c.repo_group:
198 edit_url = request.route_path('repo_group_integrations_edit',
198 edit_url = request.route_path('repo_group_integrations_edit',
199 repo_group_name=c.repo_group.group_name,
199 repo_group_name=c.repo_group.group_name,
200 integration=integration.integration_type,
200 integration=integration.integration_type,
201 integration_id=integration.integration_id)
201 integration_id=integration.integration_id)
202 else:
202 else:
203 edit_url = request.route_path('global_integrations_edit',
203 edit_url = request.route_path('global_integrations_edit',
204 integration=integration.integration_type,
204 integration=integration.integration_type,
205 integration_id=integration.integration_id)
205 integration_id=integration.integration_id)
206 %>
206 %>
207 <div class="grid_edit">
207 <div class="grid_edit">
208 <a href="${edit_url}">${_('Edit')}</a>
208 <a href="${edit_url}">${_('Edit')}</a>
209 </div>
209 </div>
210 <div class="grid_delete">
210 <div class="grid_delete">
211 <a href="${edit_url}"
211 <a href="${edit_url}"
212 class="btn btn-link btn-danger delete_integration_entry"
212 class="btn btn-link btn-danger delete_integration_entry"
213 data-desc="${integration.name}"
213 data-desc="${integration.name}"
214 data-uid="${integration.integration_id}">
214 data-uid="${integration.integration_id}">
215 ${_('Delete')}
215 ${_('Delete')}
216 </a>
216 </a>
217 </div>
217 </div>
218 %endif
218 %endif
219 </td>
219 </td>
220 </tr>
220 </tr>
221 %endfor
221 %endfor
222 <tr id="last-row"></tr>
222 <tr id="last-row"></tr>
223 </tbody>
223 </tbody>
224 </table>
224 </table>
225 <div class="integrations-paginator">
225 <div class="integrations-paginator">
226 <div class="pagination-wh pagination-left">
226 <div class="pagination-wh pagination-left">
227 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
227 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
228 </div>
228 </div>
229 </div>
229 </div>
230 </div>
230 </div>
231 </div>
231 </div>
232 <script type="text/javascript">
232 <script type="text/javascript">
233 var delete_integration = function(entry) {
233 var delete_integration = function(entry) {
234 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
234 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
235 var request = $.ajax({
235 var request = $.ajax({
236 type: "POST",
236 type: "POST",
237 url: $(entry).attr('href'),
237 url: $(entry).attr('href'),
238 data: {
238 data: {
239 'delete': 'delete',
239 'delete': 'delete',
240 'csrf_token': CSRF_TOKEN
240 'csrf_token': CSRF_TOKEN
241 },
241 },
242 success: function(){
242 success: function(){
243 location.reload();
243 location.reload();
244 },
244 },
245 error: function(data, textStatus, errorThrown){
245 error: function(data, textStatus, errorThrown){
246 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
246 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
247 }
247 }
248 });
248 });
249 };
249 };
250 };
250 };
251
251
252 $('.delete_integration_entry').on('click', function(e){
252 $('.delete_integration_entry').on('click', function(e){
253 e.preventDefault();
253 e.preventDefault();
254 delete_integration(this);
254 delete_integration(this);
255 });
255 });
256 </script> No newline at end of file
256 </script>
@@ -1,68 +1,68 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 %if c.repo:
6 %if c.repo:
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 %elif c.repo_group:
10 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
13 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
15 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 %else:
18 %else:
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 &raquo;
22 &raquo;
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 %endif
24 %endif
25 &raquo;
25 &raquo;
26 ${_('Create new integration')}
26 ${_('Create new integration')}
27 </%def>
27 </%def>
28 <%widgets:panel class_='integrations'>
28 <%widgets:panel class_='integrations'>
29 <%def name="title()">
29 <%def name="title()">
30 %if c.repo:
30 %if c.repo:
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 %elif c.repo_group:
32 %elif c.repo_group:
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 %else:
34 %else:
35 ${_('Create New Global Integration')}
35 ${_('Create New Global Integration')}
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 %for integration, IntegrationObject in c.available_integrations.items():
39 %for integration, IntegrationObject in c.available_integrations.items():
40 <%
40 <%
41 if c.repo:
41 if c.repo:
42 create_url = request.route_path('repo_integrations_create',
42 create_url = request.route_path('repo_integrations_create',
43 repo_name=c.repo.repo_name,
43 repo_name=c.repo.repo_name,
44 integration=integration)
44 integration=integration)
45 elif c.repo_group:
45 elif c.repo_group:
46 create_url = request.route_path('repo_group_integrations_create',
46 create_url = request.route_path('repo_group_integrations_create',
47 repo_group_name=c.repo_group.group_name,
47 repo_group_name=c.repo_group.group_name,
48 integration=integration)
48 integration=integration)
49 else:
49 else:
50 create_url = request.route_path('global_integrations_create',
50 create_url = request.route_path('global_integrations_create',
51 integration=integration)
51 integration=integration)
52 if IntegrationObject.is_dummy:
52 if IntegrationObject.is_dummy:
53 create_url = request.current_route_path()
53 create_url = request.current_route_path()
54 %>
54 %>
55 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
55 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
56 <%widgets:panel>
56 <%widgets:panel>
57 <h2>
57 <h2>
58 <div class="integration-icon">
58 <div class="integration-icon">
59 ${IntegrationObject.icon|n}
59 ${IntegrationObject.icon|n}
60 </div>
60 </div>
61 ${IntegrationObject.display_name}
61 ${IntegrationObject.display_name}
62 </h2>
62 </h2>
63 ${IntegrationObject.description or _('No description available')}
63 ${IntegrationObject.description or _('No description available')}
64 </%widgets:panel>
64 </%widgets:panel>
65 </a>
65 </a>
66 %endfor
66 %endfor
67 <div style="clear:both"></div>
67 <div style="clear:both"></div>
68 </%widgets:panel>
68 </%widgets:panel>
@@ -1,106 +1,106 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 ${_('Add repository group')}
5 ${_('Add repository group')}
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 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository groups'),h.route_path('repo_groups'))}
15 &raquo;
15 &raquo;
16 ${_('Add Repository Group')}
16 ${_('Add Repository Group')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <!-- box / title -->
25 <!-- box / title -->
26 <div class="title">
26 <div class="title">
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 </div>
28 </div>
29 <!-- end box / title -->
29 <!-- end box / title -->
30 ${h.secure_form(h.url('repo_groups'), request=request)}
30 ${h.secure_form(h.route_path('repo_group_create'), request=request)}
31 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33 <div class="fields">
33 <div class="fields">
34 <div class="field">
34 <div class="field">
35 <div class="label">
35 <div class="label">
36 <label for="group_name">${_('Group Name')}:</label>
36 <label for="group_name">${_('Group Name')}:</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 ${h.text('group_name', class_="medium")}
39 ${h.text('group_name', class_="medium")}
40 </div>
40 </div>
41 </div>
41 </div>
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="group_description">${_('Description')}:</label>
45 <label for="group_description">${_('Description')}:</label>
46 </div>
46 </div>
47 <div class="textarea editor">
47 <div class="textarea editor">
48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
49 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
49 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
50 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
50 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
51 <span id="meta-tags-desc" style="display: none">
51 <span id="meta-tags-desc" style="display: none">
52 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
52 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
53 ${dt.metatags_help()}
53 ${dt.metatags_help()}
54 </span>
54 </span>
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label">
59 <div class="label">
60 <label for="group_parent_id">${_('Group Parent')}:</label>
60 <label for="group_parent_id">${_('Group Parent')}:</label>
61 </div>
61 </div>
62 <div class="select">
62 <div class="select">
63 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
63 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <div id="copy_perms" class="field">
67 <div id="copy_perms" class="field">
68 <div class="label label-checkbox">
68 <div class="label label-checkbox">
69 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
69 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
70 </div>
70 </div>
71 <div class="checkboxes">
71 <div class="checkboxes">
72 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
72 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
73 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
73 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
74 </div>
74 </div>
75 </div>
75 </div>
76
76
77 <div class="buttons">
77 <div class="buttons">
78 ${h.submit('save',_('Save'),class_="btn")}
78 ${h.submit('save',_('Save'),class_="btn")}
79 </div>
79 </div>
80 </div>
80 </div>
81 </div>
81 </div>
82 ${h.end_form()}
82 ${h.end_form()}
83 </div>
83 </div>
84 <script>
84 <script>
85 $(document).ready(function(){
85 $(document).ready(function(){
86 var setCopyPermsOption = function(group_val){
86 var setCopyPermsOption = function(group_val){
87 if(group_val != "-1"){
87 if(group_val != "-1"){
88 $('#copy_perms').show()
88 $('#copy_perms').show()
89 }
89 }
90 else{
90 else{
91 $('#copy_perms').hide();
91 $('#copy_perms').hide();
92 }
92 }
93 }
93 }
94 $("#group_parent_id").select2({
94 $("#group_parent_id").select2({
95 'containerCssClass': "drop-menu",
95 'containerCssClass': "drop-menu",
96 'dropdownCssClass': "drop-menu-dropdown",
96 'dropdownCssClass': "drop-menu-dropdown",
97 'dropdownAutoWidth': true
97 'dropdownAutoWidth': true
98 });
98 });
99 setCopyPermsOption($('#group_parent_id').val())
99 setCopyPermsOption($('#group_parent_id').val())
100 $("#group_parent_id").on("change", function(e) {
100 $("#group_parent_id").on("change", function(e) {
101 setCopyPermsOption(e.val)
101 setCopyPermsOption(e.val)
102 })
102 })
103 $('#group_name').focus();
103 $('#group_name').focus();
104 })
104 })
105 </script>
105 </script>
106 </%def>
106 </%def>
@@ -1,61 +1,61 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 ${_('%s repository group settings') % c.repo_group.name}
5 ${_('%s repository group settings') % c.repo_group.name}
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 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
15 %if c.repo_group.parent_group:
15 %if c.repo_group.parent_group:
16 &raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
16 &raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
17 %endif
17 %endif
18 &raquo; ${c.repo_group.name}
18 &raquo; ${c.repo_group.name}
19 </%def>
19 </%def>
20
20
21 <%def name="breadcrumbs_side_links()">
21 <%def name="breadcrumbs_side_links()">
22 <ul class="links">
22 <ul class="links">
23 <li>
23 <li>
24 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
24 <a href="${h.route_path('repo_group_new', _query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
25 </li>
25 </li>
26 </ul>
26 </ul>
27 </%def>
27 </%def>
28
28
29 <%def name="menu_bar_nav()">
29 <%def name="menu_bar_nav()">
30 ${self.menu_items(active='admin')}
30 ${self.menu_items(active='admin')}
31 </%def>
31 </%def>
32
32
33 <%def name="main_content()">
33 <%def name="main_content()">
34 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
34 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
35 </%def>
35 </%def>
36
36
37 <%def name="main()">
37 <%def name="main()">
38 <div class="box">
38 <div class="box">
39 <div class="title">
39 <div class="title">
40 ${self.breadcrumbs()}
40 ${self.breadcrumbs()}
41 ${self.breadcrumbs_side_links()}
41 ${self.breadcrumbs_side_links()}
42 </div>
42 </div>
43
43
44 <div class="sidebar-col-wrapper">
44 <div class="sidebar-col-wrapper">
45 ##main
45 ##main
46 <div class="sidebar">
46 <div class="sidebar">
47 <ul class="nav nav-pills nav-stacked">
47 <ul class="nav nav-pills nav-stacked">
48 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_repo_group', group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
48 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
49 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
49 <li class="${'active' if c.active=='permissions' else ''}"><a href="${h.route_path('edit_repo_group_perms', repo_group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
50 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_repo_group_advanced', group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
50 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('edit_repo_group_advanced', repo_group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
51 <li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
51 <li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
52 </ul>
52 </ul>
53 </div>
53 </div>
54
54
55 <div class="main-content-full-width">
55 <div class="main-content-full-width">
56 ${self.main_content()}
56 ${self.main_content()}
57 </div>
57 </div>
58
58
59 </div>
59 </div>
60 </div>
60 </div>
61 </%def>
61 </%def>
@@ -1,64 +1,64 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
6 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
6 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
7 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
7 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
8
8
9 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
9 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
10 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
10 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
11
11
12 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
12 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
13 ]
13 ]
14 %>
14 %>
15
15
16 <div class="panel panel-default">
16 <div class="panel panel-default">
17 <div class="panel-heading">
17 <div class="panel-heading">
18 <h3 class="panel-title">${_('Repository Group: %s') % c.repo_group.group_name}</h3>
18 <h3 class="panel-title">${_('Repository Group: %s') % c.repo_group.group_name}</h3>
19 </div>
19 </div>
20 <div class="panel-body">
20 <div class="panel-body">
21 ${base.dt_info_panel(elems)}
21 ${base.dt_info_panel(elems)}
22 </div>
22 </div>
23
23
24 </div>
24 </div>
25
25
26 <div class="panel panel-danger">
26 <div class="panel panel-danger">
27 <div class="panel-heading">
27 <div class="panel-heading">
28 <h3 class="panel-title">${_('Delete repository group')}</h3>
28 <h3 class="panel-title">${_('Delete repository group')}</h3>
29 </div>
29 </div>
30 <div class="panel-body">
30 <div class="panel-body">
31 ${h.secure_form(h.url('delete_repo_group', group_name=c.repo_group.group_name),method='delete', request=request)}
31 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=c.repo_group.group_name), request=request)}
32 <table class="display">
32 <table class="display">
33
33
34 <tr>
34 <tr>
35 <td>
35 <td>
36 ${_ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
36 ${_ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
37 </td>
37 </td>
38 <td>
38 <td>
39 </td>
39 </td>
40 <td>
40 <td>
41 </td>
41 </td>
42 </tr>
42 </tr>
43 <tr>
43 <tr>
44 <td>
44 <td>
45 ${_ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
45 ${_ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
46 </td>
46 </td>
47 <td>
47 <td>
48 </td>
48 </td>
49 <td>
49 <td>
50 </td>
50 </td>
51 </tr>
51 </tr>
52
52
53 </table>
53 </table>
54 <div style="margin: 0 0 20px 0" class="fake-space"></div>
54 <div style="margin: 0 0 20px 0" class="fake-space"></div>
55
55
56 <button class="btn btn-small btn-danger" type="submit"
56 <button class="btn btn-small btn-danger" type="submit"
57 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
57 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
58 ${_('Delete this repository group')}
58 ${_('Delete this repository group')}
59 </button>
59 </button>
60 ${h.end_form()}
60 ${h.end_form()}
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64
64
@@ -1,148 +1,146 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put', request=request)}
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-user">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th></th>
16 <th></th>
17 </tr>
17 </tr>
18 ## USERS
18 ## USERS
19 %for _user in c.repo_group.permissions():
19 %for _user in c.repo_group.permissions():
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 <tr class="perm_admin_row">
21 <tr class="perm_admin_row">
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-user">
26 <td class="td-user">
27 ${base.gravatar(_user.email, 16)}
27 ${base.gravatar(_user.email, 16)}
28 <span class="user">
29 ${h.link_to_user(_user.username)}
28 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
29 %if getattr(_user, 'admin_row', None):
31 (${_('super admin')})
30 (${_('super admin')})
32 %endif
31 %endif
33 %if getattr(_user, 'owner_row', None):
32 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
33 (${_('owner')})
35 %endif
34 %endif
36 </span>
37 </td>
35 </td>
38 <td></td>
36 <td></td>
39 </tr>
37 </tr>
40 %else:
38 %else:
41 ##forbid revoking permission from yourself, except if you're an super admin
42 <tr>
39 <tr>
40 ##forbid revoking permission from yourself, except if you're an super admin
43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
41 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none')}</td>
42 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
43 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
48 <td class="td-user">
46 <td class="td-user">
49 ${base.gravatar(_user.email, 16)}
47 ${base.gravatar(_user.email, 16)}
50 <span class="user">
48 <span class="user">
51 % if _user.username == h.DEFAULT_USER:
49 % if _user.username == h.DEFAULT_USER:
52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
50 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 % else:
51 % else:
54 ${h.link_to_user(_user.username)}
52 ${h.link_to_user(_user.username)}
55 % endif
53 % endif
56 </span>
54 </span>
57 </td>
55 </td>
58 <td class="td-action">
56 <td class="td-action">
59 %if _user.username != h.DEFAULT_USER:
57 %if _user.username != h.DEFAULT_USER:
60 <span class="btn btn-link btn-danger revoke_perm"
58 <span class="btn btn-link btn-danger revoke_perm"
61 member="${_user.user_id}" member_type="user">
59 member="${_user.user_id}" member_type="user">
62 <i class="icon-remove"></i> ${_('Revoke')}
60 <i class="icon-remove"></i> ${_('Revoke')}
63 </span>
61 </span>
64 %endif
62 %endif
65 </td>
63 </td>
66 %else:
64 %else:
67 ## special case for current user permissions, we make sure he cannot take his own permissions
65 ## special case for current user permissions, we make sure he cannot take his own permissions
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
66 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
67 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
72 <td class="td-user">
70 <td class="td-user">
73 ${base.gravatar(_user.email, 16)}
71 ${base.gravatar(_user.email, 16)}
74 <span class="user">
72 <span class="user">
75 % if _user.username == h.DEFAULT_USER:
73 % if _user.username == h.DEFAULT_USER:
76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
74 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 % else:
75 % else:
78 ${h.link_to_user(_user.username)}
76 ${h.link_to_user(_user.username)}
79 % endif
77 % endif
80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
78 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 </span>
79 </span>
82 </td>
80 </td>
83 <td></td>
81 <td></td>
84 %endif
82 %endif
85 </tr>
83 </tr>
86 %endif
84 %endif
87 %endfor
85 %endfor
88
86
89 ## USER GROUPS
87 ## USER GROUPS
90 %for _user_group in c.repo_group.permission_user_groups():
88 %for _user_group in c.repo_group.permission_user_groups():
91 <tr id="id${id(_user_group.users_group_name)}">
89 <tr id="id${id(_user_group.users_group_name)}">
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}</td>
90 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
91 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
96 <td class="td-componentname">
94 <td class="td-componentname">
97 <i class="icon-group" ></i>
95 <i class="icon-group" ></i>
98 %if h.HasPermissionAny('hg.admin')():
96 %if h.HasPermissionAny('hg.admin')():
99 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
97 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
100 ${_user_group.users_group_name}
98 ${_user_group.users_group_name}
101 </a>
99 </a>
102 %else:
100 %else:
103 ${_user_group.users_group_name}
101 ${_user_group.users_group_name}
104 %endif
102 %endif
105 </td>
103 </td>
106 <td class="td-action">
104 <td class="td-action">
107 <span class="btn btn-link btn-danger revoke_perm"
105 <span class="btn btn-link btn-danger revoke_perm"
108 member="${_user_group.users_group_id}" member_type="user_group">
106 member="${_user_group.users_group_id}" member_type="user_group">
109 <i class="icon-remove"></i> ${_('Revoke')}
107 <i class="icon-remove"></i> ${_('Revoke')}
110 </span>
108 </span>
111 </td>
109 </td>
112 </tr>
110 </tr>
113 %endfor
111 %endfor
114
112
115 <tr class="new_members" id="add_perm_input"></tr>
113 <tr class="new_members" id="add_perm_input"></tr>
116 </table>
114 </table>
117 <div id="add_perm" class="link">
115 <div id="add_perm" class="link">
118 ${_('Add new')}
116 ${_('Add new')}
119 </div>
117 </div>
120 <div class="fields">
118 <div class="fields">
121 <div class="field">
119 <div class="field">
122 <div class="label label-radio">
120 <div class="label label-radio">
123 ${_('Apply to children')}:
121 ${_('Apply to children')}:
124 </div>
122 </div>
125 <div class="radios">
123 <div class="radios">
126 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
124 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
127 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
125 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
128 ${h.radio('recursive', 'repos', label=_('Repositories'))}
126 ${h.radio('recursive', 'repos', label=_('Repositories'))}
129 ${h.radio('recursive', 'all', label=_('Both'))}
127 ${h.radio('recursive', 'all', label=_('Both'))}
130 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
128 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
131 </div>
129 </div>
132 </div>
130 </div>
133 </div>
131 </div>
134 <div class="buttons">
132 <div class="buttons">
135 ${h.submit('save',_('Save'),class_="btn btn-primary")}
133 ${h.submit('save',_('Save'),class_="btn btn-primary")}
136 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
134 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
137 </div>
135 </div>
138 ${h.end_form()}
136 ${h.end_form()}
139 </div>
137 </div>
140 </div>
138 </div>
141 <script type="text/javascript">
139 <script type="text/javascript">
142 $('#add_perm').on('click', function(e){
140 $('#add_perm').on('click', function(e){
143 addNewPermInput($(this), 'group');
141 addNewPermInput($(this), 'group');
144 });
142 });
145 $('.revoke_perm').on('click', function(e){
143 $('.revoke_perm').on('click', function(e){
146 markRevokePermInput($(this), 'group');
144 markRevokePermInput($(this), 'group');
147 })
145 })
148 </script>
146 </script>
@@ -1,90 +1,95 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.url('update_repo_group',group_name=c.repo_group.group_name),method='put', request=request)}
9 ${h.secure_form(h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name), request=request)}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="group_name">${_('Group Name')}:</label>
15 <label for="group_name">${_('Group Name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('group_name',class_='medium')}
18 ${c.form['repo_group_name'].render(css_class='medium', oid='group_name')|n}
19 ${c.form.render_error(request, c.form['repo_group_name'])|n}
20 </div>
21 </div>
22
23 <div class="field">
24 <div class="label">
25 <label for="repo_group">${_('Group parent')}:</label>
26 </div>
27 <div class="select">
28 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
29 ${c.form.render_error(request, c.form['repo_group'])|n}
30
31 <p class="help-block">${_('Optional select a parent group to move this repository group into.')}</p>
19 </div>
32 </div>
20 </div>
33 </div>
21
34
22 <div class="field badged-field">
35 <div class="field badged-field">
23 <div class="label">
36 <div class="label">
24 <label for="user">${_('Owner')}:</label>
37 <label for="repo_group_owner">${_('Owner')}:</label>
25 </div>
38 </div>
26 <div class="input">
39 <div class="input">
27 <div class="badge-input-container">
40 <div class="badge-input-container">
28 <div class="user-badge">
41 <div class="user-badge">
29 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
42 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
30 </div>
43 </div>
31 <div class="badge-input-wrap">
44 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
45 ${c.form['repo_group_owner'].render(css_class='medium', oid='repo_group_owner')|n}
33 </div>
46 </div>
34 </div>
47 </div>
35 <form:error name="user"/>
48 ${c.form.render_error(request, c.form['repo_group_owner'])|n}
36 <p class="help-block">${_('Change owner of this repository group.')}</p>
49 <p class="help-block">${_('Change owner of this repository group.')}</p>
37 </div>
50 </div>
38 </div>
51 </div>
39
52
40 <div class="field">
53 <div class="field">
41 <div class="label label-textarea">
54 <div class="label label-textarea">
42 <label for="group_description">${_('Description')}:</label>
55 <label for="repo_group_description">${_('Description')}:</label>
43 </div>
56 </div>
44 <div class="textarea text-area editor">
57 <div class="textarea text-area editor">
45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
58 ${c.form['repo_group_description'].render(css_class='medium', oid='repo_group_description')|n}
59 ${c.form.render_error(request, c.form['repo_group_description'])|n}
60
46 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
61 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
47 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
62 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
48 <span id="meta-tags-desc" style="display: none">
63 <span id="meta-tags-desc" style="display: none">
49 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
64 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
50 ${dt.metatags_help()}
65 ${dt.metatags_help()}
51 </span>
66 </span>
52 </div>
67 </div>
53 </div>
68 </div>
54
69
55 <div class="field">
70 <div class="field">
56 <div class="label">
57 <label for="group_parent_id">${_('Group parent')}:</label>
58 </div>
59 <div class="select">
60 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
61 </div>
62 </div>
63 <div class="field">
64 <div class="label label-checkbox">
71 <div class="label label-checkbox">
65 <label for="enable_locking">${_('Enable Repository Locking')}:</label>
72 <label for="repo_group_enable_locking">${_('Enable Repository Locking')}:</label>
66 </div>
73 </div>
67 <div class="checkboxes">
74 <div class="checkboxes">
68 ${h.checkbox('enable_locking',value="True")}
75 ${c.form['repo_group_enable_locking'].render(css_class='medium', oid='repo_group_enable_locking')|n}
76 ${c.form.render_error(request, c.form['repo_group_enable_locking'])|n}
77
69 <span class="help-block">${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}</span>
78 <span class="help-block">${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}</span>
70 </div>
79 </div>
71 </div>
80 </div>
81
72 <div class="buttons">
82 <div class="buttons">
73 ${h.submit('save',_('Save'),class_="btn")}
83 ${h.submit('save',_('Save'),class_="btn")}
74 ${h.reset('reset',_('Reset'),class_="btn")}
84 ${h.reset('reset',_('Reset'),class_="btn")}
75 </div>
85 </div>
76 </div>
86 </div>
77 </div>
87 </div>
78 ${h.end_form()}
88 ${h.end_form()}
79 </div>
89 </div>
80 </div>
90 </div>
81 <script>
91 <script>
82 $(document).ready(function(){
92 $(document).ready(function(){
83 $("#group_parent_id").select2({
93 UsersAutoComplete('repo_group_owner', '${c.rhodecode_user.user_id}');
84 'containerCssClass': "drop-menu",
85 'dropdownCssClass': "drop-menu-dropdown",
86 'dropdownAutoWidth': true
87 });
88 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
89 })
94 })
90 </script>
95 </script>
@@ -1,97 +1,97 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 ${_('Repository groups administration')}
5 ${_('Repository groups administration')}
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 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 <ul class="links">
24 <ul class="links">
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 <li>
26 <li>
27 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
27 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
28 </li>
28 </li>
29 %endif
29 %endif
30 </ul>
30 </ul>
31 </div>
31 </div>
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="group_list_table" class="display"></table>
33 <table id="group_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39
40 var get_datatable_count = function(){
40 var get_datatable_count = function(){
41 var api = $('#group_list_table').dataTable().api();
41 var api = $('#group_list_table').dataTable().api();
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 };
43 };
44
44
45 // repo group list
45 // repo group list
46 $('#group_list_table').DataTable({
46 $('#group_list_table').DataTable({
47 data: ${c.data|n},
47 data: ${c.data|n},
48 dom: 'rtp',
48 dom: 'rtp',
49 pageLength: ${c.visual.admin_grid_items},
49 pageLength: ${c.visual.admin_grid_items},
50 order: [[ 0, "asc" ]],
50 order: [[ 0, "asc" ]],
51 columns: [
51 columns: [
52 { data: {"_": "name",
52 { data: {"_": "name",
53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 { data: {"_": "desc",
55 { data: {"_": "desc",
56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 { data: {"_": "last_change",
57 { data: {"_": "last_change",
58 "sort": "last_change_raw",
58 "sort": "last_change_raw",
59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 { data: {"_": "top_level_repos",
60 { data: {"_": "top_level_repos",
61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
62 { data: {"_": "owner",
62 { data: {"_": "owner",
63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 { data: {"_": "action",
64 { data: {"_": "action",
65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
66 ],
66 ],
67 language: {
67 language: {
68 paginate: DEFAULT_GRID_PAGINATION,
68 paginate: DEFAULT_GRID_PAGINATION,
69 emptyTable: _gettext("No repository groups available yet.")
69 emptyTable: _gettext("No repository groups available yet.")
70 },
70 },
71 "initComplete": function( settings, json ) {
71 "initComplete": function( settings, json ) {
72 get_datatable_count();
72 get_datatable_count();
73 quick_repo_menu();
73 quick_repo_menu();
74 }
74 }
75 });
75 });
76
76
77 // update the counter when doing search
77 // update the counter when doing search
78 $('#group_list_table').on( 'search.dt', function (e,settings) {
78 $('#group_list_table').on( 'search.dt', function (e,settings) {
79 get_datatable_count();
79 get_datatable_count();
80 });
80 });
81
81
82 // filter, filter both grids
82 // filter, filter both grids
83 $('#q_filter').on( 'keyup', function () {
83 $('#q_filter').on( 'keyup', function () {
84
84
85 var repo_group_api = $('#group_list_table').dataTable().api();
85 var repo_group_api = $('#group_list_table').dataTable().api();
86 repo_group_api
86 repo_group_api
87 .columns(0)
87 .columns(0)
88 .search(this.value)
88 .search(this.value)
89 .draw();
89 .draw();
90 });
90 });
91
91
92 // refilter table if page load via back button
92 // refilter table if page load via back button
93 $("#q_filter").trigger('keyup');
93 $("#q_filter").trigger('keyup');
94 });
94 });
95 </script>
95 </script>
96 </%def>
96 </%def>
97
97
@@ -1,609 +1,609 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
234 <li class="${is_active('showpullrequest')}">
234 <li class="${is_active('showpullrequest')}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
236 %if c.repository_pull_requests:
236 %if c.repository_pull_requests:
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
238 %endif
238 %endif
239 <div class="menulabel">${_('Pull Requests')}</div>
239 <div class="menulabel">${_('Pull Requests')}</div>
240 </a>
240 </a>
241 </li>
241 </li>
242 %endif
242 %endif
243 <li class="${is_active('options')}">
243 <li class="${is_active('options')}">
244 <a class="menulink dropdown">
244 <a class="menulink dropdown">
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
246 </a>
246 </a>
247 <ul class="submenu">
247 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
250 %endif
251 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
252 <li>
252 <li>
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 href="${h.route_path('repo_compare',
254 href="${h.route_path('repo_compare',
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 _query=dict(merge=1))}"
260 _query=dict(merge=1))}"
261 >
261 >
262 ${_('Compare fork')}
262 ${_('Compare fork')}
263 </a>
263 </a>
264 </li>
264 </li>
265 %endif
265 %endif
266
266
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
268
268
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
270 %if c.rhodecode_db_repo.locked[0]:
270 %if c.rhodecode_db_repo.locked[0]:
271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
272 %else:
272 %else:
273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
274 %endif
274 %endif
275 %endif
275 %endif
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 %endif
280 %endif
281 %endif
281 %endif
282 </ul>
282 </ul>
283 </li>
283 </li>
284 </ul>
284 </ul>
285 </div>
285 </div>
286 <div class="clear"></div>
286 <div class="clear"></div>
287 </div>
287 </div>
288 <!--- END CONTEXT BAR -->
288 <!--- END CONTEXT BAR -->
289
289
290 </%def>
290 </%def>
291
291
292 <%def name="usermenu(active=False)">
292 <%def name="usermenu(active=False)">
293 ## USER MENU
293 ## USER MENU
294 <li id="quick_login_li" class="${'active' if active else ''}">
294 <li id="quick_login_li" class="${'active' if active else ''}">
295 <a id="quick_login_link" class="menulink childs">
295 <a id="quick_login_link" class="menulink childs">
296 ${gravatar(c.rhodecode_user.email, 20)}
296 ${gravatar(c.rhodecode_user.email, 20)}
297 <span class="user">
297 <span class="user">
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
300 %else:
300 %else:
301 <span>${_('Sign in')}</span>
301 <span>${_('Sign in')}</span>
302 %endif
302 %endif
303 </span>
303 </span>
304 </a>
304 </a>
305
305
306 <div class="user-menu submenu">
306 <div class="user-menu submenu">
307 <div id="quick_login">
307 <div id="quick_login">
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
309 <h4>${_('Sign in to your account')}</h4>
309 <h4>${_('Sign in to your account')}</h4>
310 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
310 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
311 <div class="form form-vertical">
311 <div class="form form-vertical">
312 <div class="fields">
312 <div class="fields">
313 <div class="field">
313 <div class="field">
314 <div class="label">
314 <div class="label">
315 <label for="username">${_('Username')}:</label>
315 <label for="username">${_('Username')}:</label>
316 </div>
316 </div>
317 <div class="input">
317 <div class="input">
318 ${h.text('username',class_='focus',tabindex=1)}
318 ${h.text('username',class_='focus',tabindex=1)}
319 </div>
319 </div>
320
320
321 </div>
321 </div>
322 <div class="field">
322 <div class="field">
323 <div class="label">
323 <div class="label">
324 <label for="password">${_('Password')}:</label>
324 <label for="password">${_('Password')}:</label>
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
327 %endif
327 %endif
328 </div>
328 </div>
329 <div class="input">
329 <div class="input">
330 ${h.password('password',class_='focus',tabindex=2)}
330 ${h.password('password',class_='focus',tabindex=2)}
331 </div>
331 </div>
332 </div>
332 </div>
333 <div class="buttons">
333 <div class="buttons">
334 <div class="register">
334 <div class="register">
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
337 %endif
337 %endif
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
339 </div>
339 </div>
340 <div class="submit">
340 <div class="submit">
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
342 </div>
342 </div>
343 </div>
343 </div>
344 </div>
344 </div>
345 </div>
345 </div>
346 ${h.end_form()}
346 ${h.end_form()}
347 %else:
347 %else:
348 <div class="">
348 <div class="">
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
352 </div>
352 </div>
353 <div class="">
353 <div class="">
354 <ol class="links">
354 <ol class="links">
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
356 % if c.rhodecode_user.personal_repo_group:
356 % if c.rhodecode_user.personal_repo_group:
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
358 % endif
358 % endif
359 <li class="logout">
359 <li class="logout">
360 ${h.secure_form(h.route_path('logout'), request=request)}
360 ${h.secure_form(h.route_path('logout'), request=request)}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
362 ${h.end_form()}
362 ${h.end_form()}
363 </li>
363 </li>
364 </ol>
364 </ol>
365 </div>
365 </div>
366 %endif
366 %endif
367 </div>
367 </div>
368 </div>
368 </div>
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
370 <div class="pill_container">
370 <div class="pill_container">
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
372 </div>
372 </div>
373 % endif
373 % endif
374 </li>
374 </li>
375 </%def>
375 </%def>
376
376
377 <%def name="menu_items(active=None)">
377 <%def name="menu_items(active=None)">
378 <%
378 <%
379 def is_active(selected):
379 def is_active(selected):
380 if selected == active:
380 if selected == active:
381 return "active"
381 return "active"
382 return ""
382 return ""
383 %>
383 %>
384 <ul id="quick" class="main_nav navigation horizontal-list">
384 <ul id="quick" class="main_nav navigation horizontal-list">
385 <!-- repo switcher -->
385 <!-- repo switcher -->
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
388 </li>
388 </li>
389
389
390 ## ROOT MENU
390 ## ROOT MENU
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 <li class="${is_active('journal')}">
392 <li class="${is_active('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
394 <div class="menulabel">${_('Journal')}</div>
394 <div class="menulabel">${_('Journal')}</div>
395 </a>
395 </a>
396 </li>
396 </li>
397 %else:
397 %else:
398 <li class="${is_active('journal')}">
398 <li class="${is_active('journal')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
400 <div class="menulabel">${_('Public journal')}</div>
400 <div class="menulabel">${_('Public journal')}</div>
401 </a>
401 </a>
402 </li>
402 </li>
403 %endif
403 %endif
404 <li class="${is_active('gists')}">
404 <li class="${is_active('gists')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
406 <div class="menulabel">${_('Gists')}</div>
406 <div class="menulabel">${_('Gists')}</div>
407 </a>
407 </a>
408 </li>
408 </li>
409 <li class="${is_active('search')}">
409 <li class="${is_active('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
411 <div class="menulabel">${_('Search')}</div>
411 <div class="menulabel">${_('Search')}</div>
412 </a>
412 </a>
413 </li>
413 </li>
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
415 <li class="${is_active('admin')}">
415 <li class="${is_active('admin')}">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 </a>
418 </a>
419 ${admin_menu()}
419 ${admin_menu()}
420 </li>
420 </li>
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
422 <li class="${is_active('admin')}">
422 <li class="${is_active('admin')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
425 </a>
425 </a>
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
427 c.rhodecode_user.repository_groups_admin,
427 c.rhodecode_user.repository_groups_admin,
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
429 </li>
429 </li>
430 % endif
430 % endif
431 % if c.debug_style:
431 % if c.debug_style:
432 <li class="${is_active('debug_style')}">
432 <li class="${is_active('debug_style')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
434 <div class="menulabel">${_('Style')}</div>
434 <div class="menulabel">${_('Style')}</div>
435 </a>
435 </a>
436 </li>
436 </li>
437 % endif
437 % endif
438 ## render extra user menu
438 ## render extra user menu
439 ${usermenu(active=(active=='my_account'))}
439 ${usermenu(active=(active=='my_account'))}
440 </ul>
440 </ul>
441
441
442 <script type="text/javascript">
442 <script type="text/javascript">
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
444
444
445 /*format the look of items in the list*/
445 /*format the look of items in the list*/
446 var format = function(state, escapeMarkup){
446 var format = function(state, escapeMarkup){
447 if (!state.id){
447 if (!state.id){
448 return state.text; // optgroup
448 return state.text; // optgroup
449 }
449 }
450 var obj_dict = state.obj;
450 var obj_dict = state.obj;
451 var tmpl = '';
451 var tmpl = '';
452
452
453 if(obj_dict && state.type == 'repo'){
453 if(obj_dict && state.type == 'repo'){
454 if(obj_dict['repo_type'] === 'hg'){
454 if(obj_dict['repo_type'] === 'hg'){
455 tmpl += '<i class="icon-hg"></i> ';
455 tmpl += '<i class="icon-hg"></i> ';
456 }
456 }
457 else if(obj_dict['repo_type'] === 'git'){
457 else if(obj_dict['repo_type'] === 'git'){
458 tmpl += '<i class="icon-git"></i> ';
458 tmpl += '<i class="icon-git"></i> ';
459 }
459 }
460 else if(obj_dict['repo_type'] === 'svn'){
460 else if(obj_dict['repo_type'] === 'svn'){
461 tmpl += '<i class="icon-svn"></i> ';
461 tmpl += '<i class="icon-svn"></i> ';
462 }
462 }
463 if(obj_dict['private']){
463 if(obj_dict['private']){
464 tmpl += '<i class="icon-lock" ></i> ';
464 tmpl += '<i class="icon-lock" ></i> ';
465 }
465 }
466 else if(visual_show_public_icon){
466 else if(visual_show_public_icon){
467 tmpl += '<i class="icon-unlock-alt"></i> ';
467 tmpl += '<i class="icon-unlock-alt"></i> ';
468 }
468 }
469 }
469 }
470 if(obj_dict && state.type == 'commit') {
470 if(obj_dict && state.type == 'commit') {
471 tmpl += '<i class="icon-tag"></i>';
471 tmpl += '<i class="icon-tag"></i>';
472 }
472 }
473 if(obj_dict && state.type == 'group'){
473 if(obj_dict && state.type == 'group'){
474 tmpl += '<i class="icon-folder-close"></i> ';
474 tmpl += '<i class="icon-folder-close"></i> ';
475 }
475 }
476 tmpl += escapeMarkup(state.text);
476 tmpl += escapeMarkup(state.text);
477 return tmpl;
477 return tmpl;
478 };
478 };
479
479
480 var formatResult = function(result, container, query, escapeMarkup) {
480 var formatResult = function(result, container, query, escapeMarkup) {
481 return format(result, escapeMarkup);
481 return format(result, escapeMarkup);
482 };
482 };
483
483
484 var formatSelection = function(data, container, escapeMarkup) {
484 var formatSelection = function(data, container, escapeMarkup) {
485 return format(data, escapeMarkup);
485 return format(data, escapeMarkup);
486 };
486 };
487
487
488 $("#repo_switcher").select2({
488 $("#repo_switcher").select2({
489 cachedDataSource: {},
489 cachedDataSource: {},
490 minimumInputLength: 2,
490 minimumInputLength: 2,
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
492 dropdownAutoWidth: true,
492 dropdownAutoWidth: true,
493 formatResult: formatResult,
493 formatResult: formatResult,
494 formatSelection: formatSelection,
494 formatSelection: formatSelection,
495 containerCssClass: "repo-switcher",
495 containerCssClass: "repo-switcher",
496 dropdownCssClass: "repo-switcher-dropdown",
496 dropdownCssClass: "repo-switcher-dropdown",
497 escapeMarkup: function(m){
497 escapeMarkup: function(m){
498 // don't escape our custom placeholder
498 // don't escape our custom placeholder
499 if(m.substr(0,23) == '<div class="menulabel">'){
499 if(m.substr(0,23) == '<div class="menulabel">'){
500 return m;
500 return m;
501 }
501 }
502
502
503 return Select2.util.escapeMarkup(m);
503 return Select2.util.escapeMarkup(m);
504 },
504 },
505 query: $.debounce(250, function(query){
505 query: $.debounce(250, function(query){
506 self = this;
506 self = this;
507 var cacheKey = query.term;
507 var cacheKey = query.term;
508 var cachedData = self.cachedDataSource[cacheKey];
508 var cachedData = self.cachedDataSource[cacheKey];
509
509
510 if (cachedData) {
510 if (cachedData) {
511 query.callback({results: cachedData.results});
511 query.callback({results: cachedData.results});
512 } else {
512 } else {
513 $.ajax({
513 $.ajax({
514 url: pyroutes.url('goto_switcher_data'),
514 url: pyroutes.url('goto_switcher_data'),
515 data: {'query': query.term},
515 data: {'query': query.term},
516 dataType: 'json',
516 dataType: 'json',
517 type: 'GET',
517 type: 'GET',
518 success: function(data) {
518 success: function(data) {
519 self.cachedDataSource[cacheKey] = data;
519 self.cachedDataSource[cacheKey] = data;
520 query.callback({results: data.results});
520 query.callback({results: data.results});
521 },
521 },
522 error: function(data, textStatus, errorThrown) {
522 error: function(data, textStatus, errorThrown) {
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
524 }
524 }
525 })
525 })
526 }
526 }
527 })
527 })
528 });
528 });
529
529
530 $("#repo_switcher").on('select2-selecting', function(e){
530 $("#repo_switcher").on('select2-selecting', function(e){
531 e.preventDefault();
531 e.preventDefault();
532 window.location = e.choice.url;
532 window.location = e.choice.url;
533 });
533 });
534
534
535 </script>
535 </script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
537 </%def>
537 </%def>
538
538
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
540 <div class="modal-dialog">
540 <div class="modal-dialog">
541 <div class="modal-content">
541 <div class="modal-content">
542 <div class="modal-header">
542 <div class="modal-header">
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
545 </div>
545 </div>
546 <div class="modal-body">
546 <div class="modal-body">
547 <div class="block-left">
547 <div class="block-left">
548 <table class="keyboard-mappings">
548 <table class="keyboard-mappings">
549 <tbody>
549 <tbody>
550 <tr>
550 <tr>
551 <th></th>
551 <th></th>
552 <th>${_('Site-wide shortcuts')}</th>
552 <th>${_('Site-wide shortcuts')}</th>
553 </tr>
553 </tr>
554 <%
554 <%
555 elems = [
555 elems = [
556 ('/', 'Open quick search box'),
556 ('/', 'Open quick search box'),
557 ('g h', 'Goto home page'),
557 ('g h', 'Goto home page'),
558 ('g g', 'Goto my private gists page'),
558 ('g g', 'Goto my private gists page'),
559 ('g G', 'Goto my public gists page'),
559 ('g G', 'Goto my public gists page'),
560 ('n r', 'New repository page'),
560 ('n r', 'New repository page'),
561 ('n g', 'New gist page'),
561 ('n g', 'New gist page'),
562 ]
562 ]
563 %>
563 %>
564 %for key, desc in elems:
564 %for key, desc in elems:
565 <tr>
565 <tr>
566 <td class="keys">
566 <td class="keys">
567 <span class="key tag">${key}</span>
567 <span class="key tag">${key}</span>
568 </td>
568 </td>
569 <td>${desc}</td>
569 <td>${desc}</td>
570 </tr>
570 </tr>
571 %endfor
571 %endfor
572 </tbody>
572 </tbody>
573 </table>
573 </table>
574 </div>
574 </div>
575 <div class="block-left">
575 <div class="block-left">
576 <table class="keyboard-mappings">
576 <table class="keyboard-mappings">
577 <tbody>
577 <tbody>
578 <tr>
578 <tr>
579 <th></th>
579 <th></th>
580 <th>${_('Repositories')}</th>
580 <th>${_('Repositories')}</th>
581 </tr>
581 </tr>
582 <%
582 <%
583 elems = [
583 elems = [
584 ('g s', 'Goto summary page'),
584 ('g s', 'Goto summary page'),
585 ('g c', 'Goto changelog page'),
585 ('g c', 'Goto changelog page'),
586 ('g f', 'Goto files page'),
586 ('g f', 'Goto files page'),
587 ('g F', 'Goto files page with file search activated'),
587 ('g F', 'Goto files page with file search activated'),
588 ('g p', 'Goto pull requests page'),
588 ('g p', 'Goto pull requests page'),
589 ('g o', 'Goto repository settings'),
589 ('g o', 'Goto repository settings'),
590 ('g O', 'Goto repository permissions settings'),
590 ('g O', 'Goto repository permissions settings'),
591 ]
591 ]
592 %>
592 %>
593 %for key, desc in elems:
593 %for key, desc in elems:
594 <tr>
594 <tr>
595 <td class="keys">
595 <td class="keys">
596 <span class="key tag">${key}</span>
596 <span class="key tag">${key}</span>
597 </td>
597 </td>
598 <td>${desc}</td>
598 <td>${desc}</td>
599 </tr>
599 </tr>
600 %endfor
600 %endfor
601 </tbody>
601 </tbody>
602 </table>
602 </table>
603 </div>
603 </div>
604 </div>
604 </div>
605 <div class="modal-footer">
605 <div class="modal-footer">
606 </div>
606 </div>
607 </div><!-- /.modal-content -->
607 </div><!-- /.modal-content -->
608 </div><!-- /.modal-dialog -->
608 </div><!-- /.modal-dialog -->
609 </div><!-- /.modal -->
609 </div><!-- /.modal -->
@@ -1,268 +1,268 b''
1 ## snippet for displaying permissions overview for users
1 ## snippet for displaying permissions overview for users
2 ## usage:
2 ## usage:
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 ## ${p.perms_summary(c.perm_user.permissions)}
4 ## ${p.perms_summary(c.perm_user.permissions)}
5
5
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 <div id="perms" class="table fields">
7 <div id="perms" class="table fields">
8 %for section in sorted(permissions.keys()):
8 %for section in sorted(permissions.keys()):
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10 <div class="panel-heading">
10 <div class="panel-heading">
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 % if side_link:
12 % if side_link:
13 <div class="pull-right">
13 <div class="pull-right">
14 <a href="${side_link}">${_('in JSON format')}</a>
14 <a href="${side_link}">${_('in JSON format')}</a>
15 </div>
15 </div>
16 % endif
16 % endif
17 </div>
17 </div>
18 <div class="panel-body">
18 <div class="panel-body">
19 <div class="perms_section_head field">
19 <div class="perms_section_head field">
20 <div class="radios">
20 <div class="radios">
21 %if section != 'global':
21 %if section != 'global':
22 <span class="permissions_boxes">
22 <span class="permissions_boxes">
23 <span class="desc">${_('show')}: </span>
23 <span class="desc">${_('show')}: </span>
24 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
24 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
25 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
25 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
26 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
26 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
27 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
27 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
28 </span>
28 </span>
29 %endif
29 %endif
30 </div>
30 </div>
31 </div>
31 </div>
32 <div class="field">
32 <div class="field">
33 %if not permissions[section]:
33 %if not permissions[section]:
34 <p class="empty_data help-block">${_('No permissions defined')}</p>
34 <p class="empty_data help-block">${_('No permissions defined')}</p>
35 %else:
35 %else:
36 <div id='tbl_list_wrap_${section}'>
36 <div id='tbl_list_wrap_${section}'>
37 <table id="tbl_list_${section}" class="rctable">
37 <table id="tbl_list_${section}" class="rctable">
38 ## global permission box
38 ## global permission box
39 %if section == 'global':
39 %if section == 'global':
40 <thead>
40 <thead>
41 <tr>
41 <tr>
42 <th colspan="2" class="left">${_('Permission')}</th>
42 <th colspan="2" class="left">${_('Permission')}</th>
43 %if actions:
43 %if actions:
44 <th colspan="2">${_('Edit Permission')}</th>
44 <th colspan="2">${_('Edit Permission')}</th>
45 %endif
45 %endif
46 </thead>
46 </thead>
47 <tbody>
47 <tbody>
48
48
49 <%
49 <%
50 def get_section_perms(prefix, opts):
50 def get_section_perms(prefix, opts):
51 _selected = []
51 _selected = []
52 for op in opts:
52 for op in opts:
53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
54 _selected.append(op)
54 _selected.append(op)
55 admin = 'hg.admin' in opts
55 admin = 'hg.admin' in opts
56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
57 return admin, _selected_vals, _selected
57 return admin, _selected_vals, _selected
58 %>
58 %>
59
59
60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
61 <tr>
61 <tr>
62 <td class="td-tags">
62 <td class="td-tags">
63 ${lbl}
63 ${lbl}
64 </td>
64 </td>
65 <td class="td-tags">
65 <td class="td-tags">
66 %if val[0]:
66 %if val[0]:
67 %if not val_lbl:
67 %if not val_lbl:
68 ## super admin case
68 ## super admin case
69 True
69 True
70 %else:
70 %else:
71 <span class="perm_tag admin">${val_lbl}.admin</span>
71 <span class="perm_tag admin">${val_lbl}.admin</span>
72 %endif
72 %endif
73 %else:
73 %else:
74 %if not val_lbl:
74 %if not val_lbl:
75 ${
75 ${
76 {'false': False,
76 {'false': False,
77 'true': True,
77 'true': True,
78 'none': False,
78 'none': False,
79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
80 }
80 }
81 %else:
81 %else:
82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
83 %endif
83 %endif
84 %endif
84 %endif
85 </td>
85 </td>
86 %if actions:
86 %if actions:
87
87
88 % if edit_url or edit_global_url:
88 % if edit_url or edit_global_url:
89
89
90 <td class="td-action">
90 <td class="td-action">
91 % if edit_url:
91 % if edit_url:
92 <a href="${edit_url}">${_('edit')}</a>
92 <a href="${edit_url}">${_('edit')}</a>
93 % else:
93 % else:
94 -
94 -
95 % endif
95 % endif
96 </td>
96 </td>
97
97
98 <td class="td-action">
98 <td class="td-action">
99 % if edit_global_url:
99 % if edit_global_url:
100 <a href="${edit_global_url}">${_('edit global')}</a>
100 <a href="${edit_global_url}">${_('edit global')}</a>
101 % else:
101 % else:
102 -
102 -
103 % endif
103 % endif
104 </td>
104 </td>
105
105
106 % else:
106 % else:
107 <td class="td-action"></td>
107 <td class="td-action"></td>
108 <td class="td-action">
108 <td class="td-action">
109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
110 <td class="td-action">
110 <td class="td-action">
111 % endif
111 % endif
112
112
113 %endif
113 %endif
114 </tr>
114 </tr>
115 </%def>
115 </%def>
116
116
117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
119
119
120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
122
122
123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
125
125
126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
127 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
127 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
128
128
129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
130 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
130 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
131
131
132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
133 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
133 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
134
134
135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
136 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
136 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
137
137
138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
139 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
139 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
140
140
141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
142 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
142 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
143
143
144 </tbody>
144 </tbody>
145 %else:
145 %else:
146 ## none/read/write/admin permissions on groups/repos etc
146 ## none/read/write/admin permissions on groups/repos etc
147 <thead>
147 <thead>
148 <tr>
148 <tr>
149 <th>${_('Name')}</th>
149 <th>${_('Name')}</th>
150 <th>${_('Permission')}</th>
150 <th>${_('Permission')}</th>
151 %if actions:
151 %if actions:
152 <th>${_('Edit Permission')}</th>
152 <th>${_('Edit Permission')}</th>
153 %endif
153 %endif
154 </thead>
154 </thead>
155 <tbody class="section_${section}">
155 <tbody class="section_${section}">
156 <%
156 <%
157 def sorter(permissions):
157 def sorter(permissions):
158 def custom_sorter(item):
158 def custom_sorter(item):
159 ## read/write/admin
159 ## read/write/admin
160 section = item[1].split('.')[-1]
160 section = item[1].split('.')[-1]
161 section_importance = {'none': u'0',
161 section_importance = {'none': u'0',
162 'read': u'1',
162 'read': u'1',
163 'write':u'2',
163 'write':u'2',
164 'admin':u'3'}.get(section)
164 'admin':u'3'}.get(section)
165 ## sort by group importance+name
165 ## sort by group importance+name
166 return section_importance+item[0]
166 return section_importance+item[0]
167 return sorted(permissions, key=custom_sorter)
167 return sorted(permissions, key=custom_sorter)
168 %>
168 %>
169 %for k, section_perm in sorter(permissions[section].items()):
169 %for k, section_perm in sorter(permissions[section].items()):
170 %if section_perm.split('.')[-1] != 'none' or show_all:
170 %if section_perm.split('.')[-1] != 'none' or show_all:
171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
172 <td class="td-name">
172 <td class="td-name">
173 %if section == 'repositories':
173 %if section == 'repositories':
174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
175 %elif section == 'repositories_groups':
175 %elif section == 'repositories_groups':
176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
177 %elif section == 'user_groups':
177 %elif section == 'user_groups':
178 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
178 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
179 ${k}
179 ${k}
180 %endif
180 %endif
181 </td>
181 </td>
182 <td class="td-tags">
182 <td class="td-tags">
183 %if hasattr(permissions[section], 'perm_origin_stack'):
183 %if hasattr(permissions[section], 'perm_origin_stack'):
184 <div>
184 <div>
185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
186
186
187 % if i > 0:
187 % if i > 0:
188 <div style="color: #979797">
188 <div style="color: #979797">
189 <i class="icon-arrow_up"></i>
189 <i class="icon-arrow_up"></i>
190 ${_('overridden by')}
190 ${_('overridden by')}
191 <i class="icon-arrow_up"></i>
191 <i class="icon-arrow_up"></i>
192 </div>
192 </div>
193 % endif
193 % endif
194
194
195 <div>
195 <div>
196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
197 ${perm} (${origin})
197 ${perm} (${origin})
198 </span>
198 </span>
199 </div>
199 </div>
200
200
201 %endfor
201 %endfor
202 </div>
202 </div>
203 %else:
203 %else:
204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
205 %endif
205 %endif
206 </td>
206 </td>
207 %if actions:
207 %if actions:
208 <td class="td-action">
208 <td class="td-action">
209 %if section == 'repositories':
209 %if section == 'repositories':
210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
211 %elif section == 'repositories_groups':
211 %elif section == 'repositories_groups':
212 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
212 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
213 %elif section == 'user_groups':
213 %elif section == 'user_groups':
214 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
214 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
215 %endif
215 %endif
216 </td>
216 </td>
217 %endif
217 %endif
218 </tr>
218 </tr>
219 %endif
219 %endif
220 %endfor
220 %endfor
221
221
222 <tr id="empty_${section}" class="noborder" style="display:none;">
222 <tr id="empty_${section}" class="noborder" style="display:none;">
223 <td colspan="6">${_('No permission defined')}</td>
223 <td colspan="6">${_('No permission defined')}</td>
224 </tr>
224 </tr>
225
225
226 </tbody>
226 </tbody>
227 %endif
227 %endif
228 </table>
228 </table>
229 </div>
229 </div>
230 %endif
230 %endif
231 </div>
231 </div>
232 </div>
232 </div>
233 </div>
233 </div>
234 %endfor
234 %endfor
235 </div>
235 </div>
236
236
237 <script>
237 <script>
238 $(document).ready(function(){
238 $(document).ready(function(){
239 var show_empty = function(section){
239 var show_empty = function(section){
240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
241 if(visible == 0){
241 if(visible == 0){
242 $('#empty_{0}'.format(section)).show();
242 $('#empty_{0}'.format(section)).show();
243 }
243 }
244 else{
244 else{
245 $('#empty_{0}'.format(section)).hide();
245 $('#empty_{0}'.format(section)).hide();
246 }
246 }
247 };
247 };
248 $('.perm_filter').on('change', function(e){
248 $('.perm_filter').on('change', function(e){
249 var self = this;
249 var self = this;
250 var section = $(this).attr('section');
250 var section = $(this).attr('section');
251
251
252 var opts = {};
252 var opts = {};
253 var elems = $('.filter_' + section).each(function(el){
253 var elems = $('.filter_' + section).each(function(el){
254 var perm_type = $(this).attr('perm_type');
254 var perm_type = $(this).attr('perm_type');
255 var checked = this.checked;
255 var checked = this.checked;
256 opts[perm_type] = checked;
256 opts[perm_type] = checked;
257 if(checked){
257 if(checked){
258 $('.'+section+'_'+perm_type).show();
258 $('.'+section+'_'+perm_type).show();
259 }
259 }
260 else{
260 else{
261 $('.'+section+'_'+perm_type).hide();
261 $('.'+section+'_'+perm_type).hide();
262 }
262 }
263 });
263 });
264 show_empty(section);
264 show_empty(section);
265 })
265 })
266 })
266 })
267 </script>
267 </script>
268 </%def>
268 </%def>
@@ -1,377 +1,377 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
6 <%def name="metatags_help()">
7 <table>
7 <table>
8 <%
8 <%
9 example_tags = [
9 example_tags = [
10 ('state','[stable]'),
10 ('state','[stable]'),
11 ('state','[stale]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
14 ('state','[dead]'),
15 ('state','[deprecated]'),
15 ('state','[deprecated]'),
16
16
17 ('label','[personal]'),
17 ('label','[personal]'),
18 ('generic','[v2.0.0]'),
18 ('generic','[v2.0.0]'),
19
19
20 ('lang','[lang =&gt; JavaScript]'),
20 ('lang','[lang =&gt; JavaScript]'),
21 ('license','[license =&gt; LicenseName]'),
21 ('license','[license =&gt; LicenseName]'),
22
22
23 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[requires =&gt; RepoName]'),
24 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[recommends =&gt; GroupName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
29 ]
29 ]
30 %>
30 %>
31 % for tag_type, tag in example_tags:
31 % for tag_type, tag in example_tags:
32 <tr>
32 <tr>
33 <td>${tag|n}</td>
33 <td>${tag|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 </tr>
35 </tr>
36 % endfor
36 % endfor
37 </table>
37 </table>
38 </%def>
38 </%def>
39
39
40 ## REPOSITORY RENDERERS
40 ## REPOSITORY RENDERERS
41 <%def name="quick_menu(repo_name)">
41 <%def name="quick_menu(repo_name)">
42 <i class="icon-more"></i>
42 <i class="icon-more"></i>
43 <div class="menu_items_container hidden">
43 <div class="menu_items_container hidden">
44 <ul class="menu_items">
44 <ul class="menu_items">
45 <li>
45 <li>
46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
47 <span>${_('Summary')}</span>
47 <span>${_('Summary')}</span>
48 </a>
48 </a>
49 </li>
49 </li>
50 <li>
50 <li>
51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
52 <span>${_('Changelog')}</span>
52 <span>${_('Changelog')}</span>
53 </a>
53 </a>
54 </li>
54 </li>
55 <li>
55 <li>
56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
57 <span>${_('Files')}</span>
57 <span>${_('Files')}</span>
58 </a>
58 </a>
59 </li>
59 </li>
60 <li>
60 <li>
61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
62 <span>${_('Fork')}</span>
62 <span>${_('Fork')}</span>
63 </a>
63 </a>
64 </li>
64 </li>
65 </ul>
65 </ul>
66 </div>
66 </div>
67 </%def>
67 </%def>
68
68
69 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
69 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
70 <%
70 <%
71 def get_name(name,short_name=short_name):
71 def get_name(name,short_name=short_name):
72 if short_name:
72 if short_name:
73 return name.split('/')[-1]
73 return name.split('/')[-1]
74 else:
74 else:
75 return name
75 return name
76 %>
76 %>
77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
78 ##NAME
78 ##NAME
79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
80
80
81 ##TYPE OF REPO
81 ##TYPE OF REPO
82 %if h.is_hg(rtype):
82 %if h.is_hg(rtype):
83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
84 %elif h.is_git(rtype):
84 %elif h.is_git(rtype):
85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
86 %elif h.is_svn(rtype):
86 %elif h.is_svn(rtype):
87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
88 %endif
88 %endif
89
89
90 ##PRIVATE/PUBLIC
90 ##PRIVATE/PUBLIC
91 %if private and c.visual.show_private_icon:
91 %if private and c.visual.show_private_icon:
92 <i class="icon-lock" title="${_('Private repository')}"></i>
92 <i class="icon-lock" title="${_('Private repository')}"></i>
93 %elif not private and c.visual.show_public_icon:
93 %elif not private and c.visual.show_public_icon:
94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
95 %else:
95 %else:
96 <span></span>
96 <span></span>
97 %endif
97 %endif
98 ${get_name(name)}
98 ${get_name(name)}
99 </a>
99 </a>
100 %if fork_of:
100 %if fork_of:
101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
102 %endif
102 %endif
103 %if rstate == 'repo_state_pending':
103 %if rstate == 'repo_state_pending':
104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
105 (${_('creating...')})
105 (${_('creating...')})
106 </span>
106 </span>
107 %endif
107 %endif
108 </div>
108 </div>
109 </%def>
109 </%def>
110
110
111 <%def name="repo_desc(description, stylify_metatags)">
111 <%def name="repo_desc(description, stylify_metatags)">
112 <%
112 <%
113 tags, description = h.extract_metatags(description)
113 tags, description = h.extract_metatags(description)
114 %>
114 %>
115
115
116 <div class="truncate-wrap">
116 <div class="truncate-wrap">
117 % if stylify_metatags:
117 % if stylify_metatags:
118 % for tag_type, tag in tags:
118 % for tag_type, tag in tags:
119 ${h.style_metatag(tag_type, tag)|n}
119 ${h.style_metatag(tag_type, tag)|n}
120 % endfor
120 % endfor
121 % endif
121 % endif
122 ${description}
122 ${description}
123 </div>
123 </div>
124
124
125 </%def>
125 </%def>
126
126
127 <%def name="last_change(last_change)">
127 <%def name="last_change(last_change)">
128 ${h.age_component(last_change)}
128 ${h.age_component(last_change)}
129 </%def>
129 </%def>
130
130
131 <%def name="revision(name,rev,tip,author,last_msg)">
131 <%def name="revision(name,rev,tip,author,last_msg)">
132 <div>
132 <div>
133 %if rev >= 0:
133 %if rev >= 0:
134 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
134 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
135 %else:
135 %else:
136 ${_('No commits yet')}
136 ${_('No commits yet')}
137 %endif
137 %endif
138 </div>
138 </div>
139 </%def>
139 </%def>
140
140
141 <%def name="rss(name)">
141 <%def name="rss(name)">
142 %if c.rhodecode_user.username != h.DEFAULT_USER:
142 %if c.rhodecode_user.username != h.DEFAULT_USER:
143 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
143 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
144 %else:
144 %else:
145 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
145 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
146 %endif
146 %endif
147 </%def>
147 </%def>
148
148
149 <%def name="atom(name)">
149 <%def name="atom(name)">
150 %if c.rhodecode_user.username != h.DEFAULT_USER:
150 %if c.rhodecode_user.username != h.DEFAULT_USER:
151 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
151 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
152 %else:
152 %else:
153 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
153 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
154 %endif
154 %endif
155 </%def>
155 </%def>
156
156
157 <%def name="user_gravatar(email, size=16)">
157 <%def name="user_gravatar(email, size=16)">
158 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
158 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
159 ${base.gravatar(email, 16)}
159 ${base.gravatar(email, 16)}
160 </div>
160 </div>
161 </%def>
161 </%def>
162
162
163 <%def name="repo_actions(repo_name, super_user=True)">
163 <%def name="repo_actions(repo_name, super_user=True)">
164 <div>
164 <div>
165 <div class="grid_edit">
165 <div class="grid_edit">
166 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
166 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
167 <i class="icon-pencil"></i>Edit</a>
167 <i class="icon-pencil"></i>Edit</a>
168 </div>
168 </div>
169 <div class="grid_delete">
169 <div class="grid_delete">
170 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
170 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
171 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
171 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
172 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
172 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
173 ${h.end_form()}
173 ${h.end_form()}
174 </div>
174 </div>
175 </div>
175 </div>
176 </%def>
176 </%def>
177
177
178 <%def name="repo_state(repo_state)">
178 <%def name="repo_state(repo_state)">
179 <div>
179 <div>
180 %if repo_state == 'repo_state_pending':
180 %if repo_state == 'repo_state_pending':
181 <div class="tag tag4">${_('Creating')}</div>
181 <div class="tag tag4">${_('Creating')}</div>
182 %elif repo_state == 'repo_state_created':
182 %elif repo_state == 'repo_state_created':
183 <div class="tag tag1">${_('Created')}</div>
183 <div class="tag tag1">${_('Created')}</div>
184 %else:
184 %else:
185 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
185 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
186 %endif
186 %endif
187 </div>
187 </div>
188 </%def>
188 </%def>
189
189
190
190
191 ## REPO GROUP RENDERERS
191 ## REPO GROUP RENDERERS
192 <%def name="quick_repo_group_menu(repo_group_name)">
192 <%def name="quick_repo_group_menu(repo_group_name)">
193 <i class="icon-more"></i>
193 <i class="icon-more"></i>
194 <div class="menu_items_container hidden">
194 <div class="menu_items_container hidden">
195 <ul class="menu_items">
195 <ul class="menu_items">
196 <li>
196 <li>
197 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
197 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
198 </li>
198 </li>
199
199
200 </ul>
200 </ul>
201 </div>
201 </div>
202 </%def>
202 </%def>
203
203
204 <%def name="repo_group_name(repo_group_name, children_groups=None)">
204 <%def name="repo_group_name(repo_group_name, children_groups=None)">
205 <div>
205 <div>
206 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
206 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
207 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
207 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
208 %if children_groups:
208 %if children_groups:
209 ${h.literal(' &raquo; '.join(children_groups))}
209 ${h.literal(' &raquo; '.join(children_groups))}
210 %else:
210 %else:
211 ${repo_group_name}
211 ${repo_group_name}
212 %endif
212 %endif
213 </a>
213 </a>
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_group_desc(description, personal, stylify_metatags)">
217 <%def name="repo_group_desc(description, personal, stylify_metatags)">
218
218
219 <%
219 <%
220 tags, description = h.extract_metatags(description)
220 tags, description = h.extract_metatags(description)
221 %>
221 %>
222
222
223 <div class="truncate-wrap">
223 <div class="truncate-wrap">
224 % if personal:
224 % if personal:
225 <div class="metatag" tag="personal">${_('personal')}</div>
225 <div class="metatag" tag="personal">${_('personal')}</div>
226 % endif
226 % endif
227
227
228 % if stylify_metatags:
228 % if stylify_metatags:
229 % for tag_type, tag in tags:
229 % for tag_type, tag in tags:
230 ${h.style_metatag(tag_type, tag)|n}
230 ${h.style_metatag(tag_type, tag)|n}
231 % endfor
231 % endfor
232 % endif
232 % endif
233 ${description}
233 ${description}
234 </div>
234 </div>
235
235
236 </%def>
236 </%def>
237
237
238 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
238 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
239 <div class="grid_edit">
239 <div class="grid_edit">
240 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
240 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
241 </div>
241 </div>
242 <div class="grid_delete">
242 <div class="grid_delete">
243 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete', request=request)}
243 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
244 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
244 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
245 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
245 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
246 ${h.end_form()}
246 ${h.end_form()}
247 </div>
247 </div>
248 </%def>
248 </%def>
249
249
250
250
251 <%def name="user_actions(user_id, username)">
251 <%def name="user_actions(user_id, username)">
252 <div class="grid_edit">
252 <div class="grid_edit">
253 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
253 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
254 <i class="icon-pencil"></i>${_('Edit')}</a>
254 <i class="icon-pencil"></i>${_('Edit')}</a>
255 </div>
255 </div>
256 <div class="grid_delete">
256 <div class="grid_delete">
257 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
257 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
258 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
258 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
259 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
259 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
260 ${h.end_form()}
260 ${h.end_form()}
261 </div>
261 </div>
262 </%def>
262 </%def>
263
263
264 <%def name="user_group_actions(user_group_id, user_group_name)">
264 <%def name="user_group_actions(user_group_id, user_group_name)">
265 <div class="grid_edit">
265 <div class="grid_edit">
266 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
266 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
267 </div>
267 </div>
268 <div class="grid_delete">
268 <div class="grid_delete">
269 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
269 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
270 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
270 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
271 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
271 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
272 ${h.end_form()}
272 ${h.end_form()}
273 </div>
273 </div>
274 </%def>
274 </%def>
275
275
276
276
277 <%def name="user_name(user_id, username)">
277 <%def name="user_name(user_id, username)">
278 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
278 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
279 </%def>
279 </%def>
280
280
281 <%def name="user_profile(username)">
281 <%def name="user_profile(username)">
282 ${base.gravatar_with_user(username, 16)}
282 ${base.gravatar_with_user(username, 16)}
283 </%def>
283 </%def>
284
284
285 <%def name="user_group_name(user_group_id, user_group_name)">
285 <%def name="user_group_name(user_group_id, user_group_name)">
286 <div>
286 <div>
287 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
287 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
288 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
288 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
289 </div>
289 </div>
290 </%def>
290 </%def>
291
291
292
292
293 ## GISTS
293 ## GISTS
294
294
295 <%def name="gist_gravatar(full_contact)">
295 <%def name="gist_gravatar(full_contact)">
296 <div class="gist_gravatar">
296 <div class="gist_gravatar">
297 ${base.gravatar(full_contact, 30)}
297 ${base.gravatar(full_contact, 30)}
298 </div>
298 </div>
299 </%def>
299 </%def>
300
300
301 <%def name="gist_access_id(gist_access_id, full_contact)">
301 <%def name="gist_access_id(gist_access_id, full_contact)">
302 <div>
302 <div>
303 <b>
303 <b>
304 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
304 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
305 </b>
305 </b>
306 </div>
306 </div>
307 </%def>
307 </%def>
308
308
309 <%def name="gist_author(full_contact, created_on, expires)">
309 <%def name="gist_author(full_contact, created_on, expires)">
310 ${base.gravatar_with_user(full_contact, 16)}
310 ${base.gravatar_with_user(full_contact, 16)}
311 </%def>
311 </%def>
312
312
313
313
314 <%def name="gist_created(created_on)">
314 <%def name="gist_created(created_on)">
315 <div class="created">
315 <div class="created">
316 ${h.age_component(created_on, time_is_local=True)}
316 ${h.age_component(created_on, time_is_local=True)}
317 </div>
317 </div>
318 </%def>
318 </%def>
319
319
320 <%def name="gist_expires(expires)">
320 <%def name="gist_expires(expires)">
321 <div class="created">
321 <div class="created">
322 %if expires == -1:
322 %if expires == -1:
323 ${_('never')}
323 ${_('never')}
324 %else:
324 %else:
325 ${h.age_component(h.time_to_utcdatetime(expires))}
325 ${h.age_component(h.time_to_utcdatetime(expires))}
326 %endif
326 %endif
327 </div>
327 </div>
328 </%def>
328 </%def>
329
329
330 <%def name="gist_type(gist_type)">
330 <%def name="gist_type(gist_type)">
331 %if gist_type != 'public':
331 %if gist_type != 'public':
332 <div class="tag">${_('Private')}</div>
332 <div class="tag">${_('Private')}</div>
333 %endif
333 %endif
334 </%def>
334 </%def>
335
335
336 <%def name="gist_description(gist_description)">
336 <%def name="gist_description(gist_description)">
337 ${gist_description}
337 ${gist_description}
338 </%def>
338 </%def>
339
339
340
340
341 ## PULL REQUESTS GRID RENDERERS
341 ## PULL REQUESTS GRID RENDERERS
342
342
343 <%def name="pullrequest_target_repo(repo_name)">
343 <%def name="pullrequest_target_repo(repo_name)">
344 <div class="truncate">
344 <div class="truncate">
345 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
345 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
346 </div>
346 </div>
347 </%def>
347 </%def>
348 <%def name="pullrequest_status(status)">
348 <%def name="pullrequest_status(status)">
349 <div class="${'flag_status %s' % status} pull-left"></div>
349 <div class="${'flag_status %s' % status} pull-left"></div>
350 </%def>
350 </%def>
351
351
352 <%def name="pullrequest_title(title, description)">
352 <%def name="pullrequest_title(title, description)">
353 ${title} <br/>
353 ${title} <br/>
354 ${h.shorter(description, 40)}
354 ${h.shorter(description, 40)}
355 </%def>
355 </%def>
356
356
357 <%def name="pullrequest_comments(comments_nr)">
357 <%def name="pullrequest_comments(comments_nr)">
358 <i class="icon-comment"></i> ${comments_nr}
358 <i class="icon-comment"></i> ${comments_nr}
359 </%def>
359 </%def>
360
360
361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
363 % if short:
363 % if short:
364 #${pull_request_id}
364 #${pull_request_id}
365 % else:
365 % else:
366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
367 % endif
367 % endif
368 </a>
368 </a>
369 </%def>
369 </%def>
370
370
371 <%def name="pullrequest_updated_on(updated_on)">
371 <%def name="pullrequest_updated_on(updated_on)">
372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
373 </%def>
373 </%def>
374
374
375 <%def name="pullrequest_author(full_contact)">
375 <%def name="pullrequest_author(full_contact)">
376 ${base.gravatar_with_user(full_contact, 16)}
376 ${base.gravatar_with_user(full_contact, 16)}
377 </%def>
377 </%def>
@@ -1,173 +1,173 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="main()">
3 <%def name="main()">
4 <div class="box">
4 <div class="box">
5 <!-- box / title -->
5 <!-- box / title -->
6 <div class="title">
6 <div class="title">
7 <div class="block-left breadcrumbs">
7 <div class="block-left breadcrumbs">
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 ${self.breadcrumbs()}
9 ${self.breadcrumbs()}
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
11 </div>
11 </div>
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 <div class="block-right">
13 <div class="block-right">
14 <%
14 <%
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19
19
20 gr_name = c.repo_group.group_name if c.repo_group else None
20 gr_name = c.repo_group.group_name if c.repo_group else None
21 # create repositories with write permission on group is set to true
21 # create repositories with write permission on group is set to true
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 %>
25 %>
26
26
27 %if not c.repo_group:
27 %if not c.repo_group:
28 ## no repository group context here
28 ## no repository group context here
29 %if is_admin or create_repo:
29 %if is_admin or create_repo:
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 %endif
31 %endif
32
32
33 %if is_admin or create_repo_group:
33 %if is_admin or create_repo_group:
34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
34 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
35 %endif
35 %endif
36 %else:
36 %else:
37 ##we're inside other repository group other terms apply
37 ##we're inside other repository group other terms apply
38 %if is_admin or group_admin or (group_write and create_on_write):
38 %if is_admin or group_admin or (group_write and create_on_write):
39 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
39 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
40 %endif
40 %endif
41 %if is_admin or group_admin:
41 %if is_admin or group_admin:
42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
42 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
43 %endif
43 %endif
44 %if is_admin or group_admin:
44 %if is_admin or group_admin:
45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
45 <a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
46 %endif
46 %endif
47 %endif
47 %endif
48 </div>
48 </div>
49 %endif
49 %endif
50 </div>
50 </div>
51 <!-- end box / title -->
51 <!-- end box / title -->
52 <div class="table">
52 <div class="table">
53 <div id="groups_list_wrap">
53 <div id="groups_list_wrap">
54 <table id="group_list_table" class="display"></table>
54 <table id="group_list_table" class="display"></table>
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="table">
58 <div class="table">
59 <div id="repos_list_wrap">
59 <div id="repos_list_wrap">
60 <table id="repo_list_table" class="display"></table>
60 <table id="repo_list_table" class="display"></table>
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64 <script>
64 <script>
65 $(document).ready(function() {
65 $(document).ready(function() {
66
66
67 var get_datatable_count = function() {
67 var get_datatable_count = function() {
68 var api = $('#repo_list_table').dataTable().api();
68 var api = $('#repo_list_table').dataTable().api();
69 var pageInfo = api.page.info();
69 var pageInfo = api.page.info();
70 var repos = pageInfo.recordsDisplay;
70 var repos = pageInfo.recordsDisplay;
71 var reposTotal = pageInfo.recordsTotal;
71 var reposTotal = pageInfo.recordsTotal;
72
72
73 api = $('#group_list_table').dataTable().api();
73 api = $('#group_list_table').dataTable().api();
74 pageInfo = api.page.info();
74 pageInfo = api.page.info();
75 var repoGroups = pageInfo.recordsDisplay;
75 var repoGroups = pageInfo.recordsDisplay;
76 var repoGroupsTotal = pageInfo.recordsTotal;
76 var repoGroupsTotal = pageInfo.recordsTotal;
77
77
78 if (repoGroups !== repoGroupsTotal) {
78 if (repoGroups !== repoGroupsTotal) {
79 $('#match_count').text(repos+repoGroups);
79 $('#match_count').text(repos+repoGroups);
80 }
80 }
81 if (repos !== reposTotal) {
81 if (repos !== reposTotal) {
82 $('#match_container').show();
82 $('#match_container').show();
83 }
83 }
84 if ($('#q_filter').val() === '') {
84 if ($('#q_filter').val() === '') {
85 $('#match_container').hide();
85 $('#match_container').hide();
86 }
86 }
87 };
87 };
88
88
89 // repo group list
89 // repo group list
90 $('#group_list_table').DataTable({
90 $('#group_list_table').DataTable({
91 data: ${c.repo_groups_data|n},
91 data: ${c.repo_groups_data|n},
92 dom: 'rtp',
92 dom: 'rtp',
93 pageLength: ${c.visual.dashboard_items},
93 pageLength: ${c.visual.dashboard_items},
94 order: [[ 0, "asc" ]],
94 order: [[ 0, "asc" ]],
95 columns: [
95 columns: [
96 { data: {"_": "name",
96 { data: {"_": "name",
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 { data: {"_": "desc",
99 { data: {"_": "desc",
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 { data: {"_": "last_change",
101 { data: {"_": "last_change",
102 "sort": "last_change_raw",
102 "sort": "last_change_raw",
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
104 { data: {"_": "owner",
104 { data: {"_": "owner",
105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
106 ],
106 ],
107 language: {
107 language: {
108 paginate: DEFAULT_GRID_PAGINATION,
108 paginate: DEFAULT_GRID_PAGINATION,
109 emptyTable: _gettext("No repository groups available yet.")
109 emptyTable: _gettext("No repository groups available yet.")
110 },
110 },
111 "drawCallback": function( settings, json ) {
111 "drawCallback": function( settings, json ) {
112 timeagoActivate();
112 timeagoActivate();
113 quick_repo_menu();
113 quick_repo_menu();
114 }
114 }
115 });
115 });
116
116
117 // repo list
117 // repo list
118 $('#repo_list_table').DataTable({
118 $('#repo_list_table').DataTable({
119 data: ${c.repos_data|n},
119 data: ${c.repos_data|n},
120 dom: 'rtp',
120 dom: 'rtp',
121 order: [[ 0, "asc" ]],
121 order: [[ 0, "asc" ]],
122 pageLength: ${c.visual.dashboard_items},
122 pageLength: ${c.visual.dashboard_items},
123 columns: [
123 columns: [
124 { data: {"_": "name",
124 { data: {"_": "name",
125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
127 { data: {"_": "desc",
127 { data: {"_": "desc",
128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
129 { data: {"_": "last_change",
129 { data: {"_": "last_change",
130 "sort": "last_change_raw",
130 "sort": "last_change_raw",
131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
132 { data: {"_": "last_changeset",
132 { data: {"_": "last_changeset",
133 "sort": "last_changeset_raw",
133 "sort": "last_changeset_raw",
134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
135 { data: {"_": "owner",
135 { data: {"_": "owner",
136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
137 ],
137 ],
138 language: {
138 language: {
139 paginate: DEFAULT_GRID_PAGINATION,
139 paginate: DEFAULT_GRID_PAGINATION,
140 emptyTable: _gettext("No repositories available yet.")
140 emptyTable: _gettext("No repositories available yet.")
141 },
141 },
142 "drawCallback": function( settings, json ) {
142 "drawCallback": function( settings, json ) {
143 timeagoActivate();
143 timeagoActivate();
144 quick_repo_menu();
144 quick_repo_menu();
145 }
145 }
146 });
146 });
147
147
148 // update the counter when doing search
148 // update the counter when doing search
149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
150 get_datatable_count();
150 get_datatable_count();
151 });
151 });
152
152
153 // filter, filter both grids
153 // filter, filter both grids
154 $('#q_filter').on( 'keyup', function () {
154 $('#q_filter').on( 'keyup', function () {
155 var repo_api = $('#repo_list_table').dataTable().api();
155 var repo_api = $('#repo_list_table').dataTable().api();
156 repo_api
156 repo_api
157 .columns( 0 )
157 .columns( 0 )
158 .search( this.value )
158 .search( this.value )
159 .draw();
159 .draw();
160
160
161 var repo_group_api = $('#group_list_table').dataTable().api();
161 var repo_group_api = $('#group_list_table').dataTable().api();
162 repo_group_api
162 repo_group_api
163 .columns( 0 )
163 .columns( 0 )
164 .search( this.value )
164 .search( this.value )
165 .draw();
165 .draw();
166 });
166 });
167
167
168 // refilter table if page load via back button
168 // refilter table if page load via back button
169 $("#q_filter").trigger('keyup');
169 $("#q_filter").trigger('keyup');
170
170
171 });
171 });
172 </script>
172 </script>
173 </%def>
173 </%def>
@@ -1,150 +1,152 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 pytest
21 import pytest
22
22
23 from rhodecode.tests import TestController, url
23 from rhodecode.tests import TestController
24 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
25
25
26
26
27 def route_path(name, params=None, **kwargs):
27 def route_path(name, params=None, **kwargs):
28 import urllib
28 import urllib
29 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
30
30
31 base_url = {
31 base_url = {
32 'home': '/',
32 'home': '/',
33 'repos':
33 'repos':
34 ADMIN_PREFIX + '/repos',
34 ADMIN_PREFIX + '/repos',
35 'repo_groups':
36 ADMIN_PREFIX + '/repo_groups',
35 'user_groups':
37 'user_groups':
36 ADMIN_PREFIX + '/user_groups',
38 ADMIN_PREFIX + '/user_groups',
37 'user_groups_data':
39 'user_groups_data':
38 ADMIN_PREFIX + '/user_groups_data',
40 ADMIN_PREFIX + '/user_groups_data',
39 }[name].format(**kwargs)
41 }[name].format(**kwargs)
40
42
41 if params:
43 if params:
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 return base_url
45 return base_url
44
46
45
47
46 fixture = Fixture()
48 fixture = Fixture()
47
49
48
50
49 class TestAdminDelegatedUser(TestController):
51 class TestAdminDelegatedUser(TestController):
50
52
51 def test_regular_user_cannot_see_admin_interfaces(
53 def test_regular_user_cannot_see_admin_interfaces(
52 self, user_util, xhr_header):
54 self, user_util, xhr_header):
53 user = user_util.create_user(password='qweqwe')
55 user = user_util.create_user(password='qweqwe')
54 self.log_user(user.username, 'qweqwe')
56 self.log_user(user.username, 'qweqwe')
55
57
56 # check if in home view, such user doesn't see the "admin" menus
58 # check if in home view, such user doesn't see the "admin" menus
57 response = self.app.get(route_path('home'))
59 response = self.app.get(route_path('home'))
58
60
59 assert_response = response.assert_response()
61 assert_response = response.assert_response()
60
62
61 assert_response.no_element_exists('li.local-admin-repos')
63 assert_response.no_element_exists('li.local-admin-repos')
62 assert_response.no_element_exists('li.local-admin-repo-groups')
64 assert_response.no_element_exists('li.local-admin-repo-groups')
63 assert_response.no_element_exists('li.local-admin-user-groups')
65 assert_response.no_element_exists('li.local-admin-user-groups')
64
66
65 response = self.app.get(route_path('repos'), status=200)
67 response = self.app.get(route_path('repos'), status=200)
66 response.mustcontain('data: []')
68 response.mustcontain('data: []')
67
69
68 response = self.app.get(url('repo_groups'), status=200)
70 response = self.app.get(route_path('repo_groups'), status=200)
69 response.mustcontain('data: []')
71 response.mustcontain('data: []')
70
72
71 response = self.app.get(route_path('user_groups_data'),
73 response = self.app.get(route_path('user_groups_data'),
72 status=200, extra_environ=xhr_header)
74 status=200, extra_environ=xhr_header)
73 assert response.json['data'] == []
75 assert response.json['data'] == []
74
76
75 def test_regular_user_can_see_admin_interfaces_if_owner(
77 def test_regular_user_can_see_admin_interfaces_if_owner(
76 self, user_util, xhr_header):
78 self, user_util, xhr_header):
77 user = user_util.create_user(password='qweqwe')
79 user = user_util.create_user(password='qweqwe')
78 username = user.username
80 username = user.username
79
81
80 repo = user_util.create_repo(owner=username)
82 repo = user_util.create_repo(owner=username)
81 repo_name = repo.repo_name
83 repo_name = repo.repo_name
82
84
83 repo_group = user_util.create_repo_group(owner=username)
85 repo_group = user_util.create_repo_group(owner=username)
84 repo_group_name = repo_group.group_name
86 repo_group_name = repo_group.group_name
85
87
86 user_group = user_util.create_user_group(owner=username)
88 user_group = user_util.create_user_group(owner=username)
87 user_group_name = user_group.users_group_name
89 user_group_name = user_group.users_group_name
88
90
89 self.log_user(username, 'qweqwe')
91 self.log_user(username, 'qweqwe')
90 # check if in home view, such user doesn't see the "admin" menus
92 # check if in home view, such user doesn't see the "admin" menus
91 response = self.app.get(route_path('home'))
93 response = self.app.get(route_path('home'))
92
94
93 assert_response = response.assert_response()
95 assert_response = response.assert_response()
94
96
95 assert_response.one_element_exists('li.local-admin-repos')
97 assert_response.one_element_exists('li.local-admin-repos')
96 assert_response.one_element_exists('li.local-admin-repo-groups')
98 assert_response.one_element_exists('li.local-admin-repo-groups')
97 assert_response.one_element_exists('li.local-admin-user-groups')
99 assert_response.one_element_exists('li.local-admin-user-groups')
98
100
99 # admin interfaces have visible elements
101 # admin interfaces have visible elements
100 response = self.app.get(route_path('repos'), status=200)
102 response = self.app.get(route_path('repos'), status=200)
101 response.mustcontain('"name_raw": "{}"'.format(repo_name))
103 response.mustcontain('"name_raw": "{}"'.format(repo_name))
102
104
103 response = self.app.get(url('repo_groups'), status=200)
105 response = self.app.get(route_path('repo_groups'), status=200)
104 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
106 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
105
107
106 response = self.app.get(route_path('user_groups_data'),
108 response = self.app.get(route_path('user_groups_data'),
107 extra_environ=xhr_header, status=200)
109 extra_environ=xhr_header, status=200)
108 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
110 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
109
111
110 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
112 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
111 self, user_util, xhr_header):
113 self, user_util, xhr_header):
112 user = user_util.create_user(password='qweqwe')
114 user = user_util.create_user(password='qweqwe')
113 username = user.username
115 username = user.username
114
116
115 repo = user_util.create_repo()
117 repo = user_util.create_repo()
116 repo_name = repo.repo_name
118 repo_name = repo.repo_name
117
119
118 repo_group = user_util.create_repo_group()
120 repo_group = user_util.create_repo_group()
119 repo_group_name = repo_group.group_name
121 repo_group_name = repo_group.group_name
120
122
121 user_group = user_util.create_user_group()
123 user_group = user_util.create_user_group()
122 user_group_name = user_group.users_group_name
124 user_group_name = user_group.users_group_name
123
125
124 user_util.grant_user_permission_to_repo(
126 user_util.grant_user_permission_to_repo(
125 repo, user, 'repository.admin')
127 repo, user, 'repository.admin')
126 user_util.grant_user_permission_to_repo_group(
128 user_util.grant_user_permission_to_repo_group(
127 repo_group, user, 'group.admin')
129 repo_group, user, 'group.admin')
128 user_util.grant_user_permission_to_user_group(
130 user_util.grant_user_permission_to_user_group(
129 user_group, user, 'usergroup.admin')
131 user_group, user, 'usergroup.admin')
130
132
131 self.log_user(username, 'qweqwe')
133 self.log_user(username, 'qweqwe')
132 # check if in home view, such user doesn't see the "admin" menus
134 # check if in home view, such user doesn't see the "admin" menus
133 response = self.app.get(route_path('home'))
135 response = self.app.get(route_path('home'))
134
136
135 assert_response = response.assert_response()
137 assert_response = response.assert_response()
136
138
137 assert_response.one_element_exists('li.local-admin-repos')
139 assert_response.one_element_exists('li.local-admin-repos')
138 assert_response.one_element_exists('li.local-admin-repo-groups')
140 assert_response.one_element_exists('li.local-admin-repo-groups')
139 assert_response.one_element_exists('li.local-admin-user-groups')
141 assert_response.one_element_exists('li.local-admin-user-groups')
140
142
141 # admin interfaces have visible elements
143 # admin interfaces have visible elements
142 response = self.app.get(route_path('repos'), status=200)
144 response = self.app.get(route_path('repos'), status=200)
143 response.mustcontain('"name_raw": "{}"'.format(repo_name))
145 response.mustcontain('"name_raw": "{}"'.format(repo_name))
144
146
145 response = self.app.get(url('repo_groups'), status=200)
147 response = self.app.get(route_path('repo_groups'), status=200)
146 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
148 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
147
149
148 response = self.app.get(route_path('user_groups_data'),
150 response = self.app.get(route_path('user_groups_data'),
149 extra_environ=xhr_header, status=200)
151 extra_environ=xhr_header, status=200)
150 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
152 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
@@ -1,468 +1,450 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 json
21 import json
22 import multiprocessing
22 import multiprocessing
23 import os
23 import os
24
24
25 import mock
25 import mock
26 import py
26 import py
27 import pytest
27 import pytest
28
28
29 from rhodecode.lib import caching_query
29 from rhodecode.lib import caching_query
30 from rhodecode.lib import utils
30 from rhodecode.lib import utils
31 from rhodecode.lib.utils2 import md5
31 from rhodecode.lib.utils2 import md5
32 from rhodecode.model import settings
32 from rhodecode.model import settings
33 from rhodecode.model import db
33 from rhodecode.model import db
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
39 from rhodecode.tests.fixture import Fixture
39 from rhodecode.tests.fixture import Fixture
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
41
40
42
41
43 fixture = Fixture()
42 fixture = Fixture()
44
43
45
44
46 def extract_hooks(config):
45 def extract_hooks(config):
47 """Return a dictionary with the hook entries of the given config."""
46 """Return a dictionary with the hook entries of the given config."""
48 hooks = {}
47 hooks = {}
49 config_items = config.serialize()
48 config_items = config.serialize()
50 for section, name, value in config_items:
49 for section, name, value in config_items:
51 if section != 'hooks':
50 if section != 'hooks':
52 continue
51 continue
53 hooks[name] = value
52 hooks[name] = value
54
53
55 return hooks
54 return hooks
56
55
57
56
58 def disable_hooks(request, hooks):
57 def disable_hooks(request, hooks):
59 """Disables the given hooks from the UI settings."""
58 """Disables the given hooks from the UI settings."""
60 session = meta.Session()
59 session = meta.Session()
61
60
62 model = SettingsModel()
61 model = SettingsModel()
63 for hook_key in hooks:
62 for hook_key in hooks:
64 sett = model.get_ui_by_key(hook_key)
63 sett = model.get_ui_by_key(hook_key)
65 sett.ui_active = False
64 sett.ui_active = False
66 session.add(sett)
65 session.add(sett)
67
66
68 # Invalidate cache
67 # Invalidate cache
69 ui_settings = session.query(db.RhodeCodeUi).options(
68 ui_settings = session.query(db.RhodeCodeUi).options(
70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
69 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
71 ui_settings.invalidate()
70 ui_settings.invalidate()
72
71
73 ui_settings = session.query(db.RhodeCodeUi).options(
72 ui_settings = session.query(db.RhodeCodeUi).options(
74 caching_query.FromCache(
73 caching_query.FromCache(
75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
74 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
76 ui_settings.invalidate()
75 ui_settings.invalidate()
77
76
78 @request.addfinalizer
77 @request.addfinalizer
79 def rollback():
78 def rollback():
80 session.rollback()
79 session.rollback()
81
80
82
81
83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
82 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
84 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
83 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
85 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
84 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
85 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
86 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
87 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
89 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
88 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
90
89
91 HG_HOOKS = frozenset(
90 HG_HOOKS = frozenset(
92 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
91 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
93 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
92 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
94
93
95
94
96 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
95 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
97 ([], HG_HOOKS),
96 ([], HG_HOOKS),
98 (HG_HOOKS, []),
97 (HG_HOOKS, []),
99
98
100 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
99 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
101
100
102 # When a pull/push hook is disabled, its pre-pull/push counterpart should
101 # When a pull/push hook is disabled, its pre-pull/push counterpart should
103 # be disabled too.
102 # be disabled too.
104 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
103 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
105 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
104 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
106 HOOK_PUSH_KEY]),
105 HOOK_PUSH_KEY]),
107 ])
106 ])
108 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
107 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
109 expected_hooks):
108 expected_hooks):
110 disable_hooks(request, disabled_hooks)
109 disable_hooks(request, disabled_hooks)
111
110
112 config = utils.make_db_config()
111 config = utils.make_db_config()
113 hooks = extract_hooks(config)
112 hooks = extract_hooks(config)
114
113
115 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
114 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
116
115
117
116
118 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
117 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
119 ([], ['pull', 'push']),
118 ([], ['pull', 'push']),
120 ([HOOK_PUSH], ['pull']),
119 ([HOOK_PUSH], ['pull']),
121 ([HOOK_PULL], ['push']),
120 ([HOOK_PULL], ['push']),
122 ([HOOK_PULL, HOOK_PUSH], []),
121 ([HOOK_PULL, HOOK_PUSH], []),
123 ])
122 ])
124 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
123 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
125 hook_keys = (HOOK_PUSH, HOOK_PULL)
124 hook_keys = (HOOK_PUSH, HOOK_PULL)
126 ui_settings = [
125 ui_settings = [
127 ('hooks', key, 'some value', key not in disabled_hooks)
126 ('hooks', key, 'some value', key not in disabled_hooks)
128 for key in hook_keys]
127 for key in hook_keys]
129
128
130 result = utils.get_enabled_hook_classes(ui_settings)
129 result = utils.get_enabled_hook_classes(ui_settings)
131 assert sorted(result) == expected_hooks
130 assert sorted(result) == expected_hooks
132
131
133
132
134 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
133 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
135 _stub_git_repo(tmpdir.ensure('repo', dir=True))
134 _stub_git_repo(tmpdir.ensure('repo', dir=True))
136 repos = list(utils.get_filesystem_repos(str(tmpdir)))
135 repos = list(utils.get_filesystem_repos(str(tmpdir)))
137 assert repos == [('repo', ('git', tmpdir.join('repo')))]
136 assert repos == [('repo', ('git', tmpdir.join('repo')))]
138
137
139
138
140 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
139 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
141 tmpdir.ensure('not-a-repo', dir=True)
140 tmpdir.ensure('not-a-repo', dir=True)
142 repos = list(utils.get_filesystem_repos(str(tmpdir)))
141 repos = list(utils.get_filesystem_repos(str(tmpdir)))
143 assert repos == []
142 assert repos == []
144
143
145
144
146 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
145 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
147 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
146 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
148 repos = list(utils.get_filesystem_repos(str(tmpdir)))
147 repos = list(utils.get_filesystem_repos(str(tmpdir)))
149 assert repos == []
148 assert repos == []
150
149
151
150
152 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
151 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
153 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
152 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
154 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
153 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
155 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
154 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
156
155
157
156
158 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
157 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
159 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
158 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
160 repos = list(utils.get_filesystem_repos(str(tmpdir)))
159 repos = list(utils.get_filesystem_repos(str(tmpdir)))
161 assert repos == []
160 assert repos == []
162
161
163
162
164 def test_get_filesystem_repos_skips_files(tmpdir):
163 def test_get_filesystem_repos_skips_files(tmpdir):
165 tmpdir.ensure('test-file')
164 tmpdir.ensure('test-file')
166 repos = list(utils.get_filesystem_repos(str(tmpdir)))
165 repos = list(utils.get_filesystem_repos(str(tmpdir)))
167 assert repos == []
166 assert repos == []
168
167
169
168
170 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
169 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
171 removed_repo_name = 'rm__00000000_000000_000000__.stub'
170 removed_repo_name = 'rm__00000000_000000_000000__.stub'
172 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
171 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
173 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
172 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
174 repos = list(utils.get_filesystem_repos(str(tmpdir)))
173 repos = list(utils.get_filesystem_repos(str(tmpdir)))
175 assert repos == []
174 assert repos == []
176
175
177
176
178 def _stub_git_repo(repo_path):
177 def _stub_git_repo(repo_path):
179 """
178 """
180 Make `repo_path` look like a Git repository.
179 Make `repo_path` look like a Git repository.
181 """
180 """
182 repo_path.ensure('.git', dir=True)
181 repo_path.ensure('.git', dir=True)
183
182
184
183
185 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
184 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
186 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
185 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
187 tmpdir.ensure('test-file')
186 tmpdir.ensure('test-file')
188 dirpaths = utils._get_dirpaths(str_class(tmpdir))
187 dirpaths = utils._get_dirpaths(str_class(tmpdir))
189 assert dirpaths == ['test-file']
188 assert dirpaths == ['test-file']
190
189
191
190
192 def test_get_dirpaths_returns_all_paths_bytes(
191 def test_get_dirpaths_returns_all_paths_bytes(
193 tmpdir, platform_encodes_filenames):
192 tmpdir, platform_encodes_filenames):
194 if platform_encodes_filenames:
193 if platform_encodes_filenames:
195 pytest.skip("This platform seems to encode filenames.")
194 pytest.skip("This platform seems to encode filenames.")
196 tmpdir.ensure('repo-a-umlaut-\xe4')
195 tmpdir.ensure('repo-a-umlaut-\xe4')
197 dirpaths = utils._get_dirpaths(str(tmpdir))
196 dirpaths = utils._get_dirpaths(str(tmpdir))
198 assert dirpaths == ['repo-a-umlaut-\xe4']
197 assert dirpaths == ['repo-a-umlaut-\xe4']
199
198
200
199
201 def test_get_dirpaths_skips_paths_it_cannot_decode(
200 def test_get_dirpaths_skips_paths_it_cannot_decode(
202 tmpdir, platform_encodes_filenames):
201 tmpdir, platform_encodes_filenames):
203 if platform_encodes_filenames:
202 if platform_encodes_filenames:
204 pytest.skip("This platform seems to encode filenames.")
203 pytest.skip("This platform seems to encode filenames.")
205 path_with_latin1 = 'repo-a-umlaut-\xe4'
204 path_with_latin1 = 'repo-a-umlaut-\xe4'
206 tmpdir.ensure(path_with_latin1)
205 tmpdir.ensure(path_with_latin1)
207 dirpaths = utils._get_dirpaths(unicode(tmpdir))
206 dirpaths = utils._get_dirpaths(unicode(tmpdir))
208 assert dirpaths == []
207 assert dirpaths == []
209
208
210
209
211 @pytest.fixture(scope='session')
210 @pytest.fixture(scope='session')
212 def platform_encodes_filenames():
211 def platform_encodes_filenames():
213 """
212 """
214 Boolean indicator if the current platform changes filename encodings.
213 Boolean indicator if the current platform changes filename encodings.
215 """
214 """
216 path_with_latin1 = 'repo-a-umlaut-\xe4'
215 path_with_latin1 = 'repo-a-umlaut-\xe4'
217 tmpdir = py.path.local.mkdtemp()
216 tmpdir = py.path.local.mkdtemp()
218 tmpdir.ensure(path_with_latin1)
217 tmpdir.ensure(path_with_latin1)
219 read_path = tmpdir.listdir()[0].basename
218 read_path = tmpdir.listdir()[0].basename
220 tmpdir.remove()
219 tmpdir.remove()
221 return path_with_latin1 != read_path
220 return path_with_latin1 != read_path
222
221
223
222
224 @pytest.fixture
225 def repo_groups(request):
226 session = meta.Session()
227 zombie_group = fixture.create_repo_group('zombie')
228 parent_group = fixture.create_repo_group('parent')
229 child_group = fixture.create_repo_group('parent/child')
230 groups_in_db = session.query(db.RepoGroup).all()
231 assert len(groups_in_db) == 3
232 assert child_group.group_parent_id == parent_group.group_id
233
234 @request.addfinalizer
235 def cleanup():
236 fixture.destroy_repo_group(zombie_group)
237 fixture.destroy_repo_group(child_group)
238 fixture.destroy_repo_group(parent_group)
239
240 return (zombie_group, parent_group, child_group)
241
223
242
224
243 def test_repo2db_mapper_groups(repo_groups):
225 def test_repo2db_mapper_groups(repo_groups):
244 session = meta.Session()
226 session = meta.Session()
245 zombie_group, parent_group, child_group = repo_groups
227 zombie_group, parent_group, child_group = repo_groups
246 zombie_path = os.path.join(
228 zombie_path = os.path.join(
247 RepoGroupModel().repos_path, zombie_group.full_path)
229 RepoGroupModel().repos_path, zombie_group.full_path)
248 os.rmdir(zombie_path)
230 os.rmdir(zombie_path)
249
231
250 # Avoid removing test repos when calling repo2db_mapper
232 # Avoid removing test repos when calling repo2db_mapper
251 repo_list = {
233 repo_list = {
252 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
234 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
253 }
235 }
254 utils.repo2db_mapper(repo_list, remove_obsolete=True)
236 utils.repo2db_mapper(repo_list, remove_obsolete=True)
255
237
256 groups_in_db = session.query(db.RepoGroup).all()
238 groups_in_db = session.query(db.RepoGroup).all()
257 assert child_group in groups_in_db
239 assert child_group in groups_in_db
258 assert parent_group in groups_in_db
240 assert parent_group in groups_in_db
259 assert zombie_path not in groups_in_db
241 assert zombie_path not in groups_in_db
260
242
261
243
262 def test_repo2db_mapper_enables_largefiles(backend):
244 def test_repo2db_mapper_enables_largefiles(backend):
263 repo = backend.create_repo()
245 repo = backend.create_repo()
264 repo_list = {repo.repo_name: 'test'}
246 repo_list = {repo.repo_name: 'test'}
265 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
247 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
266 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
248 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
267 install_git_hook=mock.DEFAULT,
249 install_git_hook=mock.DEFAULT,
268 install_svn_hooks=mock.DEFAULT):
250 install_svn_hooks=mock.DEFAULT):
269 utils.repo2db_mapper(repo_list, remove_obsolete=False)
251 utils.repo2db_mapper(repo_list, remove_obsolete=False)
270 _, kwargs = scm_mock.call_args
252 _, kwargs = scm_mock.call_args
271 assert kwargs['config'].get('extensions', 'largefiles') == ''
253 assert kwargs['config'].get('extensions', 'largefiles') == ''
272
254
273
255
274 @pytest.mark.backends("git", "svn")
256 @pytest.mark.backends("git", "svn")
275 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
257 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
276 repo = backend.create_repo()
258 repo = backend.create_repo()
277 repo_list = {repo.repo_name: 'test'}
259 repo_list = {repo.repo_name: 'test'}
278 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
260 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
279 utils.repo2db_mapper(repo_list, remove_obsolete=False)
261 utils.repo2db_mapper(repo_list, remove_obsolete=False)
280 install_hooks_mock.assert_called_once_with(
262 install_hooks_mock.assert_called_once_with(
281 repo.scm_instance(), repo_type=backend.alias)
263 repo.scm_instance(), repo_type=backend.alias)
282
264
283
265
284 @pytest.mark.backends("git", "svn")
266 @pytest.mark.backends("git", "svn")
285 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
267 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
286 repo = backend.create_repo()
268 repo = backend.create_repo()
287 RepoModel().delete(repo, fs_remove=False)
269 RepoModel().delete(repo, fs_remove=False)
288 meta.Session().commit()
270 meta.Session().commit()
289 repo_list = {repo.repo_name: repo.scm_instance()}
271 repo_list = {repo.repo_name: repo.scm_instance()}
290 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
272 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
291 utils.repo2db_mapper(repo_list, remove_obsolete=False)
273 utils.repo2db_mapper(repo_list, remove_obsolete=False)
292 assert install_hooks_mock.call_count == 1
274 assert install_hooks_mock.call_count == 1
293 install_hooks_args, _ = install_hooks_mock.call_args
275 install_hooks_args, _ = install_hooks_mock.call_args
294 assert install_hooks_args[0].name == repo.repo_name
276 assert install_hooks_args[0].name == repo.repo_name
295
277
296
278
297 class TestPasswordChanged(object):
279 class TestPasswordChanged(object):
298 def setup(self):
280 def setup(self):
299 self.session = {
281 self.session = {
300 'rhodecode_user': {
282 'rhodecode_user': {
301 'password': '0cc175b9c0f1b6a831c399e269772661'
283 'password': '0cc175b9c0f1b6a831c399e269772661'
302 }
284 }
303 }
285 }
304 self.auth_user = mock.Mock()
286 self.auth_user = mock.Mock()
305 self.auth_user.userame = 'test'
287 self.auth_user.userame = 'test'
306 self.auth_user.password = 'abc123'
288 self.auth_user.password = 'abc123'
307
289
308 def test_returns_false_for_default_user(self):
290 def test_returns_false_for_default_user(self):
309 self.auth_user.username = db.User.DEFAULT_USER
291 self.auth_user.username = db.User.DEFAULT_USER
310 result = utils.password_changed(self.auth_user, self.session)
292 result = utils.password_changed(self.auth_user, self.session)
311 assert result is False
293 assert result is False
312
294
313 def test_returns_false_if_password_was_not_changed(self):
295 def test_returns_false_if_password_was_not_changed(self):
314 self.session['rhodecode_user']['password'] = md5(
296 self.session['rhodecode_user']['password'] = md5(
315 self.auth_user.password)
297 self.auth_user.password)
316 result = utils.password_changed(self.auth_user, self.session)
298 result = utils.password_changed(self.auth_user, self.session)
317 assert result is False
299 assert result is False
318
300
319 def test_returns_true_if_password_was_changed(self):
301 def test_returns_true_if_password_was_changed(self):
320 result = utils.password_changed(self.auth_user, self.session)
302 result = utils.password_changed(self.auth_user, self.session)
321 assert result is True
303 assert result is True
322
304
323 def test_returns_true_if_auth_user_password_is_empty(self):
305 def test_returns_true_if_auth_user_password_is_empty(self):
324 self.auth_user.password = None
306 self.auth_user.password = None
325 result = utils.password_changed(self.auth_user, self.session)
307 result = utils.password_changed(self.auth_user, self.session)
326 assert result is True
308 assert result is True
327
309
328 def test_returns_true_if_session_password_is_empty(self):
310 def test_returns_true_if_session_password_is_empty(self):
329 self.session['rhodecode_user'].pop('password')
311 self.session['rhodecode_user'].pop('password')
330 result = utils.password_changed(self.auth_user, self.session)
312 result = utils.password_changed(self.auth_user, self.session)
331 assert result is True
313 assert result is True
332
314
333
315
334 class TestReadOpensourceLicenses(object):
316 class TestReadOpensourceLicenses(object):
335 def test_success(self):
317 def test_success(self):
336 utils._license_cache = None
318 utils._license_cache = None
337 json_data = '''
319 json_data = '''
338 {
320 {
339 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
321 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
340 "python2.7-Markdown-2.6.2": {
322 "python2.7-Markdown-2.6.2": {
341 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
323 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
342 }
324 }
343 }
325 }
344 '''
326 '''
345 resource_string_patch = mock.patch.object(
327 resource_string_patch = mock.patch.object(
346 utils.pkg_resources, 'resource_string', return_value=json_data)
328 utils.pkg_resources, 'resource_string', return_value=json_data)
347 with resource_string_patch:
329 with resource_string_patch:
348 result = utils.read_opensource_licenses()
330 result = utils.read_opensource_licenses()
349 assert result == json.loads(json_data)
331 assert result == json.loads(json_data)
350
332
351 def test_caching(self):
333 def test_caching(self):
352 utils._license_cache = {
334 utils._license_cache = {
353 "python2.7-pytest-2.7.1": {
335 "python2.7-pytest-2.7.1": {
354 "UNKNOWN": None
336 "UNKNOWN": None
355 },
337 },
356 "python2.7-Markdown-2.6.2": {
338 "python2.7-Markdown-2.6.2": {
357 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
339 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
358 }
340 }
359 }
341 }
360 resource_patch = mock.patch.object(
342 resource_patch = mock.patch.object(
361 utils.pkg_resources, 'resource_string', side_effect=Exception)
343 utils.pkg_resources, 'resource_string', side_effect=Exception)
362 json_patch = mock.patch.object(
344 json_patch = mock.patch.object(
363 utils.json, 'loads', side_effect=Exception)
345 utils.json, 'loads', side_effect=Exception)
364
346
365 with resource_patch as resource_mock, json_patch as json_mock:
347 with resource_patch as resource_mock, json_patch as json_mock:
366 result = utils.read_opensource_licenses()
348 result = utils.read_opensource_licenses()
367
349
368 assert resource_mock.call_count == 0
350 assert resource_mock.call_count == 0
369 assert json_mock.call_count == 0
351 assert json_mock.call_count == 0
370 assert result == utils._license_cache
352 assert result == utils._license_cache
371
353
372 def test_licenses_file_contains_no_unknown_licenses(self):
354 def test_licenses_file_contains_no_unknown_licenses(self):
373 utils._license_cache = None
355 utils._license_cache = None
374 result = utils.read_opensource_licenses()
356 result = utils.read_opensource_licenses()
375 license_names = []
357 license_names = []
376 for licenses in result.values():
358 for licenses in result.values():
377 license_names.extend(licenses.keys())
359 license_names.extend(licenses.keys())
378 assert 'UNKNOWN' not in license_names
360 assert 'UNKNOWN' not in license_names
379
361
380
362
381 class TestMakeDbConfig(object):
363 class TestMakeDbConfig(object):
382 def test_data_from_config_data_from_db_returned(self):
364 def test_data_from_config_data_from_db_returned(self):
383 test_data = [
365 test_data = [
384 ('section1', 'option1', 'value1'),
366 ('section1', 'option1', 'value1'),
385 ('section2', 'option2', 'value2'),
367 ('section2', 'option2', 'value2'),
386 ('section3', 'option3', 'value3'),
368 ('section3', 'option3', 'value3'),
387 ]
369 ]
388 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
370 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
389 config_mock.return_value = test_data
371 config_mock.return_value = test_data
390 kwargs = {'clear_session': False, 'repo': 'test_repo'}
372 kwargs = {'clear_session': False, 'repo': 'test_repo'}
391 result = utils.make_db_config(**kwargs)
373 result = utils.make_db_config(**kwargs)
392 config_mock.assert_called_once_with(**kwargs)
374 config_mock.assert_called_once_with(**kwargs)
393 for section, option, expected_value in test_data:
375 for section, option, expected_value in test_data:
394 value = result.get(section, option)
376 value = result.get(section, option)
395 assert value == expected_value
377 assert value == expected_value
396
378
397
379
398 class TestConfigDataFromDb(object):
380 class TestConfigDataFromDb(object):
399 def test_config_data_from_db_returns_active_settings(self):
381 def test_config_data_from_db_returns_active_settings(self):
400 test_data = [
382 test_data = [
401 UiSetting('section1', 'option1', 'value1', True),
383 UiSetting('section1', 'option1', 'value1', True),
402 UiSetting('section2', 'option2', 'value2', True),
384 UiSetting('section2', 'option2', 'value2', True),
403 UiSetting('section3', 'option3', 'value3', False),
385 UiSetting('section3', 'option3', 'value3', False),
404 ]
386 ]
405 repo_name = 'test_repo'
387 repo_name = 'test_repo'
406
388
407 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
389 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
408 hooks_patch = mock.patch.object(
390 hooks_patch = mock.patch.object(
409 utils, 'get_enabled_hook_classes',
391 utils, 'get_enabled_hook_classes',
410 return_value=['pull', 'push', 'repo_size'])
392 return_value=['pull', 'push', 'repo_size'])
411 with model_patch as model_mock, hooks_patch:
393 with model_patch as model_mock, hooks_patch:
412 instance_mock = mock.Mock()
394 instance_mock = mock.Mock()
413 model_mock.return_value = instance_mock
395 model_mock.return_value = instance_mock
414 instance_mock.get_ui_settings.return_value = test_data
396 instance_mock.get_ui_settings.return_value = test_data
415 result = utils.config_data_from_db(
397 result = utils.config_data_from_db(
416 clear_session=False, repo=repo_name)
398 clear_session=False, repo=repo_name)
417
399
418 self._assert_repo_name_passed(model_mock, repo_name)
400 self._assert_repo_name_passed(model_mock, repo_name)
419
401
420 expected_result = [
402 expected_result = [
421 ('section1', 'option1', 'value1'),
403 ('section1', 'option1', 'value1'),
422 ('section2', 'option2', 'value2'),
404 ('section2', 'option2', 'value2'),
423 ]
405 ]
424 assert result == expected_result
406 assert result == expected_result
425
407
426 def _assert_repo_name_passed(self, model_mock, repo_name):
408 def _assert_repo_name_passed(self, model_mock, repo_name):
427 assert model_mock.call_count == 1
409 assert model_mock.call_count == 1
428 call_args, call_kwargs = model_mock.call_args
410 call_args, call_kwargs = model_mock.call_args
429 assert call_kwargs['repo'] == repo_name
411 assert call_kwargs['repo'] == repo_name
430
412
431
413
432 class TestIsDirWritable(object):
414 class TestIsDirWritable(object):
433 def test_returns_false_when_not_writable(self):
415 def test_returns_false_when_not_writable(self):
434 with mock.patch('__builtin__.open', side_effect=OSError):
416 with mock.patch('__builtin__.open', side_effect=OSError):
435 assert not utils._is_dir_writable('/stub-path')
417 assert not utils._is_dir_writable('/stub-path')
436
418
437 def test_returns_true_when_writable(self, tmpdir):
419 def test_returns_true_when_writable(self, tmpdir):
438 assert utils._is_dir_writable(str(tmpdir))
420 assert utils._is_dir_writable(str(tmpdir))
439
421
440 def test_is_safe_against_race_conditions(self, tmpdir):
422 def test_is_safe_against_race_conditions(self, tmpdir):
441 workers = multiprocessing.Pool()
423 workers = multiprocessing.Pool()
442 directories = [str(tmpdir)] * 10
424 directories = [str(tmpdir)] * 10
443 workers.map(utils._is_dir_writable, directories)
425 workers.map(utils._is_dir_writable, directories)
444
426
445
427
446 class TestGetEnabledHooks(object):
428 class TestGetEnabledHooks(object):
447 def test_only_active_hooks_are_enabled(self):
429 def test_only_active_hooks_are_enabled(self):
448 ui_settings = [
430 ui_settings = [
449 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
431 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
450 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
432 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
451 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
433 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
452 ]
434 ]
453 result = utils.get_enabled_hook_classes(ui_settings)
435 result = utils.get_enabled_hook_classes(ui_settings)
454 assert result == ['push', 'repo_size']
436 assert result == ['push', 'repo_size']
455
437
456 def test_all_hooks_are_enabled(self):
438 def test_all_hooks_are_enabled(self):
457 ui_settings = [
439 ui_settings = [
458 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
440 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
459 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
441 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
460 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
442 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
461 ]
443 ]
462 result = utils.get_enabled_hook_classes(ui_settings)
444 result = utils.get_enabled_hook_classes(ui_settings)
463 assert result == ['push', 'repo_size', 'pull']
445 assert result == ['push', 'repo_size', 'pull']
464
446
465 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
447 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
466 ui_settings = []
448 ui_settings = []
467 result = utils.get_enabled_hook_classes(ui_settings)
449 result = utils.get_enabled_hook_classes(ui_settings)
468 assert result == []
450 assert result == []
@@ -1,223 +1,223 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 os
21 import os
22
22
23 from sqlalchemy.exc import IntegrityError
23 from sqlalchemy.exc import IntegrityError
24 import pytest
24 import pytest
25
25
26 from rhodecode.tests import TESTS_TMP_PATH
26 from rhodecode.tests import TESTS_TMP_PATH
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.model.repo_group import RepoGroupModel
30 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.db import RepoGroup
31 from rhodecode.model.db import RepoGroup
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 def _update_group(id_, group_name, desc='desc', parent_id=None):
38 def _update_group(id_, group_name, desc='desc', parent_id=None):
39 form_data = fixture._get_group_create_params(group_name=group_name,
39 form_data = fixture._get_group_create_params(group_name=group_name,
40 group_desc=desc,
40 group_desc=desc,
41 group_parent_id=parent_id)
41 group_parent_id=parent_id)
42 gr = RepoGroupModel().update(id_, form_data)
42 gr = RepoGroupModel().update(id_, form_data)
43 return gr
43 return gr
44
44
45
45
46 def _update_repo(name, **kwargs):
46 def _update_repo(name, **kwargs):
47 form_data = fixture._get_repo_create_params(**kwargs)
47 form_data = fixture._get_repo_create_params(**kwargs)
48 if 'repo_name' not in kwargs:
48 if 'repo_name' not in kwargs:
49 form_data['repo_name'] = name
49 form_data['repo_name'] = name
50
50
51 if 'perm_additions' not in kwargs:
51 if 'perm_additions' not in kwargs:
52 form_data['perm_additions'] = []
52 form_data['perm_additions'] = []
53 if 'perm_updates' not in kwargs:
53 if 'perm_updates' not in kwargs:
54 form_data['perm_updates'] = []
54 form_data['perm_updates'] = []
55 if 'perm_deletions' not in kwargs:
55 if 'perm_deletions' not in kwargs:
56 form_data['perm_deletions'] = []
56 form_data['perm_deletions'] = []
57
57
58 r = RepoModel().update(name, **form_data)
58 r = RepoModel().update(name, **form_data)
59 return r
59 return r
60
60
61
61
62 class TestRepoGroups:
62 class TestRepoGroups(object):
63
63
64 @pytest.fixture(autouse=True)
64 @pytest.fixture(autouse=True)
65 def prepare(self, request):
65 def prepare(self, request):
66 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
66 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
67 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
67 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
68 self.g3 = fixture.create_repo_group('test3', skip_if_exists=True)
68 self.g3 = fixture.create_repo_group('test3', skip_if_exists=True)
69 request.addfinalizer(Session.remove)
69 request.addfinalizer(Session.remove)
70
70
71 def __check_path(self, *path):
71 def __check_path(self, *path):
72 """
72 """
73 Checks the path for existance !
73 Checks the path for existance !
74 """
74 """
75 path = [TESTS_TMP_PATH] + list(path)
75 path = [TESTS_TMP_PATH] + list(path)
76 path = os.path.join(*path)
76 path = os.path.join(*path)
77 return os.path.isdir(path)
77 return os.path.isdir(path)
78
78
79 def __delete_group(self, id_):
79 def __delete_group(self, id_):
80 RepoGroupModel().delete(id_)
80 RepoGroupModel().delete(id_)
81
81
82 def test_create_group(self):
82 def test_create_group(self):
83 g = fixture.create_repo_group('newGroup')
83 g = fixture.create_repo_group('newGroup')
84 Session().commit()
84 Session().commit()
85 assert g.full_path == 'newGroup'
85 assert g.full_path == 'newGroup'
86
86
87 assert self.__check_path('newGroup')
87 assert self.__check_path('newGroup')
88
88
89 def test_create_same_name_group(self):
89 def test_create_same_name_group(self):
90 with pytest.raises(IntegrityError):
90 with pytest.raises(IntegrityError):
91 fixture.create_repo_group('newGroup')
91 fixture.create_repo_group('newGroup')
92 Session().rollback()
92 Session().rollback()
93
93
94 def test_same_subgroup(self):
94 def test_same_subgroup(self):
95 sg1 = fixture.create_repo_group('test1/sub1')
95 sg1 = fixture.create_repo_group('test1/sub1')
96 assert sg1.parent_group == self.g1
96 assert sg1.parent_group == self.g1
97 assert sg1.full_path == 'test1/sub1'
97 assert sg1.full_path == 'test1/sub1'
98 assert self.__check_path('test1', 'sub1')
98 assert self.__check_path('test1', 'sub1')
99
99
100 ssg1 = fixture.create_repo_group('test1/sub1/subsub1')
100 ssg1 = fixture.create_repo_group('test1/sub1/subsub1')
101 assert ssg1.parent_group == sg1
101 assert ssg1.parent_group == sg1
102 assert ssg1.full_path == 'test1/sub1/subsub1'
102 assert ssg1.full_path == 'test1/sub1/subsub1'
103 assert self.__check_path('test1', 'sub1', 'subsub1')
103 assert self.__check_path('test1', 'sub1', 'subsub1')
104
104
105 def test_remove_group(self):
105 def test_remove_group(self):
106 sg1 = fixture.create_repo_group('deleteme')
106 sg1 = fixture.create_repo_group('deleteme')
107 self.__delete_group(sg1.group_id)
107 self.__delete_group(sg1.group_id)
108
108
109 assert RepoGroup.get(sg1.group_id) is None
109 assert RepoGroup.get(sg1.group_id) is None
110 assert not self.__check_path('deteteme')
110 assert not self.__check_path('deteteme')
111
111
112 sg1 = fixture.create_repo_group('test1/deleteme')
112 sg1 = fixture.create_repo_group('test1/deleteme')
113 self.__delete_group(sg1.group_id)
113 self.__delete_group(sg1.group_id)
114
114
115 assert RepoGroup.get(sg1.group_id) is None
115 assert RepoGroup.get(sg1.group_id) is None
116 assert not self.__check_path('test1', 'deteteme')
116 assert not self.__check_path('test1', 'deteteme')
117
117
118 def test_rename_single_group(self):
118 def test_rename_single_group(self):
119 sg1 = fixture.create_repo_group('initial')
119 sg1 = fixture.create_repo_group('initial')
120
120
121 _update_group(sg1.group_id, 'after')
121 _update_group(sg1.group_id, 'after')
122 assert self.__check_path('after')
122 assert self.__check_path('after')
123 assert RepoGroup.get_by_group_name('initial') is None
123 assert RepoGroup.get_by_group_name('initial') is None
124
124
125 def test_update_group_parent(self):
125 def test_update_group_parent(self):
126
126
127 sg1 = fixture.create_repo_group('test1/initial')
127 sg1 = fixture.create_repo_group('test1/initial')
128
128
129 _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
129 _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
130 assert self.__check_path('test1', 'after')
130 assert self.__check_path('test1', 'after')
131 assert RepoGroup.get_by_group_name('test1/initial') is None
131 assert RepoGroup.get_by_group_name('test1/initial') is None
132
132
133 _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
133 _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
134 assert self.__check_path('test3', 'after')
134 assert self.__check_path('test3', 'after')
135 assert RepoGroup.get_by_group_name('test3/initial') is None
135 assert RepoGroup.get_by_group_name('test3/initial') is None
136
136
137 new_sg1 = _update_group(sg1.group_id, 'hello')
137 new_sg1 = _update_group(sg1.group_id, 'hello')
138 assert self.__check_path('hello')
138 assert self.__check_path('hello')
139
139
140 assert RepoGroup.get_by_group_name('hello') == new_sg1
140 assert RepoGroup.get_by_group_name('hello') == new_sg1
141
141
142 def test_subgrouping_with_repo(self):
142 def test_subgrouping_with_repo(self):
143
143
144 g1 = fixture.create_repo_group('g1')
144 g1 = fixture.create_repo_group('g1')
145 g2 = fixture.create_repo_group('g2')
145 g2 = fixture.create_repo_group('g2')
146 # create new repo
146 # create new repo
147 r = fixture.create_repo('john')
147 r = fixture.create_repo('john')
148
148
149 assert r.repo_name == 'john'
149 assert r.repo_name == 'john'
150 # put repo into group
150 # put repo into group
151 r = _update_repo('john', repo_group=g1.group_id)
151 r = _update_repo('john', repo_group=g1.group_id)
152 Session().commit()
152 Session().commit()
153 assert r.repo_name == 'g1/john'
153 assert r.repo_name == 'g1/john'
154
154
155 _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
155 _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
156 assert self.__check_path('g2', 'g1')
156 assert self.__check_path('g2', 'g1')
157
157
158 # test repo
158 # test repo
159 assert r.repo_name == RepoGroup.url_sep().join(
159 assert r.repo_name == RepoGroup.url_sep().join(
160 ['g2', 'g1', r.just_name])
160 ['g2', 'g1', r.just_name])
161
161
162 def test_move_to_root(self):
162 def test_move_to_root(self):
163 fixture.create_repo_group('t11')
163 fixture.create_repo_group('t11')
164 g2 = fixture.create_repo_group('t11/t22')
164 g2 = fixture.create_repo_group('t11/t22')
165
165
166 assert g2.full_path == 't11/t22'
166 assert g2.full_path == 't11/t22'
167 assert self.__check_path('t11', 't22')
167 assert self.__check_path('t11', 't22')
168
168
169 g2 = _update_group(g2.group_id, 'g22', parent_id=None)
169 g2 = _update_group(g2.group_id, 'g22', parent_id=None)
170 Session().commit()
170 Session().commit()
171
171
172 assert g2.group_name == 'g22'
172 assert g2.group_name == 'g22'
173 # we moved out group from t1 to '' so it's full path should be 'g2'
173 # we moved out group from t1 to '' so it's full path should be 'g2'
174 assert g2.full_path == 'g22'
174 assert g2.full_path == 'g22'
175 assert not self.__check_path('t11', 't22')
175 assert not self.__check_path('t11', 't22')
176 assert self.__check_path('g22')
176 assert self.__check_path('g22')
177
177
178 def test_rename_top_level_group_in_nested_setup(self):
178 def test_rename_top_level_group_in_nested_setup(self):
179 g1 = fixture.create_repo_group('L1')
179 g1 = fixture.create_repo_group('L1')
180 g2 = fixture.create_repo_group('L1/L2')
180 g2 = fixture.create_repo_group('L1/L2')
181 g3 = fixture.create_repo_group('L1/L2/L3')
181 g3 = fixture.create_repo_group('L1/L2/L3')
182
182
183 r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
183 r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
184
184
185 # rename L1 all groups should be now changed
185 # rename L1 all groups should be now changed
186 _update_group(g1.group_id, 'L1_NEW')
186 _update_group(g1.group_id, 'L1_NEW')
187 Session().commit()
187 Session().commit()
188 assert g1.full_path == 'L1_NEW'
188 assert g1.full_path == 'L1_NEW'
189 assert g2.full_path == 'L1_NEW/L2'
189 assert g2.full_path == 'L1_NEW/L2'
190 assert g3.full_path == 'L1_NEW/L2/L3'
190 assert g3.full_path == 'L1_NEW/L2/L3'
191 assert r.repo_name == 'L1_NEW/L2/L3/L3_REPO'
191 assert r.repo_name == 'L1_NEW/L2/L3/L3_REPO'
192
192
193 def test_change_parent_of_top_level_group_in_nested_setup(self):
193 def test_change_parent_of_top_level_group_in_nested_setup(self):
194 g1 = fixture.create_repo_group('R1')
194 g1 = fixture.create_repo_group('R1')
195 g2 = fixture.create_repo_group('R1/R2')
195 g2 = fixture.create_repo_group('R1/R2')
196 g3 = fixture.create_repo_group('R1/R2/R3')
196 g3 = fixture.create_repo_group('R1/R2/R3')
197 g4 = fixture.create_repo_group('R1_NEW')
197 g4 = fixture.create_repo_group('R1_NEW')
198
198
199 r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
199 r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
200 # rename L1 all groups should be now changed
200 # rename L1 all groups should be now changed
201 _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
201 _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
202 Session().commit()
202 Session().commit()
203 assert g1.full_path == 'R1_NEW/R1'
203 assert g1.full_path == 'R1_NEW/R1'
204 assert g2.full_path == 'R1_NEW/R1/R2'
204 assert g2.full_path == 'R1_NEW/R1/R2'
205 assert g3.full_path == 'R1_NEW/R1/R2/R3'
205 assert g3.full_path == 'R1_NEW/R1/R2/R3'
206 assert r.repo_name == 'R1_NEW/R1/R2/R3/R3_REPO'
206 assert r.repo_name == 'R1_NEW/R1/R2/R3/R3_REPO'
207
207
208 def test_change_parent_of_top_level_group_in_nested_setup_with_rename(
208 def test_change_parent_of_top_level_group_in_nested_setup_with_rename(
209 self):
209 self):
210 g1 = fixture.create_repo_group('X1')
210 g1 = fixture.create_repo_group('X1')
211 g2 = fixture.create_repo_group('X1/X2')
211 g2 = fixture.create_repo_group('X1/X2')
212 g3 = fixture.create_repo_group('X1/X2/X3')
212 g3 = fixture.create_repo_group('X1/X2/X3')
213 g4 = fixture.create_repo_group('X1_NEW')
213 g4 = fixture.create_repo_group('X1_NEW')
214
214
215 r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
215 r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
216
216
217 # rename L1 all groups should be now changed
217 # rename L1 all groups should be now changed
218 _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
218 _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
219 Session().commit()
219 Session().commit()
220 assert g1.full_path == 'X1_NEW/X1_PRIM'
220 assert g1.full_path == 'X1_NEW/X1_PRIM'
221 assert g2.full_path == 'X1_NEW/X1_PRIM/X2'
221 assert g2.full_path == 'X1_NEW/X1_PRIM/X2'
222 assert g3.full_path == 'X1_NEW/X1_PRIM/X2/X3'
222 assert g3.full_path == 'X1_NEW/X1_PRIM/X2/X3'
223 assert r.repo_name == 'X1_NEW/X1_PRIM/X2/X3/X3_REPO'
223 assert r.repo_name == 'X1_NEW/X1_PRIM/X2/X3/X3_REPO'
@@ -1,1832 +1,1858 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 collections
21 import collections
22 import datetime
22 import datetime
23 import hashlib
23 import hashlib
24 import os
24 import os
25 import re
25 import re
26 import pprint
26 import pprint
27 import shutil
27 import shutil
28 import socket
28 import socket
29 import subprocess32
29 import subprocess32
30 import time
30 import time
31 import uuid
31 import uuid
32 import dateutil.tz
32 import dateutil.tz
33
33
34 import mock
34 import mock
35 import pyramid.testing
35 import pyramid.testing
36 import pytest
36 import pytest
37 import colander
37 import colander
38 import requests
38 import requests
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.lib.utils2 import AttributeDict
41 from rhodecode.lib.utils2 import AttributeDict
42 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.changeset_status import ChangesetStatusModel
43 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.user_group import UserGroupModel
54 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.model.integration import IntegrationModel
55 from rhodecode.integrations import integration_type_registry
55 from rhodecode.integrations import integration_type_registry
56 from rhodecode.integrations.types.base import IntegrationTypeBase
56 from rhodecode.integrations.types.base import IntegrationTypeBase
57 from rhodecode.lib.utils import repo2db_mapper
57 from rhodecode.lib.utils import repo2db_mapper
58 from rhodecode.lib.vcs import create_vcsserver_proxy
58 from rhodecode.lib.vcs import create_vcsserver_proxy
59 from rhodecode.lib.vcs.backends import get_backend
59 from rhodecode.lib.vcs.backends import get_backend
60 from rhodecode.lib.vcs.nodes import FileNode
60 from rhodecode.lib.vcs.nodes import FileNode
61 from rhodecode.tests import (
61 from rhodecode.tests import (
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 TEST_USER_REGULAR_PASS)
64 TEST_USER_REGULAR_PASS)
65 from rhodecode.tests.utils import CustomTestApp, set_anonymous_access, add_test_routes
65 from rhodecode.tests.utils import CustomTestApp, set_anonymous_access, add_test_routes
66 from rhodecode.tests.fixture import Fixture
66 from rhodecode.tests.fixture import Fixture
67
67
68
68
69 def _split_comma(value):
69 def _split_comma(value):
70 return value.split(',')
70 return value.split(',')
71
71
72
72
73 def pytest_addoption(parser):
73 def pytest_addoption(parser):
74 parser.addoption(
74 parser.addoption(
75 '--keep-tmp-path', action='store_true',
75 '--keep-tmp-path', action='store_true',
76 help="Keep the test temporary directories")
76 help="Keep the test temporary directories")
77 parser.addoption(
77 parser.addoption(
78 '--backends', action='store', type=_split_comma,
78 '--backends', action='store', type=_split_comma,
79 default=['git', 'hg', 'svn'],
79 default=['git', 'hg', 'svn'],
80 help="Select which backends to test for backend specific tests.")
80 help="Select which backends to test for backend specific tests.")
81 parser.addoption(
81 parser.addoption(
82 '--dbs', action='store', type=_split_comma,
82 '--dbs', action='store', type=_split_comma,
83 default=['sqlite'],
83 default=['sqlite'],
84 help="Select which database to test for database specific tests. "
84 help="Select which database to test for database specific tests. "
85 "Possible options are sqlite,postgres,mysql")
85 "Possible options are sqlite,postgres,mysql")
86 parser.addoption(
86 parser.addoption(
87 '--appenlight', '--ae', action='store_true',
87 '--appenlight', '--ae', action='store_true',
88 help="Track statistics in appenlight.")
88 help="Track statistics in appenlight.")
89 parser.addoption(
89 parser.addoption(
90 '--appenlight-api-key', '--ae-key',
90 '--appenlight-api-key', '--ae-key',
91 help="API key for Appenlight.")
91 help="API key for Appenlight.")
92 parser.addoption(
92 parser.addoption(
93 '--appenlight-url', '--ae-url',
93 '--appenlight-url', '--ae-url',
94 default="https://ae.rhodecode.com",
94 default="https://ae.rhodecode.com",
95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
96 parser.addoption(
96 parser.addoption(
97 '--sqlite-connection-string', action='store',
97 '--sqlite-connection-string', action='store',
98 default='', help="Connection string for the dbs tests with SQLite")
98 default='', help="Connection string for the dbs tests with SQLite")
99 parser.addoption(
99 parser.addoption(
100 '--postgres-connection-string', action='store',
100 '--postgres-connection-string', action='store',
101 default='', help="Connection string for the dbs tests with Postgres")
101 default='', help="Connection string for the dbs tests with Postgres")
102 parser.addoption(
102 parser.addoption(
103 '--mysql-connection-string', action='store',
103 '--mysql-connection-string', action='store',
104 default='', help="Connection string for the dbs tests with MySQL")
104 default='', help="Connection string for the dbs tests with MySQL")
105 parser.addoption(
105 parser.addoption(
106 '--repeat', type=int, default=100,
106 '--repeat', type=int, default=100,
107 help="Number of repetitions in performance tests.")
107 help="Number of repetitions in performance tests.")
108
108
109
109
110 def pytest_configure(config):
110 def pytest_configure(config):
111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
112 from rhodecode.config import patches
112 from rhodecode.config import patches
113 patches.kombu_1_5_1_python_2_7_11()
113 patches.kombu_1_5_1_python_2_7_11()
114
114
115
115
116 def pytest_collection_modifyitems(session, config, items):
116 def pytest_collection_modifyitems(session, config, items):
117 # nottest marked, compare nose, used for transition from nose to pytest
117 # nottest marked, compare nose, used for transition from nose to pytest
118 remaining = [
118 remaining = [
119 i for i in items if getattr(i.obj, '__test__', True)]
119 i for i in items if getattr(i.obj, '__test__', True)]
120 items[:] = remaining
120 items[:] = remaining
121
121
122
122
123 def pytest_generate_tests(metafunc):
123 def pytest_generate_tests(metafunc):
124 # Support test generation based on --backend parameter
124 # Support test generation based on --backend parameter
125 if 'backend_alias' in metafunc.fixturenames:
125 if 'backend_alias' in metafunc.fixturenames:
126 backends = get_backends_from_metafunc(metafunc)
126 backends = get_backends_from_metafunc(metafunc)
127 scope = None
127 scope = None
128 if not backends:
128 if not backends:
129 pytest.skip("Not enabled for any of selected backends")
129 pytest.skip("Not enabled for any of selected backends")
130 metafunc.parametrize('backend_alias', backends, scope=scope)
130 metafunc.parametrize('backend_alias', backends, scope=scope)
131 elif hasattr(metafunc.function, 'backends'):
131 elif hasattr(metafunc.function, 'backends'):
132 backends = get_backends_from_metafunc(metafunc)
132 backends = get_backends_from_metafunc(metafunc)
133 if not backends:
133 if not backends:
134 pytest.skip("Not enabled for any of selected backends")
134 pytest.skip("Not enabled for any of selected backends")
135
135
136
136
137 def get_backends_from_metafunc(metafunc):
137 def get_backends_from_metafunc(metafunc):
138 requested_backends = set(metafunc.config.getoption('--backends'))
138 requested_backends = set(metafunc.config.getoption('--backends'))
139 if hasattr(metafunc.function, 'backends'):
139 if hasattr(metafunc.function, 'backends'):
140 # Supported backends by this test function, created from
140 # Supported backends by this test function, created from
141 # pytest.mark.backends
141 # pytest.mark.backends
142 backends = metafunc.function.backends.args
142 backends = metafunc.function.backends.args
143 elif hasattr(metafunc.cls, 'backend_alias'):
143 elif hasattr(metafunc.cls, 'backend_alias'):
144 # Support class attribute "backend_alias", this is mainly
144 # Support class attribute "backend_alias", this is mainly
145 # for legacy reasons for tests not yet using pytest.mark.backends
145 # for legacy reasons for tests not yet using pytest.mark.backends
146 backends = [metafunc.cls.backend_alias]
146 backends = [metafunc.cls.backend_alias]
147 else:
147 else:
148 backends = metafunc.config.getoption('--backends')
148 backends = metafunc.config.getoption('--backends')
149 return requested_backends.intersection(backends)
149 return requested_backends.intersection(backends)
150
150
151
151
152 @pytest.fixture(scope='session', autouse=True)
152 @pytest.fixture(scope='session', autouse=True)
153 def activate_example_rcextensions(request):
153 def activate_example_rcextensions(request):
154 """
154 """
155 Patch in an example rcextensions module which verifies passed in kwargs.
155 Patch in an example rcextensions module which verifies passed in kwargs.
156 """
156 """
157 from rhodecode.tests.other import example_rcextensions
157 from rhodecode.tests.other import example_rcextensions
158
158
159 old_extensions = rhodecode.EXTENSIONS
159 old_extensions = rhodecode.EXTENSIONS
160 rhodecode.EXTENSIONS = example_rcextensions
160 rhodecode.EXTENSIONS = example_rcextensions
161
161
162 @request.addfinalizer
162 @request.addfinalizer
163 def cleanup():
163 def cleanup():
164 rhodecode.EXTENSIONS = old_extensions
164 rhodecode.EXTENSIONS = old_extensions
165
165
166
166
167 @pytest.fixture
167 @pytest.fixture
168 def capture_rcextensions():
168 def capture_rcextensions():
169 """
169 """
170 Returns the recorded calls to entry points in rcextensions.
170 Returns the recorded calls to entry points in rcextensions.
171 """
171 """
172 calls = rhodecode.EXTENSIONS.calls
172 calls = rhodecode.EXTENSIONS.calls
173 calls.clear()
173 calls.clear()
174 # Note: At this moment, it is still the empty dict, but that will
174 # Note: At this moment, it is still the empty dict, but that will
175 # be filled during the test run and since it is a reference this
175 # be filled during the test run and since it is a reference this
176 # is enough to make it work.
176 # is enough to make it work.
177 return calls
177 return calls
178
178
179
179
180 @pytest.fixture(scope='session')
180 @pytest.fixture(scope='session')
181 def http_environ_session():
181 def http_environ_session():
182 """
182 """
183 Allow to use "http_environ" in session scope.
183 Allow to use "http_environ" in session scope.
184 """
184 """
185 return http_environ(
185 return http_environ(
186 http_host_stub=http_host_stub())
186 http_host_stub=http_host_stub())
187
187
188
188
189 @pytest.fixture
189 @pytest.fixture
190 def http_host_stub():
190 def http_host_stub():
191 """
191 """
192 Value of HTTP_HOST in the test run.
192 Value of HTTP_HOST in the test run.
193 """
193 """
194 return 'example.com:80'
194 return 'example.com:80'
195
195
196
196
197 @pytest.fixture
197 @pytest.fixture
198 def http_host_only_stub():
198 def http_host_only_stub():
199 """
199 """
200 Value of HTTP_HOST in the test run.
200 Value of HTTP_HOST in the test run.
201 """
201 """
202 return http_host_stub().split(':')[0]
202 return http_host_stub().split(':')[0]
203
203
204
204
205 @pytest.fixture
205 @pytest.fixture
206 def http_environ(http_host_stub):
206 def http_environ(http_host_stub):
207 """
207 """
208 HTTP extra environ keys.
208 HTTP extra environ keys.
209
209
210 User by the test application and as well for setting up the pylons
210 User by the test application and as well for setting up the pylons
211 environment. In the case of the fixture "app" it should be possible
211 environment. In the case of the fixture "app" it should be possible
212 to override this for a specific test case.
212 to override this for a specific test case.
213 """
213 """
214 return {
214 return {
215 'SERVER_NAME': http_host_only_stub(),
215 'SERVER_NAME': http_host_only_stub(),
216 'SERVER_PORT': http_host_stub.split(':')[1],
216 'SERVER_PORT': http_host_stub.split(':')[1],
217 'HTTP_HOST': http_host_stub,
217 'HTTP_HOST': http_host_stub,
218 'HTTP_USER_AGENT': 'rc-test-agent',
218 'HTTP_USER_AGENT': 'rc-test-agent',
219 'REQUEST_METHOD': 'GET'
219 'REQUEST_METHOD': 'GET'
220 }
220 }
221
221
222
222
223 @pytest.fixture(scope='function')
223 @pytest.fixture(scope='function')
224 def app(request, config_stub, pylonsapp, http_environ):
224 def app(request, config_stub, pylonsapp, http_environ):
225 app = CustomTestApp(
225 app = CustomTestApp(
226 pylonsapp,
226 pylonsapp,
227 extra_environ=http_environ)
227 extra_environ=http_environ)
228 if request.cls:
228 if request.cls:
229 request.cls.app = app
229 request.cls.app = app
230 return app
230 return app
231
231
232
232
233 @pytest.fixture(scope='session')
233 @pytest.fixture(scope='session')
234 def app_settings(pylonsapp, pylons_config):
234 def app_settings(pylonsapp, pylons_config):
235 """
235 """
236 Settings dictionary used to create the app.
236 Settings dictionary used to create the app.
237
237
238 Parses the ini file and passes the result through the sanitize and apply
238 Parses the ini file and passes the result through the sanitize and apply
239 defaults mechanism in `rhodecode.config.middleware`.
239 defaults mechanism in `rhodecode.config.middleware`.
240 """
240 """
241 from paste.deploy.loadwsgi import loadcontext, APP
241 from paste.deploy.loadwsgi import loadcontext, APP
242 from rhodecode.config.middleware import (
242 from rhodecode.config.middleware import (
243 sanitize_settings_and_apply_defaults)
243 sanitize_settings_and_apply_defaults)
244 context = loadcontext(APP, 'config:' + pylons_config)
244 context = loadcontext(APP, 'config:' + pylons_config)
245 settings = sanitize_settings_and_apply_defaults(context.config())
245 settings = sanitize_settings_and_apply_defaults(context.config())
246 return settings
246 return settings
247
247
248
248
249 @pytest.fixture(scope='session')
249 @pytest.fixture(scope='session')
250 def db(app_settings):
250 def db(app_settings):
251 """
251 """
252 Initializes the database connection.
252 Initializes the database connection.
253
253
254 It uses the same settings which are used to create the ``pylonsapp`` or
254 It uses the same settings which are used to create the ``pylonsapp`` or
255 ``app`` fixtures.
255 ``app`` fixtures.
256 """
256 """
257 from rhodecode.config.utils import initialize_database
257 from rhodecode.config.utils import initialize_database
258 initialize_database(app_settings)
258 initialize_database(app_settings)
259
259
260
260
261 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
261 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
262
262
263
263
264 def _autologin_user(app, *args):
264 def _autologin_user(app, *args):
265 session = login_user_session(app, *args)
265 session = login_user_session(app, *args)
266 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
266 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
267 return LoginData(csrf_token, session['rhodecode_user'])
267 return LoginData(csrf_token, session['rhodecode_user'])
268
268
269
269
270 @pytest.fixture
270 @pytest.fixture
271 def autologin_user(app):
271 def autologin_user(app):
272 """
272 """
273 Utility fixture which makes sure that the admin user is logged in
273 Utility fixture which makes sure that the admin user is logged in
274 """
274 """
275 return _autologin_user(app)
275 return _autologin_user(app)
276
276
277
277
278 @pytest.fixture
278 @pytest.fixture
279 def autologin_regular_user(app):
279 def autologin_regular_user(app):
280 """
280 """
281 Utility fixture which makes sure that the regular user is logged in
281 Utility fixture which makes sure that the regular user is logged in
282 """
282 """
283 return _autologin_user(
283 return _autologin_user(
284 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
284 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
285
285
286
286
287 @pytest.fixture(scope='function')
287 @pytest.fixture(scope='function')
288 def csrf_token(request, autologin_user):
288 def csrf_token(request, autologin_user):
289 return autologin_user.csrf_token
289 return autologin_user.csrf_token
290
290
291
291
292 @pytest.fixture(scope='function')
292 @pytest.fixture(scope='function')
293 def xhr_header(request):
293 def xhr_header(request):
294 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
294 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
295
295
296
296
297 @pytest.fixture
297 @pytest.fixture
298 def real_crypto_backend(monkeypatch):
298 def real_crypto_backend(monkeypatch):
299 """
299 """
300 Switch the production crypto backend on for this test.
300 Switch the production crypto backend on for this test.
301
301
302 During the test run the crypto backend is replaced with a faster
302 During the test run the crypto backend is replaced with a faster
303 implementation based on the MD5 algorithm.
303 implementation based on the MD5 algorithm.
304 """
304 """
305 monkeypatch.setattr(rhodecode, 'is_test', False)
305 monkeypatch.setattr(rhodecode, 'is_test', False)
306
306
307
307
308 @pytest.fixture(scope='class')
308 @pytest.fixture(scope='class')
309 def index_location(request, pylonsapp):
309 def index_location(request, pylonsapp):
310 index_location = pylonsapp.config['app_conf']['search.location']
310 index_location = pylonsapp.config['app_conf']['search.location']
311 if request.cls:
311 if request.cls:
312 request.cls.index_location = index_location
312 request.cls.index_location = index_location
313 return index_location
313 return index_location
314
314
315
315
316 @pytest.fixture(scope='session', autouse=True)
316 @pytest.fixture(scope='session', autouse=True)
317 def tests_tmp_path(request):
317 def tests_tmp_path(request):
318 """
318 """
319 Create temporary directory to be used during the test session.
319 Create temporary directory to be used during the test session.
320 """
320 """
321 if not os.path.exists(TESTS_TMP_PATH):
321 if not os.path.exists(TESTS_TMP_PATH):
322 os.makedirs(TESTS_TMP_PATH)
322 os.makedirs(TESTS_TMP_PATH)
323
323
324 if not request.config.getoption('--keep-tmp-path'):
324 if not request.config.getoption('--keep-tmp-path'):
325 @request.addfinalizer
325 @request.addfinalizer
326 def remove_tmp_path():
326 def remove_tmp_path():
327 shutil.rmtree(TESTS_TMP_PATH)
327 shutil.rmtree(TESTS_TMP_PATH)
328
328
329 return TESTS_TMP_PATH
329 return TESTS_TMP_PATH
330
330
331
331
332 @pytest.fixture
332 @pytest.fixture
333 def test_repo_group(request):
333 def test_repo_group(request):
334 """
334 """
335 Create a temporary repository group, and destroy it after
335 Create a temporary repository group, and destroy it after
336 usage automatically
336 usage automatically
337 """
337 """
338 fixture = Fixture()
338 fixture = Fixture()
339 repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '')
339 repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '')
340 repo_group = fixture.create_repo_group(repogroupid)
340 repo_group = fixture.create_repo_group(repogroupid)
341
341
342 def _cleanup():
342 def _cleanup():
343 fixture.destroy_repo_group(repogroupid)
343 fixture.destroy_repo_group(repogroupid)
344
344
345 request.addfinalizer(_cleanup)
345 request.addfinalizer(_cleanup)
346 return repo_group
346 return repo_group
347
347
348
348
349 @pytest.fixture
349 @pytest.fixture
350 def test_user_group(request):
350 def test_user_group(request):
351 """
351 """
352 Create a temporary user group, and destroy it after
352 Create a temporary user group, and destroy it after
353 usage automatically
353 usage automatically
354 """
354 """
355 fixture = Fixture()
355 fixture = Fixture()
356 usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '')
356 usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '')
357 user_group = fixture.create_user_group(usergroupid)
357 user_group = fixture.create_user_group(usergroupid)
358
358
359 def _cleanup():
359 def _cleanup():
360 fixture.destroy_user_group(user_group)
360 fixture.destroy_user_group(user_group)
361
361
362 request.addfinalizer(_cleanup)
362 request.addfinalizer(_cleanup)
363 return user_group
363 return user_group
364
364
365
365
366 @pytest.fixture(scope='session')
366 @pytest.fixture(scope='session')
367 def test_repo(request):
367 def test_repo(request):
368 container = TestRepoContainer()
368 container = TestRepoContainer()
369 request.addfinalizer(container._cleanup)
369 request.addfinalizer(container._cleanup)
370 return container
370 return container
371
371
372
372
373 class TestRepoContainer(object):
373 class TestRepoContainer(object):
374 """
374 """
375 Container for test repositories which are used read only.
375 Container for test repositories which are used read only.
376
376
377 Repositories will be created on demand and re-used during the lifetime
377 Repositories will be created on demand and re-used during the lifetime
378 of this object.
378 of this object.
379
379
380 Usage to get the svn test repository "minimal"::
380 Usage to get the svn test repository "minimal"::
381
381
382 test_repo = TestContainer()
382 test_repo = TestContainer()
383 repo = test_repo('minimal', 'svn')
383 repo = test_repo('minimal', 'svn')
384
384
385 """
385 """
386
386
387 dump_extractors = {
387 dump_extractors = {
388 'git': utils.extract_git_repo_from_dump,
388 'git': utils.extract_git_repo_from_dump,
389 'hg': utils.extract_hg_repo_from_dump,
389 'hg': utils.extract_hg_repo_from_dump,
390 'svn': utils.extract_svn_repo_from_dump,
390 'svn': utils.extract_svn_repo_from_dump,
391 }
391 }
392
392
393 def __init__(self):
393 def __init__(self):
394 self._cleanup_repos = []
394 self._cleanup_repos = []
395 self._fixture = Fixture()
395 self._fixture = Fixture()
396 self._repos = {}
396 self._repos = {}
397
397
398 def __call__(self, dump_name, backend_alias, config=None):
398 def __call__(self, dump_name, backend_alias, config=None):
399 key = (dump_name, backend_alias)
399 key = (dump_name, backend_alias)
400 if key not in self._repos:
400 if key not in self._repos:
401 repo = self._create_repo(dump_name, backend_alias, config)
401 repo = self._create_repo(dump_name, backend_alias, config)
402 self._repos[key] = repo.repo_id
402 self._repos[key] = repo.repo_id
403 return Repository.get(self._repos[key])
403 return Repository.get(self._repos[key])
404
404
405 def _create_repo(self, dump_name, backend_alias, config):
405 def _create_repo(self, dump_name, backend_alias, config):
406 repo_name = '%s-%s' % (backend_alias, dump_name)
406 repo_name = '%s-%s' % (backend_alias, dump_name)
407 backend_class = get_backend(backend_alias)
407 backend_class = get_backend(backend_alias)
408 dump_extractor = self.dump_extractors[backend_alias]
408 dump_extractor = self.dump_extractors[backend_alias]
409 repo_path = dump_extractor(dump_name, repo_name)
409 repo_path = dump_extractor(dump_name, repo_name)
410
410
411 vcs_repo = backend_class(repo_path, config=config)
411 vcs_repo = backend_class(repo_path, config=config)
412 repo2db_mapper({repo_name: vcs_repo})
412 repo2db_mapper({repo_name: vcs_repo})
413
413
414 repo = RepoModel().get_by_repo_name(repo_name)
414 repo = RepoModel().get_by_repo_name(repo_name)
415 self._cleanup_repos.append(repo_name)
415 self._cleanup_repos.append(repo_name)
416 return repo
416 return repo
417
417
418 def _cleanup(self):
418 def _cleanup(self):
419 for repo_name in reversed(self._cleanup_repos):
419 for repo_name in reversed(self._cleanup_repos):
420 self._fixture.destroy_repo(repo_name)
420 self._fixture.destroy_repo(repo_name)
421
421
422
422
423 @pytest.fixture
423 @pytest.fixture
424 def backend(request, backend_alias, pylonsapp, test_repo):
424 def backend(request, backend_alias, pylonsapp, test_repo):
425 """
425 """
426 Parametrized fixture which represents a single backend implementation.
426 Parametrized fixture which represents a single backend implementation.
427
427
428 It respects the option `--backends` to focus the test run on specific
428 It respects the option `--backends` to focus the test run on specific
429 backend implementations.
429 backend implementations.
430
430
431 It also supports `pytest.mark.xfail_backends` to mark tests as failing
431 It also supports `pytest.mark.xfail_backends` to mark tests as failing
432 for specific backends. This is intended as a utility for incremental
432 for specific backends. This is intended as a utility for incremental
433 development of a new backend implementation.
433 development of a new backend implementation.
434 """
434 """
435 if backend_alias not in request.config.getoption('--backends'):
435 if backend_alias not in request.config.getoption('--backends'):
436 pytest.skip("Backend %s not selected." % (backend_alias, ))
436 pytest.skip("Backend %s not selected." % (backend_alias, ))
437
437
438 utils.check_xfail_backends(request.node, backend_alias)
438 utils.check_xfail_backends(request.node, backend_alias)
439 utils.check_skip_backends(request.node, backend_alias)
439 utils.check_skip_backends(request.node, backend_alias)
440
440
441 repo_name = 'vcs_test_%s' % (backend_alias, )
441 repo_name = 'vcs_test_%s' % (backend_alias, )
442 backend = Backend(
442 backend = Backend(
443 alias=backend_alias,
443 alias=backend_alias,
444 repo_name=repo_name,
444 repo_name=repo_name,
445 test_name=request.node.name,
445 test_name=request.node.name,
446 test_repo_container=test_repo)
446 test_repo_container=test_repo)
447 request.addfinalizer(backend.cleanup)
447 request.addfinalizer(backend.cleanup)
448 return backend
448 return backend
449
449
450
450
451 @pytest.fixture
451 @pytest.fixture
452 def backend_git(request, pylonsapp, test_repo):
452 def backend_git(request, pylonsapp, test_repo):
453 return backend(request, 'git', pylonsapp, test_repo)
453 return backend(request, 'git', pylonsapp, test_repo)
454
454
455
455
456 @pytest.fixture
456 @pytest.fixture
457 def backend_hg(request, pylonsapp, test_repo):
457 def backend_hg(request, pylonsapp, test_repo):
458 return backend(request, 'hg', pylonsapp, test_repo)
458 return backend(request, 'hg', pylonsapp, test_repo)
459
459
460
460
461 @pytest.fixture
461 @pytest.fixture
462 def backend_svn(request, pylonsapp, test_repo):
462 def backend_svn(request, pylonsapp, test_repo):
463 return backend(request, 'svn', pylonsapp, test_repo)
463 return backend(request, 'svn', pylonsapp, test_repo)
464
464
465
465
466 @pytest.fixture
466 @pytest.fixture
467 def backend_random(backend_git):
467 def backend_random(backend_git):
468 """
468 """
469 Use this to express that your tests need "a backend.
469 Use this to express that your tests need "a backend.
470
470
471 A few of our tests need a backend, so that we can run the code. This
471 A few of our tests need a backend, so that we can run the code. This
472 fixture is intended to be used for such cases. It will pick one of the
472 fixture is intended to be used for such cases. It will pick one of the
473 backends and run the tests.
473 backends and run the tests.
474
474
475 The fixture `backend` would run the test multiple times for each
475 The fixture `backend` would run the test multiple times for each
476 available backend which is a pure waste of time if the test is
476 available backend which is a pure waste of time if the test is
477 independent of the backend type.
477 independent of the backend type.
478 """
478 """
479 # TODO: johbo: Change this to pick a random backend
479 # TODO: johbo: Change this to pick a random backend
480 return backend_git
480 return backend_git
481
481
482
482
483 @pytest.fixture
483 @pytest.fixture
484 def backend_stub(backend_git):
484 def backend_stub(backend_git):
485 """
485 """
486 Use this to express that your tests need a backend stub
486 Use this to express that your tests need a backend stub
487
487
488 TODO: mikhail: Implement a real stub logic instead of returning
488 TODO: mikhail: Implement a real stub logic instead of returning
489 a git backend
489 a git backend
490 """
490 """
491 return backend_git
491 return backend_git
492
492
493
493
494 @pytest.fixture
494 @pytest.fixture
495 def repo_stub(backend_stub):
495 def repo_stub(backend_stub):
496 """
496 """
497 Use this to express that your tests need a repository stub
497 Use this to express that your tests need a repository stub
498 """
498 """
499 return backend_stub.create_repo()
499 return backend_stub.create_repo()
500
500
501
501
502 class Backend(object):
502 class Backend(object):
503 """
503 """
504 Represents the test configuration for one supported backend
504 Represents the test configuration for one supported backend
505
505
506 Provides easy access to different test repositories based on
506 Provides easy access to different test repositories based on
507 `__getitem__`. Such repositories will only be created once per test
507 `__getitem__`. Such repositories will only be created once per test
508 session.
508 session.
509 """
509 """
510
510
511 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
511 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
512 _master_repo = None
512 _master_repo = None
513 _commit_ids = {}
513 _commit_ids = {}
514
514
515 def __init__(self, alias, repo_name, test_name, test_repo_container):
515 def __init__(self, alias, repo_name, test_name, test_repo_container):
516 self.alias = alias
516 self.alias = alias
517 self.repo_name = repo_name
517 self.repo_name = repo_name
518 self._cleanup_repos = []
518 self._cleanup_repos = []
519 self._test_name = test_name
519 self._test_name = test_name
520 self._test_repo_container = test_repo_container
520 self._test_repo_container = test_repo_container
521 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
521 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
522 # Fixture will survive in the end.
522 # Fixture will survive in the end.
523 self._fixture = Fixture()
523 self._fixture = Fixture()
524
524
525 def __getitem__(self, key):
525 def __getitem__(self, key):
526 return self._test_repo_container(key, self.alias)
526 return self._test_repo_container(key, self.alias)
527
527
528 def create_test_repo(self, key, config=None):
528 def create_test_repo(self, key, config=None):
529 return self._test_repo_container(key, self.alias, config)
529 return self._test_repo_container(key, self.alias, config)
530
530
531 @property
531 @property
532 def repo(self):
532 def repo(self):
533 """
533 """
534 Returns the "current" repository. This is the vcs_test repo or the
534 Returns the "current" repository. This is the vcs_test repo or the
535 last repo which has been created with `create_repo`.
535 last repo which has been created with `create_repo`.
536 """
536 """
537 from rhodecode.model.db import Repository
537 from rhodecode.model.db import Repository
538 return Repository.get_by_repo_name(self.repo_name)
538 return Repository.get_by_repo_name(self.repo_name)
539
539
540 @property
540 @property
541 def default_branch_name(self):
541 def default_branch_name(self):
542 VcsRepository = get_backend(self.alias)
542 VcsRepository = get_backend(self.alias)
543 return VcsRepository.DEFAULT_BRANCH_NAME
543 return VcsRepository.DEFAULT_BRANCH_NAME
544
544
545 @property
545 @property
546 def default_head_id(self):
546 def default_head_id(self):
547 """
547 """
548 Returns the default head id of the underlying backend.
548 Returns the default head id of the underlying backend.
549
549
550 This will be the default branch name in case the backend does have a
550 This will be the default branch name in case the backend does have a
551 default branch. In the other cases it will point to a valid head
551 default branch. In the other cases it will point to a valid head
552 which can serve as the base to create a new commit on top of it.
552 which can serve as the base to create a new commit on top of it.
553 """
553 """
554 vcsrepo = self.repo.scm_instance()
554 vcsrepo = self.repo.scm_instance()
555 head_id = (
555 head_id = (
556 vcsrepo.DEFAULT_BRANCH_NAME or
556 vcsrepo.DEFAULT_BRANCH_NAME or
557 vcsrepo.commit_ids[-1])
557 vcsrepo.commit_ids[-1])
558 return head_id
558 return head_id
559
559
560 @property
560 @property
561 def commit_ids(self):
561 def commit_ids(self):
562 """
562 """
563 Returns the list of commits for the last created repository
563 Returns the list of commits for the last created repository
564 """
564 """
565 return self._commit_ids
565 return self._commit_ids
566
566
567 def create_master_repo(self, commits):
567 def create_master_repo(self, commits):
568 """
568 """
569 Create a repository and remember it as a template.
569 Create a repository and remember it as a template.
570
570
571 This allows to easily create derived repositories to construct
571 This allows to easily create derived repositories to construct
572 more complex scenarios for diff, compare and pull requests.
572 more complex scenarios for diff, compare and pull requests.
573
573
574 Returns a commit map which maps from commit message to raw_id.
574 Returns a commit map which maps from commit message to raw_id.
575 """
575 """
576 self._master_repo = self.create_repo(commits=commits)
576 self._master_repo = self.create_repo(commits=commits)
577 return self._commit_ids
577 return self._commit_ids
578
578
579 def create_repo(
579 def create_repo(
580 self, commits=None, number_of_commits=0, heads=None,
580 self, commits=None, number_of_commits=0, heads=None,
581 name_suffix=u'', **kwargs):
581 name_suffix=u'', **kwargs):
582 """
582 """
583 Create a repository and record it for later cleanup.
583 Create a repository and record it for later cleanup.
584
584
585 :param commits: Optional. A sequence of dict instances.
585 :param commits: Optional. A sequence of dict instances.
586 Will add a commit per entry to the new repository.
586 Will add a commit per entry to the new repository.
587 :param number_of_commits: Optional. If set to a number, this number of
587 :param number_of_commits: Optional. If set to a number, this number of
588 commits will be added to the new repository.
588 commits will be added to the new repository.
589 :param heads: Optional. Can be set to a sequence of of commit
589 :param heads: Optional. Can be set to a sequence of of commit
590 names which shall be pulled in from the master repository.
590 names which shall be pulled in from the master repository.
591
591
592 """
592 """
593 self.repo_name = self._next_repo_name() + name_suffix
593 self.repo_name = self._next_repo_name() + name_suffix
594 repo = self._fixture.create_repo(
594 repo = self._fixture.create_repo(
595 self.repo_name, repo_type=self.alias, **kwargs)
595 self.repo_name, repo_type=self.alias, **kwargs)
596 self._cleanup_repos.append(repo.repo_name)
596 self._cleanup_repos.append(repo.repo_name)
597
597
598 commits = commits or [
598 commits = commits or [
599 {'message': 'Commit %s of %s' % (x, self.repo_name)}
599 {'message': 'Commit %s of %s' % (x, self.repo_name)}
600 for x in xrange(number_of_commits)]
600 for x in xrange(number_of_commits)]
601 self._add_commits_to_repo(repo.scm_instance(), commits)
601 self._add_commits_to_repo(repo.scm_instance(), commits)
602 if heads:
602 if heads:
603 self.pull_heads(repo, heads)
603 self.pull_heads(repo, heads)
604
604
605 return repo
605 return repo
606
606
607 def pull_heads(self, repo, heads):
607 def pull_heads(self, repo, heads):
608 """
608 """
609 Make sure that repo contains all commits mentioned in `heads`
609 Make sure that repo contains all commits mentioned in `heads`
610 """
610 """
611 vcsmaster = self._master_repo.scm_instance()
611 vcsmaster = self._master_repo.scm_instance()
612 vcsrepo = repo.scm_instance()
612 vcsrepo = repo.scm_instance()
613 vcsrepo.config.clear_section('hooks')
613 vcsrepo.config.clear_section('hooks')
614 commit_ids = [self._commit_ids[h] for h in heads]
614 commit_ids = [self._commit_ids[h] for h in heads]
615 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
615 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
616
616
617 def create_fork(self):
617 def create_fork(self):
618 repo_to_fork = self.repo_name
618 repo_to_fork = self.repo_name
619 self.repo_name = self._next_repo_name()
619 self.repo_name = self._next_repo_name()
620 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
620 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
621 self._cleanup_repos.append(self.repo_name)
621 self._cleanup_repos.append(self.repo_name)
622 return repo
622 return repo
623
623
624 def new_repo_name(self, suffix=u''):
624 def new_repo_name(self, suffix=u''):
625 self.repo_name = self._next_repo_name() + suffix
625 self.repo_name = self._next_repo_name() + suffix
626 self._cleanup_repos.append(self.repo_name)
626 self._cleanup_repos.append(self.repo_name)
627 return self.repo_name
627 return self.repo_name
628
628
629 def _next_repo_name(self):
629 def _next_repo_name(self):
630 return u"%s_%s" % (
630 return u"%s_%s" % (
631 self.invalid_repo_name.sub(u'_', self._test_name),
631 self.invalid_repo_name.sub(u'_', self._test_name),
632 len(self._cleanup_repos))
632 len(self._cleanup_repos))
633
633
634 def ensure_file(self, filename, content='Test content\n'):
634 def ensure_file(self, filename, content='Test content\n'):
635 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
635 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
636 commits = [
636 commits = [
637 {'added': [
637 {'added': [
638 FileNode(filename, content=content),
638 FileNode(filename, content=content),
639 ]},
639 ]},
640 ]
640 ]
641 self._add_commits_to_repo(self.repo.scm_instance(), commits)
641 self._add_commits_to_repo(self.repo.scm_instance(), commits)
642
642
643 def enable_downloads(self):
643 def enable_downloads(self):
644 repo = self.repo
644 repo = self.repo
645 repo.enable_downloads = True
645 repo.enable_downloads = True
646 Session().add(repo)
646 Session().add(repo)
647 Session().commit()
647 Session().commit()
648
648
649 def cleanup(self):
649 def cleanup(self):
650 for repo_name in reversed(self._cleanup_repos):
650 for repo_name in reversed(self._cleanup_repos):
651 self._fixture.destroy_repo(repo_name)
651 self._fixture.destroy_repo(repo_name)
652
652
653 def _add_commits_to_repo(self, repo, commits):
653 def _add_commits_to_repo(self, repo, commits):
654 commit_ids = _add_commits_to_repo(repo, commits)
654 commit_ids = _add_commits_to_repo(repo, commits)
655 if not commit_ids:
655 if not commit_ids:
656 return
656 return
657 self._commit_ids = commit_ids
657 self._commit_ids = commit_ids
658
658
659 # Creating refs for Git to allow fetching them from remote repository
659 # Creating refs for Git to allow fetching them from remote repository
660 if self.alias == 'git':
660 if self.alias == 'git':
661 refs = {}
661 refs = {}
662 for message in self._commit_ids:
662 for message in self._commit_ids:
663 # TODO: mikhail: do more special chars replacements
663 # TODO: mikhail: do more special chars replacements
664 ref_name = 'refs/test-refs/{}'.format(
664 ref_name = 'refs/test-refs/{}'.format(
665 message.replace(' ', ''))
665 message.replace(' ', ''))
666 refs[ref_name] = self._commit_ids[message]
666 refs[ref_name] = self._commit_ids[message]
667 self._create_refs(repo, refs)
667 self._create_refs(repo, refs)
668
668
669 def _create_refs(self, repo, refs):
669 def _create_refs(self, repo, refs):
670 for ref_name in refs:
670 for ref_name in refs:
671 repo.set_refs(ref_name, refs[ref_name])
671 repo.set_refs(ref_name, refs[ref_name])
672
672
673
673
674 @pytest.fixture
674 @pytest.fixture
675 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
675 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
676 """
676 """
677 Parametrized fixture which represents a single vcs backend implementation.
677 Parametrized fixture which represents a single vcs backend implementation.
678
678
679 See the fixture `backend` for more details. This one implements the same
679 See the fixture `backend` for more details. This one implements the same
680 concept, but on vcs level. So it does not provide model instances etc.
680 concept, but on vcs level. So it does not provide model instances etc.
681
681
682 Parameters are generated dynamically, see :func:`pytest_generate_tests`
682 Parameters are generated dynamically, see :func:`pytest_generate_tests`
683 for how this works.
683 for how this works.
684 """
684 """
685 if backend_alias not in request.config.getoption('--backends'):
685 if backend_alias not in request.config.getoption('--backends'):
686 pytest.skip("Backend %s not selected." % (backend_alias, ))
686 pytest.skip("Backend %s not selected." % (backend_alias, ))
687
687
688 utils.check_xfail_backends(request.node, backend_alias)
688 utils.check_xfail_backends(request.node, backend_alias)
689 utils.check_skip_backends(request.node, backend_alias)
689 utils.check_skip_backends(request.node, backend_alias)
690
690
691 repo_name = 'vcs_test_%s' % (backend_alias, )
691 repo_name = 'vcs_test_%s' % (backend_alias, )
692 repo_path = os.path.join(tests_tmp_path, repo_name)
692 repo_path = os.path.join(tests_tmp_path, repo_name)
693 backend = VcsBackend(
693 backend = VcsBackend(
694 alias=backend_alias,
694 alias=backend_alias,
695 repo_path=repo_path,
695 repo_path=repo_path,
696 test_name=request.node.name,
696 test_name=request.node.name,
697 test_repo_container=test_repo)
697 test_repo_container=test_repo)
698 request.addfinalizer(backend.cleanup)
698 request.addfinalizer(backend.cleanup)
699 return backend
699 return backend
700
700
701
701
702 @pytest.fixture
702 @pytest.fixture
703 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
703 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
704 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
704 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
705
705
706
706
707 @pytest.fixture
707 @pytest.fixture
708 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
708 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
709 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
709 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
710
710
711
711
712 @pytest.fixture
712 @pytest.fixture
713 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
713 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
714 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
714 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
715
715
716
716
717 @pytest.fixture
717 @pytest.fixture
718 def vcsbackend_random(vcsbackend_git):
718 def vcsbackend_random(vcsbackend_git):
719 """
719 """
720 Use this to express that your tests need "a vcsbackend".
720 Use this to express that your tests need "a vcsbackend".
721
721
722 The fixture `vcsbackend` would run the test multiple times for each
722 The fixture `vcsbackend` would run the test multiple times for each
723 available vcs backend which is a pure waste of time if the test is
723 available vcs backend which is a pure waste of time if the test is
724 independent of the vcs backend type.
724 independent of the vcs backend type.
725 """
725 """
726 # TODO: johbo: Change this to pick a random backend
726 # TODO: johbo: Change this to pick a random backend
727 return vcsbackend_git
727 return vcsbackend_git
728
728
729
729
730 @pytest.fixture
730 @pytest.fixture
731 def vcsbackend_stub(vcsbackend_git):
731 def vcsbackend_stub(vcsbackend_git):
732 """
732 """
733 Use this to express that your test just needs a stub of a vcsbackend.
733 Use this to express that your test just needs a stub of a vcsbackend.
734
734
735 Plan is to eventually implement an in-memory stub to speed tests up.
735 Plan is to eventually implement an in-memory stub to speed tests up.
736 """
736 """
737 return vcsbackend_git
737 return vcsbackend_git
738
738
739
739
740 class VcsBackend(object):
740 class VcsBackend(object):
741 """
741 """
742 Represents the test configuration for one supported vcs backend.
742 Represents the test configuration for one supported vcs backend.
743 """
743 """
744
744
745 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
745 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
746
746
747 def __init__(self, alias, repo_path, test_name, test_repo_container):
747 def __init__(self, alias, repo_path, test_name, test_repo_container):
748 self.alias = alias
748 self.alias = alias
749 self._repo_path = repo_path
749 self._repo_path = repo_path
750 self._cleanup_repos = []
750 self._cleanup_repos = []
751 self._test_name = test_name
751 self._test_name = test_name
752 self._test_repo_container = test_repo_container
752 self._test_repo_container = test_repo_container
753
753
754 def __getitem__(self, key):
754 def __getitem__(self, key):
755 return self._test_repo_container(key, self.alias).scm_instance()
755 return self._test_repo_container(key, self.alias).scm_instance()
756
756
757 @property
757 @property
758 def repo(self):
758 def repo(self):
759 """
759 """
760 Returns the "current" repository. This is the vcs_test repo of the last
760 Returns the "current" repository. This is the vcs_test repo of the last
761 repo which has been created.
761 repo which has been created.
762 """
762 """
763 Repository = get_backend(self.alias)
763 Repository = get_backend(self.alias)
764 return Repository(self._repo_path)
764 return Repository(self._repo_path)
765
765
766 @property
766 @property
767 def backend(self):
767 def backend(self):
768 """
768 """
769 Returns the backend implementation class.
769 Returns the backend implementation class.
770 """
770 """
771 return get_backend(self.alias)
771 return get_backend(self.alias)
772
772
773 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
773 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
774 repo_name = self._next_repo_name()
774 repo_name = self._next_repo_name()
775 self._repo_path = get_new_dir(repo_name)
775 self._repo_path = get_new_dir(repo_name)
776 repo_class = get_backend(self.alias)
776 repo_class = get_backend(self.alias)
777 src_url = None
777 src_url = None
778 if _clone_repo:
778 if _clone_repo:
779 src_url = _clone_repo.path
779 src_url = _clone_repo.path
780 repo = repo_class(self._repo_path, create=True, src_url=src_url)
780 repo = repo_class(self._repo_path, create=True, src_url=src_url)
781 self._cleanup_repos.append(repo)
781 self._cleanup_repos.append(repo)
782
782
783 commits = commits or [
783 commits = commits or [
784 {'message': 'Commit %s of %s' % (x, repo_name)}
784 {'message': 'Commit %s of %s' % (x, repo_name)}
785 for x in xrange(number_of_commits)]
785 for x in xrange(number_of_commits)]
786 _add_commits_to_repo(repo, commits)
786 _add_commits_to_repo(repo, commits)
787 return repo
787 return repo
788
788
789 def clone_repo(self, repo):
789 def clone_repo(self, repo):
790 return self.create_repo(_clone_repo=repo)
790 return self.create_repo(_clone_repo=repo)
791
791
792 def cleanup(self):
792 def cleanup(self):
793 for repo in self._cleanup_repos:
793 for repo in self._cleanup_repos:
794 shutil.rmtree(repo.path)
794 shutil.rmtree(repo.path)
795
795
796 def new_repo_path(self):
796 def new_repo_path(self):
797 repo_name = self._next_repo_name()
797 repo_name = self._next_repo_name()
798 self._repo_path = get_new_dir(repo_name)
798 self._repo_path = get_new_dir(repo_name)
799 return self._repo_path
799 return self._repo_path
800
800
801 def _next_repo_name(self):
801 def _next_repo_name(self):
802 return "%s_%s" % (
802 return "%s_%s" % (
803 self.invalid_repo_name.sub('_', self._test_name),
803 self.invalid_repo_name.sub('_', self._test_name),
804 len(self._cleanup_repos))
804 len(self._cleanup_repos))
805
805
806 def add_file(self, repo, filename, content='Test content\n'):
806 def add_file(self, repo, filename, content='Test content\n'):
807 imc = repo.in_memory_commit
807 imc = repo.in_memory_commit
808 imc.add(FileNode(filename, content=content))
808 imc.add(FileNode(filename, content=content))
809 imc.commit(
809 imc.commit(
810 message=u'Automatic commit from vcsbackend fixture',
810 message=u'Automatic commit from vcsbackend fixture',
811 author=u'Automatic')
811 author=u'Automatic')
812
812
813 def ensure_file(self, filename, content='Test content\n'):
813 def ensure_file(self, filename, content='Test content\n'):
814 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
814 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
815 self.add_file(self.repo, filename, content)
815 self.add_file(self.repo, filename, content)
816
816
817
817
818 def _add_commits_to_repo(vcs_repo, commits):
818 def _add_commits_to_repo(vcs_repo, commits):
819 commit_ids = {}
819 commit_ids = {}
820 if not commits:
820 if not commits:
821 return commit_ids
821 return commit_ids
822
822
823 imc = vcs_repo.in_memory_commit
823 imc = vcs_repo.in_memory_commit
824 commit = None
824 commit = None
825
825
826 for idx, commit in enumerate(commits):
826 for idx, commit in enumerate(commits):
827 message = unicode(commit.get('message', 'Commit %s' % idx))
827 message = unicode(commit.get('message', 'Commit %s' % idx))
828
828
829 for node in commit.get('added', []):
829 for node in commit.get('added', []):
830 imc.add(FileNode(node.path, content=node.content))
830 imc.add(FileNode(node.path, content=node.content))
831 for node in commit.get('changed', []):
831 for node in commit.get('changed', []):
832 imc.change(FileNode(node.path, content=node.content))
832 imc.change(FileNode(node.path, content=node.content))
833 for node in commit.get('removed', []):
833 for node in commit.get('removed', []):
834 imc.remove(FileNode(node.path))
834 imc.remove(FileNode(node.path))
835
835
836 parents = [
836 parents = [
837 vcs_repo.get_commit(commit_id=commit_ids[p])
837 vcs_repo.get_commit(commit_id=commit_ids[p])
838 for p in commit.get('parents', [])]
838 for p in commit.get('parents', [])]
839
839
840 operations = ('added', 'changed', 'removed')
840 operations = ('added', 'changed', 'removed')
841 if not any((commit.get(o) for o in operations)):
841 if not any((commit.get(o) for o in operations)):
842 imc.add(FileNode('file_%s' % idx, content=message))
842 imc.add(FileNode('file_%s' % idx, content=message))
843
843
844 commit = imc.commit(
844 commit = imc.commit(
845 message=message,
845 message=message,
846 author=unicode(commit.get('author', 'Automatic')),
846 author=unicode(commit.get('author', 'Automatic')),
847 date=commit.get('date'),
847 date=commit.get('date'),
848 branch=commit.get('branch'),
848 branch=commit.get('branch'),
849 parents=parents)
849 parents=parents)
850
850
851 commit_ids[commit.message] = commit.raw_id
851 commit_ids[commit.message] = commit.raw_id
852
852
853 return commit_ids
853 return commit_ids
854
854
855
855
856 @pytest.fixture
856 @pytest.fixture
857 def reposerver(request):
857 def reposerver(request):
858 """
858 """
859 Allows to serve a backend repository
859 Allows to serve a backend repository
860 """
860 """
861
861
862 repo_server = RepoServer()
862 repo_server = RepoServer()
863 request.addfinalizer(repo_server.cleanup)
863 request.addfinalizer(repo_server.cleanup)
864 return repo_server
864 return repo_server
865
865
866
866
867 class RepoServer(object):
867 class RepoServer(object):
868 """
868 """
869 Utility to serve a local repository for the duration of a test case.
869 Utility to serve a local repository for the duration of a test case.
870
870
871 Supports only Subversion so far.
871 Supports only Subversion so far.
872 """
872 """
873
873
874 url = None
874 url = None
875
875
876 def __init__(self):
876 def __init__(self):
877 self._cleanup_servers = []
877 self._cleanup_servers = []
878
878
879 def serve(self, vcsrepo):
879 def serve(self, vcsrepo):
880 if vcsrepo.alias != 'svn':
880 if vcsrepo.alias != 'svn':
881 raise TypeError("Backend %s not supported" % vcsrepo.alias)
881 raise TypeError("Backend %s not supported" % vcsrepo.alias)
882
882
883 proc = subprocess32.Popen(
883 proc = subprocess32.Popen(
884 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
884 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
885 '--root', vcsrepo.path])
885 '--root', vcsrepo.path])
886 self._cleanup_servers.append(proc)
886 self._cleanup_servers.append(proc)
887 self.url = 'svn://localhost'
887 self.url = 'svn://localhost'
888
888
889 def cleanup(self):
889 def cleanup(self):
890 for proc in self._cleanup_servers:
890 for proc in self._cleanup_servers:
891 proc.terminate()
891 proc.terminate()
892
892
893
893
894 @pytest.fixture
894 @pytest.fixture
895 def pr_util(backend, request, config_stub):
895 def pr_util(backend, request, config_stub):
896 """
896 """
897 Utility for tests of models and for functional tests around pull requests.
897 Utility for tests of models and for functional tests around pull requests.
898
898
899 It gives an instance of :class:`PRTestUtility` which provides various
899 It gives an instance of :class:`PRTestUtility` which provides various
900 utility methods around one pull request.
900 utility methods around one pull request.
901
901
902 This fixture uses `backend` and inherits its parameterization.
902 This fixture uses `backend` and inherits its parameterization.
903 """
903 """
904
904
905 util = PRTestUtility(backend)
905 util = PRTestUtility(backend)
906
906
907 @request.addfinalizer
907 @request.addfinalizer
908 def cleanup():
908 def cleanup():
909 util.cleanup()
909 util.cleanup()
910
910
911 return util
911 return util
912
912
913
913
914 class PRTestUtility(object):
914 class PRTestUtility(object):
915
915
916 pull_request = None
916 pull_request = None
917 pull_request_id = None
917 pull_request_id = None
918 mergeable_patcher = None
918 mergeable_patcher = None
919 mergeable_mock = None
919 mergeable_mock = None
920 notification_patcher = None
920 notification_patcher = None
921
921
922 def __init__(self, backend):
922 def __init__(self, backend):
923 self.backend = backend
923 self.backend = backend
924
924
925 def create_pull_request(
925 def create_pull_request(
926 self, commits=None, target_head=None, source_head=None,
926 self, commits=None, target_head=None, source_head=None,
927 revisions=None, approved=False, author=None, mergeable=False,
927 revisions=None, approved=False, author=None, mergeable=False,
928 enable_notifications=True, name_suffix=u'', reviewers=None,
928 enable_notifications=True, name_suffix=u'', reviewers=None,
929 title=u"Test", description=u"Description"):
929 title=u"Test", description=u"Description"):
930 self.set_mergeable(mergeable)
930 self.set_mergeable(mergeable)
931 if not enable_notifications:
931 if not enable_notifications:
932 # mock notification side effect
932 # mock notification side effect
933 self.notification_patcher = mock.patch(
933 self.notification_patcher = mock.patch(
934 'rhodecode.model.notification.NotificationModel.create')
934 'rhodecode.model.notification.NotificationModel.create')
935 self.notification_patcher.start()
935 self.notification_patcher.start()
936
936
937 if not self.pull_request:
937 if not self.pull_request:
938 if not commits:
938 if not commits:
939 commits = [
939 commits = [
940 {'message': 'c1'},
940 {'message': 'c1'},
941 {'message': 'c2'},
941 {'message': 'c2'},
942 {'message': 'c3'},
942 {'message': 'c3'},
943 ]
943 ]
944 target_head = 'c1'
944 target_head = 'c1'
945 source_head = 'c2'
945 source_head = 'c2'
946 revisions = ['c2']
946 revisions = ['c2']
947
947
948 self.commit_ids = self.backend.create_master_repo(commits)
948 self.commit_ids = self.backend.create_master_repo(commits)
949 self.target_repository = self.backend.create_repo(
949 self.target_repository = self.backend.create_repo(
950 heads=[target_head], name_suffix=name_suffix)
950 heads=[target_head], name_suffix=name_suffix)
951 self.source_repository = self.backend.create_repo(
951 self.source_repository = self.backend.create_repo(
952 heads=[source_head], name_suffix=name_suffix)
952 heads=[source_head], name_suffix=name_suffix)
953 self.author = author or UserModel().get_by_username(
953 self.author = author or UserModel().get_by_username(
954 TEST_USER_ADMIN_LOGIN)
954 TEST_USER_ADMIN_LOGIN)
955
955
956 model = PullRequestModel()
956 model = PullRequestModel()
957 self.create_parameters = {
957 self.create_parameters = {
958 'created_by': self.author,
958 'created_by': self.author,
959 'source_repo': self.source_repository.repo_name,
959 'source_repo': self.source_repository.repo_name,
960 'source_ref': self._default_branch_reference(source_head),
960 'source_ref': self._default_branch_reference(source_head),
961 'target_repo': self.target_repository.repo_name,
961 'target_repo': self.target_repository.repo_name,
962 'target_ref': self._default_branch_reference(target_head),
962 'target_ref': self._default_branch_reference(target_head),
963 'revisions': [self.commit_ids[r] for r in revisions],
963 'revisions': [self.commit_ids[r] for r in revisions],
964 'reviewers': reviewers or self._get_reviewers(),
964 'reviewers': reviewers or self._get_reviewers(),
965 'title': title,
965 'title': title,
966 'description': description,
966 'description': description,
967 }
967 }
968 self.pull_request = model.create(**self.create_parameters)
968 self.pull_request = model.create(**self.create_parameters)
969 assert model.get_versions(self.pull_request) == []
969 assert model.get_versions(self.pull_request) == []
970
970
971 self.pull_request_id = self.pull_request.pull_request_id
971 self.pull_request_id = self.pull_request.pull_request_id
972
972
973 if approved:
973 if approved:
974 self.approve()
974 self.approve()
975
975
976 Session().add(self.pull_request)
976 Session().add(self.pull_request)
977 Session().commit()
977 Session().commit()
978
978
979 return self.pull_request
979 return self.pull_request
980
980
981 def approve(self):
981 def approve(self):
982 self.create_status_votes(
982 self.create_status_votes(
983 ChangesetStatus.STATUS_APPROVED,
983 ChangesetStatus.STATUS_APPROVED,
984 *self.pull_request.reviewers)
984 *self.pull_request.reviewers)
985
985
986 def close(self):
986 def close(self):
987 PullRequestModel().close_pull_request(self.pull_request, self.author)
987 PullRequestModel().close_pull_request(self.pull_request, self.author)
988
988
989 def _default_branch_reference(self, commit_message):
989 def _default_branch_reference(self, commit_message):
990 reference = '%s:%s:%s' % (
990 reference = '%s:%s:%s' % (
991 'branch',
991 'branch',
992 self.backend.default_branch_name,
992 self.backend.default_branch_name,
993 self.commit_ids[commit_message])
993 self.commit_ids[commit_message])
994 return reference
994 return reference
995
995
996 def _get_reviewers(self):
996 def _get_reviewers(self):
997 return [
997 return [
998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
1000 ]
1000 ]
1001
1001
1002 def update_source_repository(self, head=None):
1002 def update_source_repository(self, head=None):
1003 heads = [head or 'c3']
1003 heads = [head or 'c3']
1004 self.backend.pull_heads(self.source_repository, heads=heads)
1004 self.backend.pull_heads(self.source_repository, heads=heads)
1005
1005
1006 def add_one_commit(self, head=None):
1006 def add_one_commit(self, head=None):
1007 self.update_source_repository(head=head)
1007 self.update_source_repository(head=head)
1008 old_commit_ids = set(self.pull_request.revisions)
1008 old_commit_ids = set(self.pull_request.revisions)
1009 PullRequestModel().update_commits(self.pull_request)
1009 PullRequestModel().update_commits(self.pull_request)
1010 commit_ids = set(self.pull_request.revisions)
1010 commit_ids = set(self.pull_request.revisions)
1011 new_commit_ids = commit_ids - old_commit_ids
1011 new_commit_ids = commit_ids - old_commit_ids
1012 assert len(new_commit_ids) == 1
1012 assert len(new_commit_ids) == 1
1013 return new_commit_ids.pop()
1013 return new_commit_ids.pop()
1014
1014
1015 def remove_one_commit(self):
1015 def remove_one_commit(self):
1016 assert len(self.pull_request.revisions) == 2
1016 assert len(self.pull_request.revisions) == 2
1017 source_vcs = self.source_repository.scm_instance()
1017 source_vcs = self.source_repository.scm_instance()
1018 removed_commit_id = source_vcs.commit_ids[-1]
1018 removed_commit_id = source_vcs.commit_ids[-1]
1019
1019
1020 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1020 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1021 # remove the if once that's sorted out.
1021 # remove the if once that's sorted out.
1022 if self.backend.alias == "git":
1022 if self.backend.alias == "git":
1023 kwargs = {'branch_name': self.backend.default_branch_name}
1023 kwargs = {'branch_name': self.backend.default_branch_name}
1024 else:
1024 else:
1025 kwargs = {}
1025 kwargs = {}
1026 source_vcs.strip(removed_commit_id, **kwargs)
1026 source_vcs.strip(removed_commit_id, **kwargs)
1027
1027
1028 PullRequestModel().update_commits(self.pull_request)
1028 PullRequestModel().update_commits(self.pull_request)
1029 assert len(self.pull_request.revisions) == 1
1029 assert len(self.pull_request.revisions) == 1
1030 return removed_commit_id
1030 return removed_commit_id
1031
1031
1032 def create_comment(self, linked_to=None):
1032 def create_comment(self, linked_to=None):
1033 comment = CommentsModel().create(
1033 comment = CommentsModel().create(
1034 text=u"Test comment",
1034 text=u"Test comment",
1035 repo=self.target_repository.repo_name,
1035 repo=self.target_repository.repo_name,
1036 user=self.author,
1036 user=self.author,
1037 pull_request=self.pull_request)
1037 pull_request=self.pull_request)
1038 assert comment.pull_request_version_id is None
1038 assert comment.pull_request_version_id is None
1039
1039
1040 if linked_to:
1040 if linked_to:
1041 PullRequestModel()._link_comments_to_version(linked_to)
1041 PullRequestModel()._link_comments_to_version(linked_to)
1042
1042
1043 return comment
1043 return comment
1044
1044
1045 def create_inline_comment(
1045 def create_inline_comment(
1046 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1046 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1047 comment = CommentsModel().create(
1047 comment = CommentsModel().create(
1048 text=u"Test comment",
1048 text=u"Test comment",
1049 repo=self.target_repository.repo_name,
1049 repo=self.target_repository.repo_name,
1050 user=self.author,
1050 user=self.author,
1051 line_no=line_no,
1051 line_no=line_no,
1052 f_path=file_path,
1052 f_path=file_path,
1053 pull_request=self.pull_request)
1053 pull_request=self.pull_request)
1054 assert comment.pull_request_version_id is None
1054 assert comment.pull_request_version_id is None
1055
1055
1056 if linked_to:
1056 if linked_to:
1057 PullRequestModel()._link_comments_to_version(linked_to)
1057 PullRequestModel()._link_comments_to_version(linked_to)
1058
1058
1059 return comment
1059 return comment
1060
1060
1061 def create_version_of_pull_request(self):
1061 def create_version_of_pull_request(self):
1062 pull_request = self.create_pull_request()
1062 pull_request = self.create_pull_request()
1063 version = PullRequestModel()._create_version_from_snapshot(
1063 version = PullRequestModel()._create_version_from_snapshot(
1064 pull_request)
1064 pull_request)
1065 return version
1065 return version
1066
1066
1067 def create_status_votes(self, status, *reviewers):
1067 def create_status_votes(self, status, *reviewers):
1068 for reviewer in reviewers:
1068 for reviewer in reviewers:
1069 ChangesetStatusModel().set_status(
1069 ChangesetStatusModel().set_status(
1070 repo=self.pull_request.target_repo,
1070 repo=self.pull_request.target_repo,
1071 status=status,
1071 status=status,
1072 user=reviewer.user_id,
1072 user=reviewer.user_id,
1073 pull_request=self.pull_request)
1073 pull_request=self.pull_request)
1074
1074
1075 def set_mergeable(self, value):
1075 def set_mergeable(self, value):
1076 if not self.mergeable_patcher:
1076 if not self.mergeable_patcher:
1077 self.mergeable_patcher = mock.patch.object(
1077 self.mergeable_patcher = mock.patch.object(
1078 VcsSettingsModel, 'get_general_settings')
1078 VcsSettingsModel, 'get_general_settings')
1079 self.mergeable_mock = self.mergeable_patcher.start()
1079 self.mergeable_mock = self.mergeable_patcher.start()
1080 self.mergeable_mock.return_value = {
1080 self.mergeable_mock.return_value = {
1081 'rhodecode_pr_merge_enabled': value}
1081 'rhodecode_pr_merge_enabled': value}
1082
1082
1083 def cleanup(self):
1083 def cleanup(self):
1084 # In case the source repository is already cleaned up, the pull
1084 # In case the source repository is already cleaned up, the pull
1085 # request will already be deleted.
1085 # request will already be deleted.
1086 pull_request = PullRequest().get(self.pull_request_id)
1086 pull_request = PullRequest().get(self.pull_request_id)
1087 if pull_request:
1087 if pull_request:
1088 PullRequestModel().delete(pull_request, pull_request.author)
1088 PullRequestModel().delete(pull_request, pull_request.author)
1089 Session().commit()
1089 Session().commit()
1090
1090
1091 if self.notification_patcher:
1091 if self.notification_patcher:
1092 self.notification_patcher.stop()
1092 self.notification_patcher.stop()
1093
1093
1094 if self.mergeable_patcher:
1094 if self.mergeable_patcher:
1095 self.mergeable_patcher.stop()
1095 self.mergeable_patcher.stop()
1096
1096
1097
1097
1098 @pytest.fixture
1098 @pytest.fixture
1099 def user_admin(pylonsapp):
1099 def user_admin(pylonsapp):
1100 """
1100 """
1101 Provides the default admin test user as an instance of `db.User`.
1101 Provides the default admin test user as an instance of `db.User`.
1102 """
1102 """
1103 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1103 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1104 return user
1104 return user
1105
1105
1106
1106
1107 @pytest.fixture
1107 @pytest.fixture
1108 def user_regular(pylonsapp):
1108 def user_regular(pylonsapp):
1109 """
1109 """
1110 Provides the default regular test user as an instance of `db.User`.
1110 Provides the default regular test user as an instance of `db.User`.
1111 """
1111 """
1112 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1112 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1113 return user
1113 return user
1114
1114
1115
1115
1116 @pytest.fixture
1116 @pytest.fixture
1117 def user_util(request, pylonsapp):
1117 def user_util(request, pylonsapp):
1118 """
1118 """
1119 Provides a wired instance of `UserUtility` with integrated cleanup.
1119 Provides a wired instance of `UserUtility` with integrated cleanup.
1120 """
1120 """
1121 utility = UserUtility(test_name=request.node.name)
1121 utility = UserUtility(test_name=request.node.name)
1122 request.addfinalizer(utility.cleanup)
1122 request.addfinalizer(utility.cleanup)
1123 return utility
1123 return utility
1124
1124
1125
1125
1126 # TODO: johbo: Split this up into utilities per domain or something similar
1126 # TODO: johbo: Split this up into utilities per domain or something similar
1127 class UserUtility(object):
1127 class UserUtility(object):
1128
1128
1129 def __init__(self, test_name="test"):
1129 def __init__(self, test_name="test"):
1130 self._test_name = self._sanitize_name(test_name)
1130 self._test_name = self._sanitize_name(test_name)
1131 self.fixture = Fixture()
1131 self.fixture = Fixture()
1132 self.repo_group_ids = []
1132 self.repo_group_ids = []
1133 self.repos_ids = []
1133 self.repos_ids = []
1134 self.user_ids = []
1134 self.user_ids = []
1135 self.user_group_ids = []
1135 self.user_group_ids = []
1136 self.user_repo_permission_ids = []
1136 self.user_repo_permission_ids = []
1137 self.user_group_repo_permission_ids = []
1137 self.user_group_repo_permission_ids = []
1138 self.user_repo_group_permission_ids = []
1138 self.user_repo_group_permission_ids = []
1139 self.user_group_repo_group_permission_ids = []
1139 self.user_group_repo_group_permission_ids = []
1140 self.user_user_group_permission_ids = []
1140 self.user_user_group_permission_ids = []
1141 self.user_group_user_group_permission_ids = []
1141 self.user_group_user_group_permission_ids = []
1142 self.user_permissions = []
1142 self.user_permissions = []
1143
1143
1144 def _sanitize_name(self, name):
1144 def _sanitize_name(self, name):
1145 for char in ['[', ']']:
1145 for char in ['[', ']']:
1146 name = name.replace(char, '_')
1146 name = name.replace(char, '_')
1147 return name
1147 return name
1148
1148
1149 def create_repo_group(
1149 def create_repo_group(
1150 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1150 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1151 group_name = "{prefix}_repogroup_{count}".format(
1151 group_name = "{prefix}_repogroup_{count}".format(
1152 prefix=self._test_name,
1152 prefix=self._test_name,
1153 count=len(self.repo_group_ids))
1153 count=len(self.repo_group_ids))
1154 repo_group = self.fixture.create_repo_group(
1154 repo_group = self.fixture.create_repo_group(
1155 group_name, cur_user=owner)
1155 group_name, cur_user=owner)
1156 if auto_cleanup:
1156 if auto_cleanup:
1157 self.repo_group_ids.append(repo_group.group_id)
1157 self.repo_group_ids.append(repo_group.group_id)
1158 return repo_group
1158 return repo_group
1159
1159
1160 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None,
1160 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None,
1161 auto_cleanup=True, repo_type='hg'):
1161 auto_cleanup=True, repo_type='hg'):
1162 repo_name = "{prefix}_repository_{count}".format(
1162 repo_name = "{prefix}_repository_{count}".format(
1163 prefix=self._test_name,
1163 prefix=self._test_name,
1164 count=len(self.repos_ids))
1164 count=len(self.repos_ids))
1165
1165
1166 repository = self.fixture.create_repo(
1166 repository = self.fixture.create_repo(
1167 repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type)
1167 repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type)
1168 if auto_cleanup:
1168 if auto_cleanup:
1169 self.repos_ids.append(repository.repo_id)
1169 self.repos_ids.append(repository.repo_id)
1170 return repository
1170 return repository
1171
1171
1172 def create_user(self, auto_cleanup=True, **kwargs):
1172 def create_user(self, auto_cleanup=True, **kwargs):
1173 user_name = "{prefix}_user_{count}".format(
1173 user_name = "{prefix}_user_{count}".format(
1174 prefix=self._test_name,
1174 prefix=self._test_name,
1175 count=len(self.user_ids))
1175 count=len(self.user_ids))
1176 user = self.fixture.create_user(user_name, **kwargs)
1176 user = self.fixture.create_user(user_name, **kwargs)
1177 if auto_cleanup:
1177 if auto_cleanup:
1178 self.user_ids.append(user.user_id)
1178 self.user_ids.append(user.user_id)
1179 return user
1179 return user
1180
1180
1181 def create_user_with_group(self):
1181 def create_user_with_group(self):
1182 user = self.create_user()
1182 user = self.create_user()
1183 user_group = self.create_user_group(members=[user])
1183 user_group = self.create_user_group(members=[user])
1184 return user, user_group
1184 return user, user_group
1185
1185
1186 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1186 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1187 auto_cleanup=True, **kwargs):
1187 auto_cleanup=True, **kwargs):
1188 group_name = "{prefix}_usergroup_{count}".format(
1188 group_name = "{prefix}_usergroup_{count}".format(
1189 prefix=self._test_name,
1189 prefix=self._test_name,
1190 count=len(self.user_group_ids))
1190 count=len(self.user_group_ids))
1191 user_group = self.fixture.create_user_group(
1191 user_group = self.fixture.create_user_group(
1192 group_name, cur_user=owner, **kwargs)
1192 group_name, cur_user=owner, **kwargs)
1193
1193
1194 if auto_cleanup:
1194 if auto_cleanup:
1195 self.user_group_ids.append(user_group.users_group_id)
1195 self.user_group_ids.append(user_group.users_group_id)
1196 if members:
1196 if members:
1197 for user in members:
1197 for user in members:
1198 UserGroupModel().add_user_to_group(user_group, user)
1198 UserGroupModel().add_user_to_group(user_group, user)
1199 return user_group
1199 return user_group
1200
1200
1201 def grant_user_permission(self, user_name, permission_name):
1201 def grant_user_permission(self, user_name, permission_name):
1202 self._inherit_default_user_permissions(user_name, False)
1202 self._inherit_default_user_permissions(user_name, False)
1203 self.user_permissions.append((user_name, permission_name))
1203 self.user_permissions.append((user_name, permission_name))
1204
1204
1205 def grant_user_permission_to_repo_group(
1205 def grant_user_permission_to_repo_group(
1206 self, repo_group, user, permission_name):
1206 self, repo_group, user, permission_name):
1207 permission = RepoGroupModel().grant_user_permission(
1207 permission = RepoGroupModel().grant_user_permission(
1208 repo_group, user, permission_name)
1208 repo_group, user, permission_name)
1209 self.user_repo_group_permission_ids.append(
1209 self.user_repo_group_permission_ids.append(
1210 (repo_group.group_id, user.user_id))
1210 (repo_group.group_id, user.user_id))
1211 return permission
1211 return permission
1212
1212
1213 def grant_user_group_permission_to_repo_group(
1213 def grant_user_group_permission_to_repo_group(
1214 self, repo_group, user_group, permission_name):
1214 self, repo_group, user_group, permission_name):
1215 permission = RepoGroupModel().grant_user_group_permission(
1215 permission = RepoGroupModel().grant_user_group_permission(
1216 repo_group, user_group, permission_name)
1216 repo_group, user_group, permission_name)
1217 self.user_group_repo_group_permission_ids.append(
1217 self.user_group_repo_group_permission_ids.append(
1218 (repo_group.group_id, user_group.users_group_id))
1218 (repo_group.group_id, user_group.users_group_id))
1219 return permission
1219 return permission
1220
1220
1221 def grant_user_permission_to_repo(
1221 def grant_user_permission_to_repo(
1222 self, repo, user, permission_name):
1222 self, repo, user, permission_name):
1223 permission = RepoModel().grant_user_permission(
1223 permission = RepoModel().grant_user_permission(
1224 repo, user, permission_name)
1224 repo, user, permission_name)
1225 self.user_repo_permission_ids.append(
1225 self.user_repo_permission_ids.append(
1226 (repo.repo_id, user.user_id))
1226 (repo.repo_id, user.user_id))
1227 return permission
1227 return permission
1228
1228
1229 def grant_user_group_permission_to_repo(
1229 def grant_user_group_permission_to_repo(
1230 self, repo, user_group, permission_name):
1230 self, repo, user_group, permission_name):
1231 permission = RepoModel().grant_user_group_permission(
1231 permission = RepoModel().grant_user_group_permission(
1232 repo, user_group, permission_name)
1232 repo, user_group, permission_name)
1233 self.user_group_repo_permission_ids.append(
1233 self.user_group_repo_permission_ids.append(
1234 (repo.repo_id, user_group.users_group_id))
1234 (repo.repo_id, user_group.users_group_id))
1235 return permission
1235 return permission
1236
1236
1237 def grant_user_permission_to_user_group(
1237 def grant_user_permission_to_user_group(
1238 self, target_user_group, user, permission_name):
1238 self, target_user_group, user, permission_name):
1239 permission = UserGroupModel().grant_user_permission(
1239 permission = UserGroupModel().grant_user_permission(
1240 target_user_group, user, permission_name)
1240 target_user_group, user, permission_name)
1241 self.user_user_group_permission_ids.append(
1241 self.user_user_group_permission_ids.append(
1242 (target_user_group.users_group_id, user.user_id))
1242 (target_user_group.users_group_id, user.user_id))
1243 return permission
1243 return permission
1244
1244
1245 def grant_user_group_permission_to_user_group(
1245 def grant_user_group_permission_to_user_group(
1246 self, target_user_group, user_group, permission_name):
1246 self, target_user_group, user_group, permission_name):
1247 permission = UserGroupModel().grant_user_group_permission(
1247 permission = UserGroupModel().grant_user_group_permission(
1248 target_user_group, user_group, permission_name)
1248 target_user_group, user_group, permission_name)
1249 self.user_group_user_group_permission_ids.append(
1249 self.user_group_user_group_permission_ids.append(
1250 (target_user_group.users_group_id, user_group.users_group_id))
1250 (target_user_group.users_group_id, user_group.users_group_id))
1251 return permission
1251 return permission
1252
1252
1253 def revoke_user_permission(self, user_name, permission_name):
1253 def revoke_user_permission(self, user_name, permission_name):
1254 self._inherit_default_user_permissions(user_name, True)
1254 self._inherit_default_user_permissions(user_name, True)
1255 UserModel().revoke_perm(user_name, permission_name)
1255 UserModel().revoke_perm(user_name, permission_name)
1256
1256
1257 def _inherit_default_user_permissions(self, user_name, value):
1257 def _inherit_default_user_permissions(self, user_name, value):
1258 user = UserModel().get_by_username(user_name)
1258 user = UserModel().get_by_username(user_name)
1259 user.inherit_default_permissions = value
1259 user.inherit_default_permissions = value
1260 Session().add(user)
1260 Session().add(user)
1261 Session().commit()
1261 Session().commit()
1262
1262
1263 def cleanup(self):
1263 def cleanup(self):
1264 self._cleanup_permissions()
1264 self._cleanup_permissions()
1265 self._cleanup_repos()
1265 self._cleanup_repos()
1266 self._cleanup_repo_groups()
1266 self._cleanup_repo_groups()
1267 self._cleanup_user_groups()
1267 self._cleanup_user_groups()
1268 self._cleanup_users()
1268 self._cleanup_users()
1269
1269
1270 def _cleanup_permissions(self):
1270 def _cleanup_permissions(self):
1271 if self.user_permissions:
1271 if self.user_permissions:
1272 for user_name, permission_name in self.user_permissions:
1272 for user_name, permission_name in self.user_permissions:
1273 self.revoke_user_permission(user_name, permission_name)
1273 self.revoke_user_permission(user_name, permission_name)
1274
1274
1275 for permission in self.user_repo_permission_ids:
1275 for permission in self.user_repo_permission_ids:
1276 RepoModel().revoke_user_permission(*permission)
1276 RepoModel().revoke_user_permission(*permission)
1277
1277
1278 for permission in self.user_group_repo_permission_ids:
1278 for permission in self.user_group_repo_permission_ids:
1279 RepoModel().revoke_user_group_permission(*permission)
1279 RepoModel().revoke_user_group_permission(*permission)
1280
1280
1281 for permission in self.user_repo_group_permission_ids:
1281 for permission in self.user_repo_group_permission_ids:
1282 RepoGroupModel().revoke_user_permission(*permission)
1282 RepoGroupModel().revoke_user_permission(*permission)
1283
1283
1284 for permission in self.user_group_repo_group_permission_ids:
1284 for permission in self.user_group_repo_group_permission_ids:
1285 RepoGroupModel().revoke_user_group_permission(*permission)
1285 RepoGroupModel().revoke_user_group_permission(*permission)
1286
1286
1287 for permission in self.user_user_group_permission_ids:
1287 for permission in self.user_user_group_permission_ids:
1288 UserGroupModel().revoke_user_permission(*permission)
1288 UserGroupModel().revoke_user_permission(*permission)
1289
1289
1290 for permission in self.user_group_user_group_permission_ids:
1290 for permission in self.user_group_user_group_permission_ids:
1291 UserGroupModel().revoke_user_group_permission(*permission)
1291 UserGroupModel().revoke_user_group_permission(*permission)
1292
1292
1293 def _cleanup_repo_groups(self):
1293 def _cleanup_repo_groups(self):
1294 def _repo_group_compare(first_group_id, second_group_id):
1294 def _repo_group_compare(first_group_id, second_group_id):
1295 """
1295 """
1296 Gives higher priority to the groups with the most complex paths
1296 Gives higher priority to the groups with the most complex paths
1297 """
1297 """
1298 first_group = RepoGroup.get(first_group_id)
1298 first_group = RepoGroup.get(first_group_id)
1299 second_group = RepoGroup.get(second_group_id)
1299 second_group = RepoGroup.get(second_group_id)
1300 first_group_parts = (
1300 first_group_parts = (
1301 len(first_group.group_name.split('/')) if first_group else 0)
1301 len(first_group.group_name.split('/')) if first_group else 0)
1302 second_group_parts = (
1302 second_group_parts = (
1303 len(second_group.group_name.split('/')) if second_group else 0)
1303 len(second_group.group_name.split('/')) if second_group else 0)
1304 return cmp(second_group_parts, first_group_parts)
1304 return cmp(second_group_parts, first_group_parts)
1305
1305
1306 sorted_repo_group_ids = sorted(
1306 sorted_repo_group_ids = sorted(
1307 self.repo_group_ids, cmp=_repo_group_compare)
1307 self.repo_group_ids, cmp=_repo_group_compare)
1308 for repo_group_id in sorted_repo_group_ids:
1308 for repo_group_id in sorted_repo_group_ids:
1309 self.fixture.destroy_repo_group(repo_group_id)
1309 self.fixture.destroy_repo_group(repo_group_id)
1310
1310
1311 def _cleanup_repos(self):
1311 def _cleanup_repos(self):
1312 sorted_repos_ids = sorted(self.repos_ids)
1312 sorted_repos_ids = sorted(self.repos_ids)
1313 for repo_id in sorted_repos_ids:
1313 for repo_id in sorted_repos_ids:
1314 self.fixture.destroy_repo(repo_id)
1314 self.fixture.destroy_repo(repo_id)
1315
1315
1316 def _cleanup_user_groups(self):
1316 def _cleanup_user_groups(self):
1317 def _user_group_compare(first_group_id, second_group_id):
1317 def _user_group_compare(first_group_id, second_group_id):
1318 """
1318 """
1319 Gives higher priority to the groups with the most complex paths
1319 Gives higher priority to the groups with the most complex paths
1320 """
1320 """
1321 first_group = UserGroup.get(first_group_id)
1321 first_group = UserGroup.get(first_group_id)
1322 second_group = UserGroup.get(second_group_id)
1322 second_group = UserGroup.get(second_group_id)
1323 first_group_parts = (
1323 first_group_parts = (
1324 len(first_group.users_group_name.split('/'))
1324 len(first_group.users_group_name.split('/'))
1325 if first_group else 0)
1325 if first_group else 0)
1326 second_group_parts = (
1326 second_group_parts = (
1327 len(second_group.users_group_name.split('/'))
1327 len(second_group.users_group_name.split('/'))
1328 if second_group else 0)
1328 if second_group else 0)
1329 return cmp(second_group_parts, first_group_parts)
1329 return cmp(second_group_parts, first_group_parts)
1330
1330
1331 sorted_user_group_ids = sorted(
1331 sorted_user_group_ids = sorted(
1332 self.user_group_ids, cmp=_user_group_compare)
1332 self.user_group_ids, cmp=_user_group_compare)
1333 for user_group_id in sorted_user_group_ids:
1333 for user_group_id in sorted_user_group_ids:
1334 self.fixture.destroy_user_group(user_group_id)
1334 self.fixture.destroy_user_group(user_group_id)
1335
1335
1336 def _cleanup_users(self):
1336 def _cleanup_users(self):
1337 for user_id in self.user_ids:
1337 for user_id in self.user_ids:
1338 self.fixture.destroy_user(user_id)
1338 self.fixture.destroy_user(user_id)
1339
1339
1340
1340
1341 # TODO: Think about moving this into a pytest-pyro package and make it a
1341 # TODO: Think about moving this into a pytest-pyro package and make it a
1342 # pytest plugin
1342 # pytest plugin
1343 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1343 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1344 def pytest_runtest_makereport(item, call):
1344 def pytest_runtest_makereport(item, call):
1345 """
1345 """
1346 Adding the remote traceback if the exception has this information.
1346 Adding the remote traceback if the exception has this information.
1347
1347
1348 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1348 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1349 to the exception instance.
1349 to the exception instance.
1350 """
1350 """
1351 outcome = yield
1351 outcome = yield
1352 report = outcome.get_result()
1352 report = outcome.get_result()
1353 if call.excinfo:
1353 if call.excinfo:
1354 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1354 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1355
1355
1356
1356
1357 def _add_vcsserver_remote_traceback(report, exc):
1357 def _add_vcsserver_remote_traceback(report, exc):
1358 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1358 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1359
1359
1360 if vcsserver_traceback:
1360 if vcsserver_traceback:
1361 section = 'VCSServer remote traceback ' + report.when
1361 section = 'VCSServer remote traceback ' + report.when
1362 report.sections.append((section, vcsserver_traceback))
1362 report.sections.append((section, vcsserver_traceback))
1363
1363
1364
1364
1365 @pytest.fixture(scope='session')
1365 @pytest.fixture(scope='session')
1366 def testrun():
1366 def testrun():
1367 return {
1367 return {
1368 'uuid': uuid.uuid4(),
1368 'uuid': uuid.uuid4(),
1369 'start': datetime.datetime.utcnow().isoformat(),
1369 'start': datetime.datetime.utcnow().isoformat(),
1370 'timestamp': int(time.time()),
1370 'timestamp': int(time.time()),
1371 }
1371 }
1372
1372
1373
1373
1374 @pytest.fixture(autouse=True)
1374 @pytest.fixture(autouse=True)
1375 def collect_appenlight_stats(request, testrun):
1375 def collect_appenlight_stats(request, testrun):
1376 """
1376 """
1377 This fixture reports memory consumtion of single tests.
1377 This fixture reports memory consumtion of single tests.
1378
1378
1379 It gathers data based on `psutil` and sends them to Appenlight. The option
1379 It gathers data based on `psutil` and sends them to Appenlight. The option
1380 ``--ae`` has te be used to enable this fixture and the API key for your
1380 ``--ae`` has te be used to enable this fixture and the API key for your
1381 application has to be provided in ``--ae-key``.
1381 application has to be provided in ``--ae-key``.
1382 """
1382 """
1383 try:
1383 try:
1384 # cygwin cannot have yet psutil support.
1384 # cygwin cannot have yet psutil support.
1385 import psutil
1385 import psutil
1386 except ImportError:
1386 except ImportError:
1387 return
1387 return
1388
1388
1389 if not request.config.getoption('--appenlight'):
1389 if not request.config.getoption('--appenlight'):
1390 return
1390 return
1391 else:
1391 else:
1392 # Only request the pylonsapp fixture if appenlight tracking is
1392 # Only request the pylonsapp fixture if appenlight tracking is
1393 # enabled. This will speed up a test run of unit tests by 2 to 3
1393 # enabled. This will speed up a test run of unit tests by 2 to 3
1394 # seconds if appenlight is not enabled.
1394 # seconds if appenlight is not enabled.
1395 pylonsapp = request.getfuncargvalue("pylonsapp")
1395 pylonsapp = request.getfuncargvalue("pylonsapp")
1396 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1396 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1397 client = AppenlightClient(
1397 client = AppenlightClient(
1398 url=url,
1398 url=url,
1399 api_key=request.config.getoption('--appenlight-api-key'),
1399 api_key=request.config.getoption('--appenlight-api-key'),
1400 namespace=request.node.nodeid,
1400 namespace=request.node.nodeid,
1401 request=str(testrun['uuid']),
1401 request=str(testrun['uuid']),
1402 testrun=testrun)
1402 testrun=testrun)
1403
1403
1404 client.collect({
1404 client.collect({
1405 'message': "Starting",
1405 'message': "Starting",
1406 })
1406 })
1407
1407
1408 server_and_port = pylonsapp.config['vcs.server']
1408 server_and_port = pylonsapp.config['vcs.server']
1409 protocol = pylonsapp.config['vcs.server.protocol']
1409 protocol = pylonsapp.config['vcs.server.protocol']
1410 server = create_vcsserver_proxy(server_and_port, protocol)
1410 server = create_vcsserver_proxy(server_and_port, protocol)
1411 with server:
1411 with server:
1412 vcs_pid = server.get_pid()
1412 vcs_pid = server.get_pid()
1413 server.run_gc()
1413 server.run_gc()
1414 vcs_process = psutil.Process(vcs_pid)
1414 vcs_process = psutil.Process(vcs_pid)
1415 mem = vcs_process.memory_info()
1415 mem = vcs_process.memory_info()
1416 client.tag_before('vcsserver.rss', mem.rss)
1416 client.tag_before('vcsserver.rss', mem.rss)
1417 client.tag_before('vcsserver.vms', mem.vms)
1417 client.tag_before('vcsserver.vms', mem.vms)
1418
1418
1419 test_process = psutil.Process()
1419 test_process = psutil.Process()
1420 mem = test_process.memory_info()
1420 mem = test_process.memory_info()
1421 client.tag_before('test.rss', mem.rss)
1421 client.tag_before('test.rss', mem.rss)
1422 client.tag_before('test.vms', mem.vms)
1422 client.tag_before('test.vms', mem.vms)
1423
1423
1424 client.tag_before('time', time.time())
1424 client.tag_before('time', time.time())
1425
1425
1426 @request.addfinalizer
1426 @request.addfinalizer
1427 def send_stats():
1427 def send_stats():
1428 client.tag_after('time', time.time())
1428 client.tag_after('time', time.time())
1429 with server:
1429 with server:
1430 gc_stats = server.run_gc()
1430 gc_stats = server.run_gc()
1431 for tag, value in gc_stats.items():
1431 for tag, value in gc_stats.items():
1432 client.tag_after(tag, value)
1432 client.tag_after(tag, value)
1433 mem = vcs_process.memory_info()
1433 mem = vcs_process.memory_info()
1434 client.tag_after('vcsserver.rss', mem.rss)
1434 client.tag_after('vcsserver.rss', mem.rss)
1435 client.tag_after('vcsserver.vms', mem.vms)
1435 client.tag_after('vcsserver.vms', mem.vms)
1436
1436
1437 mem = test_process.memory_info()
1437 mem = test_process.memory_info()
1438 client.tag_after('test.rss', mem.rss)
1438 client.tag_after('test.rss', mem.rss)
1439 client.tag_after('test.vms', mem.vms)
1439 client.tag_after('test.vms', mem.vms)
1440
1440
1441 client.collect({
1441 client.collect({
1442 'message': "Finished",
1442 'message': "Finished",
1443 })
1443 })
1444 client.send_stats()
1444 client.send_stats()
1445
1445
1446 return client
1446 return client
1447
1447
1448
1448
1449 class AppenlightClient():
1449 class AppenlightClient():
1450
1450
1451 url_template = '{url}?protocol_version=0.5'
1451 url_template = '{url}?protocol_version=0.5'
1452
1452
1453 def __init__(
1453 def __init__(
1454 self, url, api_key, add_server=True, add_timestamp=True,
1454 self, url, api_key, add_server=True, add_timestamp=True,
1455 namespace=None, request=None, testrun=None):
1455 namespace=None, request=None, testrun=None):
1456 self.url = self.url_template.format(url=url)
1456 self.url = self.url_template.format(url=url)
1457 self.api_key = api_key
1457 self.api_key = api_key
1458 self.add_server = add_server
1458 self.add_server = add_server
1459 self.add_timestamp = add_timestamp
1459 self.add_timestamp = add_timestamp
1460 self.namespace = namespace
1460 self.namespace = namespace
1461 self.request = request
1461 self.request = request
1462 self.server = socket.getfqdn(socket.gethostname())
1462 self.server = socket.getfqdn(socket.gethostname())
1463 self.tags_before = {}
1463 self.tags_before = {}
1464 self.tags_after = {}
1464 self.tags_after = {}
1465 self.stats = []
1465 self.stats = []
1466 self.testrun = testrun or {}
1466 self.testrun = testrun or {}
1467
1467
1468 def tag_before(self, tag, value):
1468 def tag_before(self, tag, value):
1469 self.tags_before[tag] = value
1469 self.tags_before[tag] = value
1470
1470
1471 def tag_after(self, tag, value):
1471 def tag_after(self, tag, value):
1472 self.tags_after[tag] = value
1472 self.tags_after[tag] = value
1473
1473
1474 def collect(self, data):
1474 def collect(self, data):
1475 if self.add_server:
1475 if self.add_server:
1476 data.setdefault('server', self.server)
1476 data.setdefault('server', self.server)
1477 if self.add_timestamp:
1477 if self.add_timestamp:
1478 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1478 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1479 if self.namespace:
1479 if self.namespace:
1480 data.setdefault('namespace', self.namespace)
1480 data.setdefault('namespace', self.namespace)
1481 if self.request:
1481 if self.request:
1482 data.setdefault('request', self.request)
1482 data.setdefault('request', self.request)
1483 self.stats.append(data)
1483 self.stats.append(data)
1484
1484
1485 def send_stats(self):
1485 def send_stats(self):
1486 tags = [
1486 tags = [
1487 ('testrun', self.request),
1487 ('testrun', self.request),
1488 ('testrun.start', self.testrun['start']),
1488 ('testrun.start', self.testrun['start']),
1489 ('testrun.timestamp', self.testrun['timestamp']),
1489 ('testrun.timestamp', self.testrun['timestamp']),
1490 ('test', self.namespace),
1490 ('test', self.namespace),
1491 ]
1491 ]
1492 for key, value in self.tags_before.items():
1492 for key, value in self.tags_before.items():
1493 tags.append((key + '.before', value))
1493 tags.append((key + '.before', value))
1494 try:
1494 try:
1495 delta = self.tags_after[key] - value
1495 delta = self.tags_after[key] - value
1496 tags.append((key + '.delta', delta))
1496 tags.append((key + '.delta', delta))
1497 except Exception:
1497 except Exception:
1498 pass
1498 pass
1499 for key, value in self.tags_after.items():
1499 for key, value in self.tags_after.items():
1500 tags.append((key + '.after', value))
1500 tags.append((key + '.after', value))
1501 self.collect({
1501 self.collect({
1502 'message': "Collected tags",
1502 'message': "Collected tags",
1503 'tags': tags,
1503 'tags': tags,
1504 })
1504 })
1505
1505
1506 response = requests.post(
1506 response = requests.post(
1507 self.url,
1507 self.url,
1508 headers={
1508 headers={
1509 'X-appenlight-api-key': self.api_key},
1509 'X-appenlight-api-key': self.api_key},
1510 json=self.stats,
1510 json=self.stats,
1511 )
1511 )
1512
1512
1513 if not response.status_code == 200:
1513 if not response.status_code == 200:
1514 pprint.pprint(self.stats)
1514 pprint.pprint(self.stats)
1515 print response.headers
1515 print response.headers
1516 print response.text
1516 print response.text
1517 raise Exception('Sending to appenlight failed')
1517 raise Exception('Sending to appenlight failed')
1518
1518
1519
1519
1520 @pytest.fixture
1520 @pytest.fixture
1521 def gist_util(request, pylonsapp):
1521 def gist_util(request, pylonsapp):
1522 """
1522 """
1523 Provides a wired instance of `GistUtility` with integrated cleanup.
1523 Provides a wired instance of `GistUtility` with integrated cleanup.
1524 """
1524 """
1525 utility = GistUtility()
1525 utility = GistUtility()
1526 request.addfinalizer(utility.cleanup)
1526 request.addfinalizer(utility.cleanup)
1527 return utility
1527 return utility
1528
1528
1529
1529
1530 class GistUtility(object):
1530 class GistUtility(object):
1531 def __init__(self):
1531 def __init__(self):
1532 self.fixture = Fixture()
1532 self.fixture = Fixture()
1533 self.gist_ids = []
1533 self.gist_ids = []
1534
1534
1535 def create_gist(self, **kwargs):
1535 def create_gist(self, **kwargs):
1536 gist = self.fixture.create_gist(**kwargs)
1536 gist = self.fixture.create_gist(**kwargs)
1537 self.gist_ids.append(gist.gist_id)
1537 self.gist_ids.append(gist.gist_id)
1538 return gist
1538 return gist
1539
1539
1540 def cleanup(self):
1540 def cleanup(self):
1541 for id_ in self.gist_ids:
1541 for id_ in self.gist_ids:
1542 self.fixture.destroy_gists(str(id_))
1542 self.fixture.destroy_gists(str(id_))
1543
1543
1544
1544
1545 @pytest.fixture
1545 @pytest.fixture
1546 def enabled_backends(request):
1546 def enabled_backends(request):
1547 backends = request.config.option.backends
1547 backends = request.config.option.backends
1548 return backends[:]
1548 return backends[:]
1549
1549
1550
1550
1551 @pytest.fixture
1551 @pytest.fixture
1552 def settings_util(request):
1552 def settings_util(request):
1553 """
1553 """
1554 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1554 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1555 """
1555 """
1556 utility = SettingsUtility()
1556 utility = SettingsUtility()
1557 request.addfinalizer(utility.cleanup)
1557 request.addfinalizer(utility.cleanup)
1558 return utility
1558 return utility
1559
1559
1560
1560
1561 class SettingsUtility(object):
1561 class SettingsUtility(object):
1562 def __init__(self):
1562 def __init__(self):
1563 self.rhodecode_ui_ids = []
1563 self.rhodecode_ui_ids = []
1564 self.rhodecode_setting_ids = []
1564 self.rhodecode_setting_ids = []
1565 self.repo_rhodecode_ui_ids = []
1565 self.repo_rhodecode_ui_ids = []
1566 self.repo_rhodecode_setting_ids = []
1566 self.repo_rhodecode_setting_ids = []
1567
1567
1568 def create_repo_rhodecode_ui(
1568 def create_repo_rhodecode_ui(
1569 self, repo, section, value, key=None, active=True, cleanup=True):
1569 self, repo, section, value, key=None, active=True, cleanup=True):
1570 key = key or hashlib.sha1(
1570 key = key or hashlib.sha1(
1571 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1571 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1572
1572
1573 setting = RepoRhodeCodeUi()
1573 setting = RepoRhodeCodeUi()
1574 setting.repository_id = repo.repo_id
1574 setting.repository_id = repo.repo_id
1575 setting.ui_section = section
1575 setting.ui_section = section
1576 setting.ui_value = value
1576 setting.ui_value = value
1577 setting.ui_key = key
1577 setting.ui_key = key
1578 setting.ui_active = active
1578 setting.ui_active = active
1579 Session().add(setting)
1579 Session().add(setting)
1580 Session().commit()
1580 Session().commit()
1581
1581
1582 if cleanup:
1582 if cleanup:
1583 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1583 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1584 return setting
1584 return setting
1585
1585
1586 def create_rhodecode_ui(
1586 def create_rhodecode_ui(
1587 self, section, value, key=None, active=True, cleanup=True):
1587 self, section, value, key=None, active=True, cleanup=True):
1588 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1588 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1589
1589
1590 setting = RhodeCodeUi()
1590 setting = RhodeCodeUi()
1591 setting.ui_section = section
1591 setting.ui_section = section
1592 setting.ui_value = value
1592 setting.ui_value = value
1593 setting.ui_key = key
1593 setting.ui_key = key
1594 setting.ui_active = active
1594 setting.ui_active = active
1595 Session().add(setting)
1595 Session().add(setting)
1596 Session().commit()
1596 Session().commit()
1597
1597
1598 if cleanup:
1598 if cleanup:
1599 self.rhodecode_ui_ids.append(setting.ui_id)
1599 self.rhodecode_ui_ids.append(setting.ui_id)
1600 return setting
1600 return setting
1601
1601
1602 def create_repo_rhodecode_setting(
1602 def create_repo_rhodecode_setting(
1603 self, repo, name, value, type_, cleanup=True):
1603 self, repo, name, value, type_, cleanup=True):
1604 setting = RepoRhodeCodeSetting(
1604 setting = RepoRhodeCodeSetting(
1605 repo.repo_id, key=name, val=value, type=type_)
1605 repo.repo_id, key=name, val=value, type=type_)
1606 Session().add(setting)
1606 Session().add(setting)
1607 Session().commit()
1607 Session().commit()
1608
1608
1609 if cleanup:
1609 if cleanup:
1610 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1610 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1611 return setting
1611 return setting
1612
1612
1613 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1613 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1614 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1614 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1615 Session().add(setting)
1615 Session().add(setting)
1616 Session().commit()
1616 Session().commit()
1617
1617
1618 if cleanup:
1618 if cleanup:
1619 self.rhodecode_setting_ids.append(setting.app_settings_id)
1619 self.rhodecode_setting_ids.append(setting.app_settings_id)
1620
1620
1621 return setting
1621 return setting
1622
1622
1623 def cleanup(self):
1623 def cleanup(self):
1624 for id_ in self.rhodecode_ui_ids:
1624 for id_ in self.rhodecode_ui_ids:
1625 setting = RhodeCodeUi.get(id_)
1625 setting = RhodeCodeUi.get(id_)
1626 Session().delete(setting)
1626 Session().delete(setting)
1627
1627
1628 for id_ in self.rhodecode_setting_ids:
1628 for id_ in self.rhodecode_setting_ids:
1629 setting = RhodeCodeSetting.get(id_)
1629 setting = RhodeCodeSetting.get(id_)
1630 Session().delete(setting)
1630 Session().delete(setting)
1631
1631
1632 for id_ in self.repo_rhodecode_ui_ids:
1632 for id_ in self.repo_rhodecode_ui_ids:
1633 setting = RepoRhodeCodeUi.get(id_)
1633 setting = RepoRhodeCodeUi.get(id_)
1634 Session().delete(setting)
1634 Session().delete(setting)
1635
1635
1636 for id_ in self.repo_rhodecode_setting_ids:
1636 for id_ in self.repo_rhodecode_setting_ids:
1637 setting = RepoRhodeCodeSetting.get(id_)
1637 setting = RepoRhodeCodeSetting.get(id_)
1638 Session().delete(setting)
1638 Session().delete(setting)
1639
1639
1640 Session().commit()
1640 Session().commit()
1641
1641
1642
1642
1643 @pytest.fixture
1643 @pytest.fixture
1644 def no_notifications(request):
1644 def no_notifications(request):
1645 notification_patcher = mock.patch(
1645 notification_patcher = mock.patch(
1646 'rhodecode.model.notification.NotificationModel.create')
1646 'rhodecode.model.notification.NotificationModel.create')
1647 notification_patcher.start()
1647 notification_patcher.start()
1648 request.addfinalizer(notification_patcher.stop)
1648 request.addfinalizer(notification_patcher.stop)
1649
1649
1650
1650
1651 @pytest.fixture(scope='session')
1651 @pytest.fixture(scope='session')
1652 def repeat(request):
1652 def repeat(request):
1653 """
1653 """
1654 The number of repetitions is based on this fixture.
1654 The number of repetitions is based on this fixture.
1655
1655
1656 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1656 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1657 tests are not too slow in our default test suite.
1657 tests are not too slow in our default test suite.
1658 """
1658 """
1659 return request.config.getoption('--repeat')
1659 return request.config.getoption('--repeat')
1660
1660
1661
1661
1662 @pytest.fixture
1662 @pytest.fixture
1663 def rhodecode_fixtures():
1663 def rhodecode_fixtures():
1664 return Fixture()
1664 return Fixture()
1665
1665
1666
1666
1667 @pytest.fixture
1667 @pytest.fixture
1668 def request_stub():
1668 def request_stub():
1669 """
1669 """
1670 Stub request object.
1670 Stub request object.
1671 """
1671 """
1672 from rhodecode.lib.base import bootstrap_request
1672 from rhodecode.lib.base import bootstrap_request
1673 request = bootstrap_request(scheme='https')
1673 request = bootstrap_request(scheme='https')
1674 return request
1674 return request
1675
1675
1676
1676
1677 @pytest.fixture
1677 @pytest.fixture
1678 def context_stub():
1678 def context_stub():
1679 """
1679 """
1680 Stub context object.
1680 Stub context object.
1681 """
1681 """
1682 context = pyramid.testing.DummyResource()
1682 context = pyramid.testing.DummyResource()
1683 return context
1683 return context
1684
1684
1685
1685
1686 @pytest.fixture
1686 @pytest.fixture
1687 def config_stub(request, request_stub):
1687 def config_stub(request, request_stub):
1688 """
1688 """
1689 Set up pyramid.testing and return the Configurator.
1689 Set up pyramid.testing and return the Configurator.
1690 """
1690 """
1691 config = pyramid.testing.setUp(request=request_stub)
1691 config = pyramid.testing.setUp(request=request_stub)
1692 add_test_routes(config)
1692 add_test_routes(config)
1693
1693
1694 @request.addfinalizer
1694 @request.addfinalizer
1695 def cleanup():
1695 def cleanup():
1696 pyramid.testing.tearDown()
1696 pyramid.testing.tearDown()
1697
1697
1698 return config
1698 return config
1699
1699
1700
1700
1701 @pytest.fixture
1701 @pytest.fixture
1702 def StubIntegrationType():
1702 def StubIntegrationType():
1703 class _StubIntegrationType(IntegrationTypeBase):
1703 class _StubIntegrationType(IntegrationTypeBase):
1704 """ Test integration type class """
1704 """ Test integration type class """
1705
1705
1706 key = 'test'
1706 key = 'test'
1707 display_name = 'Test integration type'
1707 display_name = 'Test integration type'
1708 description = 'A test integration type for testing'
1708 description = 'A test integration type for testing'
1709 icon = 'test_icon_html_image'
1709 icon = 'test_icon_html_image'
1710
1710
1711 def __init__(self, settings):
1711 def __init__(self, settings):
1712 super(_StubIntegrationType, self).__init__(settings)
1712 super(_StubIntegrationType, self).__init__(settings)
1713 self.sent_events = [] # for testing
1713 self.sent_events = [] # for testing
1714
1714
1715 def send_event(self, event):
1715 def send_event(self, event):
1716 self.sent_events.append(event)
1716 self.sent_events.append(event)
1717
1717
1718 def settings_schema(self):
1718 def settings_schema(self):
1719 class SettingsSchema(colander.Schema):
1719 class SettingsSchema(colander.Schema):
1720 test_string_field = colander.SchemaNode(
1720 test_string_field = colander.SchemaNode(
1721 colander.String(),
1721 colander.String(),
1722 missing=colander.required,
1722 missing=colander.required,
1723 title='test string field',
1723 title='test string field',
1724 )
1724 )
1725 test_int_field = colander.SchemaNode(
1725 test_int_field = colander.SchemaNode(
1726 colander.Int(),
1726 colander.Int(),
1727 title='some integer setting',
1727 title='some integer setting',
1728 )
1728 )
1729 return SettingsSchema()
1729 return SettingsSchema()
1730
1730
1731
1731
1732 integration_type_registry.register_integration_type(_StubIntegrationType)
1732 integration_type_registry.register_integration_type(_StubIntegrationType)
1733 return _StubIntegrationType
1733 return _StubIntegrationType
1734
1734
1735 @pytest.fixture
1735 @pytest.fixture
1736 def stub_integration_settings():
1736 def stub_integration_settings():
1737 return {
1737 return {
1738 'test_string_field': 'some data',
1738 'test_string_field': 'some data',
1739 'test_int_field': 100,
1739 'test_int_field': 100,
1740 }
1740 }
1741
1741
1742
1742
1743 @pytest.fixture
1743 @pytest.fixture
1744 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1744 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1745 stub_integration_settings):
1745 stub_integration_settings):
1746 integration = IntegrationModel().create(
1746 integration = IntegrationModel().create(
1747 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1747 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1748 name='test repo integration',
1748 name='test repo integration',
1749 repo=repo_stub, repo_group=None, child_repos_only=None)
1749 repo=repo_stub, repo_group=None, child_repos_only=None)
1750
1750
1751 @request.addfinalizer
1751 @request.addfinalizer
1752 def cleanup():
1752 def cleanup():
1753 IntegrationModel().delete(integration)
1753 IntegrationModel().delete(integration)
1754
1754
1755 return integration
1755 return integration
1756
1756
1757
1757
1758 @pytest.fixture
1758 @pytest.fixture
1759 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1759 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1760 stub_integration_settings):
1760 stub_integration_settings):
1761 integration = IntegrationModel().create(
1761 integration = IntegrationModel().create(
1762 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1762 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1763 name='test repogroup integration',
1763 name='test repogroup integration',
1764 repo=None, repo_group=test_repo_group, child_repos_only=True)
1764 repo=None, repo_group=test_repo_group, child_repos_only=True)
1765
1765
1766 @request.addfinalizer
1766 @request.addfinalizer
1767 def cleanup():
1767 def cleanup():
1768 IntegrationModel().delete(integration)
1768 IntegrationModel().delete(integration)
1769
1769
1770 return integration
1770 return integration
1771
1771
1772
1772
1773 @pytest.fixture
1773 @pytest.fixture
1774 def repogroup_recursive_integration_stub(request, test_repo_group,
1774 def repogroup_recursive_integration_stub(request, test_repo_group,
1775 StubIntegrationType, stub_integration_settings):
1775 StubIntegrationType, stub_integration_settings):
1776 integration = IntegrationModel().create(
1776 integration = IntegrationModel().create(
1777 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1777 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1778 name='test recursive repogroup integration',
1778 name='test recursive repogroup integration',
1779 repo=None, repo_group=test_repo_group, child_repos_only=False)
1779 repo=None, repo_group=test_repo_group, child_repos_only=False)
1780
1780
1781 @request.addfinalizer
1781 @request.addfinalizer
1782 def cleanup():
1782 def cleanup():
1783 IntegrationModel().delete(integration)
1783 IntegrationModel().delete(integration)
1784
1784
1785 return integration
1785 return integration
1786
1786
1787
1787
1788 @pytest.fixture
1788 @pytest.fixture
1789 def global_integration_stub(request, StubIntegrationType,
1789 def global_integration_stub(request, StubIntegrationType,
1790 stub_integration_settings):
1790 stub_integration_settings):
1791 integration = IntegrationModel().create(
1791 integration = IntegrationModel().create(
1792 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1792 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1793 name='test global integration',
1793 name='test global integration',
1794 repo=None, repo_group=None, child_repos_only=None)
1794 repo=None, repo_group=None, child_repos_only=None)
1795
1795
1796 @request.addfinalizer
1796 @request.addfinalizer
1797 def cleanup():
1797 def cleanup():
1798 IntegrationModel().delete(integration)
1798 IntegrationModel().delete(integration)
1799
1799
1800 return integration
1800 return integration
1801
1801
1802
1802
1803 @pytest.fixture
1803 @pytest.fixture
1804 def root_repos_integration_stub(request, StubIntegrationType,
1804 def root_repos_integration_stub(request, StubIntegrationType,
1805 stub_integration_settings):
1805 stub_integration_settings):
1806 integration = IntegrationModel().create(
1806 integration = IntegrationModel().create(
1807 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1807 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1808 name='test global integration',
1808 name='test global integration',
1809 repo=None, repo_group=None, child_repos_only=True)
1809 repo=None, repo_group=None, child_repos_only=True)
1810
1810
1811 @request.addfinalizer
1811 @request.addfinalizer
1812 def cleanup():
1812 def cleanup():
1813 IntegrationModel().delete(integration)
1813 IntegrationModel().delete(integration)
1814
1814
1815 return integration
1815 return integration
1816
1816
1817
1817
1818 @pytest.fixture
1818 @pytest.fixture
1819 def local_dt_to_utc():
1819 def local_dt_to_utc():
1820 def _factory(dt):
1820 def _factory(dt):
1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1823 return _factory
1823 return _factory
1824
1824
1825
1825
1826 @pytest.fixture
1826 @pytest.fixture
1827 def disable_anonymous_user(request, pylonsapp):
1827 def disable_anonymous_user(request, pylonsapp):
1828 set_anonymous_access(False)
1828 set_anonymous_access(False)
1829
1829
1830 @request.addfinalizer
1830 @request.addfinalizer
1831 def cleanup():
1831 def cleanup():
1832 set_anonymous_access(True)
1832 set_anonymous_access(True)
1833
1834
1835 @pytest.fixture
1836 def rc_fixture(request):
1837 return Fixture()
1838
1839
1840 @pytest.fixture
1841 def repo_groups(request):
1842 fixture = Fixture()
1843
1844 session = Session()
1845 zombie_group = fixture.create_repo_group('zombie')
1846 parent_group = fixture.create_repo_group('parent')
1847 child_group = fixture.create_repo_group('parent/child')
1848 groups_in_db = session.query(RepoGroup).all()
1849 assert len(groups_in_db) == 3
1850 assert child_group.group_parent_id == parent_group.group_id
1851
1852 @request.addfinalizer
1853 def cleanup():
1854 fixture.destroy_repo_group(zombie_group)
1855 fixture.destroy_repo_group(child_group)
1856 fixture.destroy_repo_group(parent_group)
1857
1858 return zombie_group, parent_group, child_group
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now