##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 32 from rhodecode.model import user_group
33 33 from rhodecode.model import user
34 34 from rhodecode.model.db import User
35 35 from rhodecode.model.scm import ScmModel
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 ADMIN_PREFIX = '/_admin'
41 41 STATIC_FILE_PREFIX = '/_static'
42 42
43 43 URL_NAME_REQUIREMENTS = {
44 44 # group name can have a slash in them, but they must not end with a slash
45 45 'group_name': r'.*?[^/]',
46 46 'repo_group_name': r'.*?[^/]',
47 47 # repo names can have a slash in them, but they must not end with a slash
48 48 'repo_name': r'.*?[^/]',
49 49 # file path eats up everything at the end
50 50 'f_path': r'.*',
51 51 # reference types
52 52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 54 }
55 55
56 56
57 57 def add_route_with_slash(config,name, pattern, **kw):
58 58 config.add_route(name, pattern, **kw)
59 59 if not pattern.endswith('/'):
60 60 config.add_route(name + '_slash', pattern + '/', **kw)
61 61
62 62
63 63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 64 """
65 65 Adds regex requirements to pyramid routes using a mapping dict
66 66 e.g::
67 67 add_route_requirements('{repo_name}/settings')
68 68 """
69 69 for key, regex in requirements.items():
70 70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 71 return route_path
72 72
73 73
74 74 def get_format_ref_id(repo):
75 75 """Returns a `repo` specific reference formatter function"""
76 76 if h.is_svn(repo):
77 77 return _format_ref_id_svn
78 78 else:
79 79 return _format_ref_id
80 80
81 81
82 82 def _format_ref_id(name, raw_id):
83 83 """Default formatting of a given reference `name`"""
84 84 return name
85 85
86 86
87 87 def _format_ref_id_svn(name, raw_id):
88 88 """Special way of formatting a reference for Subversion including path"""
89 89 return '%s@%s' % (name, raw_id)
90 90
91 91
92 92 class TemplateArgs(StrictAttributeDict):
93 93 pass
94 94
95 95
96 96 class BaseAppView(object):
97 97
98 98 def __init__(self, context, request):
99 99 self.request = request
100 100 self.context = context
101 101 self.session = request.session
102 102 self._rhodecode_user = request.user # auth user
103 103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 104 self._maybe_needs_password_change(
105 105 request.matched_route.name, self._rhodecode_db_user)
106 106
107 107 def _maybe_needs_password_change(self, view_name, user_obj):
108 108 log.debug('Checking if user %s needs password change on view %s',
109 109 user_obj, view_name)
110 110 skip_user_views = [
111 111 'logout', 'login',
112 112 'my_account_password', 'my_account_password_update'
113 113 ]
114 114
115 115 if not user_obj:
116 116 return
117 117
118 118 if user_obj.username == User.DEFAULT_USER:
119 119 return
120 120
121 121 now = time.time()
122 122 should_change = user_obj.user_data.get('force_password_change')
123 123 change_after = safe_int(should_change) or 0
124 124 if should_change and now > change_after:
125 125 log.debug('User %s requires password change', user_obj)
126 126 h.flash('You are required to change your password', 'warning',
127 127 ignore_duplicate=True)
128 128
129 129 if view_name not in skip_user_views:
130 130 raise HTTPFound(
131 131 self.request.route_path('my_account_password'))
132 132
133 133 def _log_creation_exception(self, e, repo_name):
134 134 _ = self.request.translate
135 135 reason = None
136 136 if len(e.args) == 2:
137 137 reason = e.args[1]
138 138
139 139 if reason == 'INVALID_CERTIFICATE':
140 140 log.exception(
141 141 'Exception creating a repository: invalid certificate')
142 142 msg = (_('Error creating repository %s: invalid certificate')
143 143 % repo_name)
144 144 else:
145 145 log.exception("Exception creating a repository")
146 146 msg = (_('Error creating repository %s')
147 147 % repo_name)
148 148 return msg
149 149
150 150 def _get_local_tmpl_context(self, include_app_defaults=False):
151 151 c = TemplateArgs()
152 152 c.auth_user = self.request.user
153 153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 154 c.rhodecode_user = self.request.user
155 155
156 156 if include_app_defaults:
157 157 # NOTE(marcink): after full pyramid migration include_app_defaults
158 158 # should be turned on by default
159 159 from rhodecode.lib.base import attach_context_attributes
160 160 attach_context_attributes(c, self.request, self.request.user.user_id)
161 161
162 162 return c
163 163
164 164 def _register_global_c(self, tmpl_args):
165 165 """
166 166 Registers attributes to pylons global `c`
167 167 """
168 168
169 169 # TODO(marcink): remove once pyramid migration is finished
170 170 from pylons import tmpl_context as c
171 171 try:
172 172 for k, v in tmpl_args.items():
173 173 setattr(c, k, v)
174 174 except TypeError:
175 175 log.exception('Failed to register pylons C')
176 176 pass
177 177
178 178 def _get_template_context(self, tmpl_args):
179 179 self._register_global_c(tmpl_args)
180 180
181 181 local_tmpl_args = {
182 182 'defaults': {},
183 183 'errors': {},
184 184 # register a fake 'c' to be used in templates instead of global
185 185 # pylons c, after migration to pyramid we should rename it to 'c'
186 186 # make sure we replace usage of _c in templates too
187 187 '_c': tmpl_args
188 188 }
189 189 local_tmpl_args.update(tmpl_args)
190 190 return local_tmpl_args
191 191
192 192 def load_default_context(self):
193 193 """
194 194 example:
195 195
196 196 def load_default_context(self):
197 197 c = self._get_local_tmpl_context()
198 198 c.custom_var = 'foobar'
199 199 self._register_global_c(c)
200 200 return c
201 201 """
202 202 raise NotImplementedError('Needs implementation in view class')
203 203
204 204
205 205 class RepoAppView(BaseAppView):
206 206
207 207 def __init__(self, context, request):
208 208 super(RepoAppView, self).__init__(context, request)
209 209 self.db_repo = request.db_repo
210 210 self.db_repo_name = self.db_repo.repo_name
211 211 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
212 212
213 213 def _handle_missing_requirements(self, error):
214 214 log.error(
215 215 'Requirements are missing for repository %s: %s',
216 216 self.db_repo_name, error.message)
217 217
218 218 def _get_local_tmpl_context(self, include_app_defaults=False):
219 219 _ = self.request.translate
220 220 c = super(RepoAppView, self)._get_local_tmpl_context(
221 221 include_app_defaults=include_app_defaults)
222 222
223 223 # register common vars for this type of view
224 224 c.rhodecode_db_repo = self.db_repo
225 225 c.repo_name = self.db_repo_name
226 226 c.repository_pull_requests = self.db_repo_pull_requests
227 227
228 228 c.repository_requirements_missing = False
229 229 try:
230 230 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
231 231 except RepositoryRequirementError as e:
232 232 c.repository_requirements_missing = True
233 233 self._handle_missing_requirements(e)
234 234 self.rhodecode_vcs_repo = None
235 235
236 236 if (not c.repository_requirements_missing
237 237 and self.rhodecode_vcs_repo is None):
238 238 # unable to fetch this repo as vcs instance, report back to user
239 239 h.flash(_(
240 240 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
241 241 "Please check if it exist, or is not damaged.") %
242 242 {'repo_name': c.repo_name},
243 243 category='error', ignore_duplicate=True)
244 244 raise HTTPFound(h.route_path('home'))
245 245
246 246 return c
247 247
248 248 def _get_f_path(self, matchdict, default=None):
249 249 f_path = matchdict.get('f_path')
250 250 if f_path:
251 251 # fix for multiple initial slashes that causes errors for GIT
252 252 return f_path.lstrip('/')
253 253
254 254 return default
255 255
256 256
257 257 class RepoGroupAppView(BaseAppView):
258 258 def __init__(self, context, request):
259 259 super(RepoGroupAppView, self).__init__(context, request)
260 260 self.db_repo_group = request.db_repo_group
261 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 278 class UserGroupAppView(BaseAppView):
265 279 def __init__(self, context, request):
266 280 super(UserGroupAppView, self).__init__(context, request)
267 281 self.db_user_group = request.db_user_group
268 282 self.db_user_group_name = self.db_user_group.users_group_name
269 283
270 284
271 285 class UserAppView(BaseAppView):
272 286 def __init__(self, context, request):
273 287 super(UserAppView, self).__init__(context, request)
274 288 self.db_user = request.db_user
275 289 self.db_user_id = self.db_user.user_id
276 290
277 291 _ = self.request.translate
278 292 if not request.db_user_supports_default:
279 293 if self.db_user.username == User.DEFAULT_USER:
280 294 h.flash(_("Editing user `{}` is disabled.".format(
281 295 User.DEFAULT_USER)), category='warning')
282 296 raise HTTPFound(h.route_path('users'))
283 297
284 298
285 299 class DataGridAppView(object):
286 300 """
287 301 Common class to have re-usable grid rendering components
288 302 """
289 303
290 304 def _extract_ordering(self, request, column_map=None):
291 305 column_map = column_map or {}
292 306 column_index = safe_int(request.GET.get('order[0][column]'))
293 307 order_dir = request.GET.get(
294 308 'order[0][dir]', 'desc')
295 309 order_by = request.GET.get(
296 310 'columns[%s][data][sort]' % column_index, 'name_raw')
297 311
298 312 # translate datatable to DB columns
299 313 order_by = column_map.get(order_by) or order_by
300 314
301 315 search_q = request.GET.get('search[value]')
302 316 return search_q, order_by, order_dir
303 317
304 318 def _extract_chunk(self, request):
305 319 start = safe_int(request.GET.get('start'), 0)
306 320 length = safe_int(request.GET.get('length'), 25)
307 321 draw = safe_int(request.GET.get('draw'))
308 322 return draw, start, length
309 323
310 324 def _get_order_col(self, order_by, model):
311 325 if isinstance(order_by, basestring):
312 326 try:
313 327 return operator.attrgetter(order_by)(model)
314 328 except AttributeError:
315 329 return None
316 330 else:
317 331 return order_by
318 332
319 333
320 334 class BaseReferencesView(RepoAppView):
321 335 """
322 336 Base for reference view for branches, tags and bookmarks.
323 337 """
324 338 def load_default_context(self):
325 339 c = self._get_local_tmpl_context()
326 340
327 341 self._register_global_c(c)
328 342 return c
329 343
330 344 def load_refs_context(self, ref_items, partials_template):
331 345 _render = self.request.get_partial_renderer(partials_template)
332 346 pre_load = ["author", "date", "message"]
333 347
334 348 is_svn = h.is_svn(self.rhodecode_vcs_repo)
335 349 is_hg = h.is_hg(self.rhodecode_vcs_repo)
336 350
337 351 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
338 352
339 353 closed_refs = {}
340 354 if is_hg:
341 355 closed_refs = self.rhodecode_vcs_repo.branches_closed
342 356
343 357 data = []
344 358 for ref_name, commit_id in ref_items:
345 359 commit = self.rhodecode_vcs_repo.get_commit(
346 360 commit_id=commit_id, pre_load=pre_load)
347 361 closed = ref_name in closed_refs
348 362
349 363 # TODO: johbo: Unify generation of reference links
350 364 use_commit_id = '/' in ref_name or is_svn
351 365
352 366 if use_commit_id:
353 367 files_url = h.route_path(
354 368 'repo_files',
355 369 repo_name=self.db_repo_name,
356 370 f_path=ref_name if is_svn else '',
357 371 commit_id=commit_id)
358 372
359 373 else:
360 374 files_url = h.route_path(
361 375 'repo_files',
362 376 repo_name=self.db_repo_name,
363 377 f_path=ref_name if is_svn else '',
364 378 commit_id=ref_name,
365 379 _query=dict(at=ref_name))
366 380
367 381 data.append({
368 382 "name": _render('name', ref_name, files_url, closed),
369 383 "name_raw": ref_name,
370 384 "date": _render('date', commit.date),
371 385 "date_raw": datetime_to_time(commit.date),
372 386 "author": _render('author', commit.author),
373 387 "commit": _render(
374 388 'commit', commit.message, commit.raw_id, commit.idx),
375 389 "commit_raw": commit.idx,
376 390 "compare": _render(
377 391 'compare', format_ref_id(ref_name, commit.raw_id)),
378 392 })
379 393
380 394 return data
381 395
382 396
383 397 class RepoRoutePredicate(object):
384 398 def __init__(self, val, config):
385 399 self.val = val
386 400
387 401 def text(self):
388 402 return 'repo_route = %s' % self.val
389 403
390 404 phash = text
391 405
392 406 def __call__(self, info, request):
393 407
394 408 if hasattr(request, 'vcs_call'):
395 409 # skip vcs calls
396 410 return
397 411
398 412 repo_name = info['match']['repo_name']
399 413 repo_model = repo.RepoModel()
400 414 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
401 415
402 416 def redirect_if_creating(db_repo):
403 417 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
404 418 raise HTTPFound(
405 419 request.route_path('repo_creating',
406 420 repo_name=db_repo.repo_name))
407 421
408 422 if by_name_match:
409 423 # register this as request object we can re-use later
410 424 request.db_repo = by_name_match
411 425 redirect_if_creating(by_name_match)
412 426 return True
413 427
414 428 by_id_match = repo_model.get_repo_by_id(repo_name)
415 429 if by_id_match:
416 430 request.db_repo = by_id_match
417 431 redirect_if_creating(by_id_match)
418 432 return True
419 433
420 434 return False
421 435
422 436
423 437 class RepoTypeRoutePredicate(object):
424 438 def __init__(self, val, config):
425 439 self.val = val or ['hg', 'git', 'svn']
426 440
427 441 def text(self):
428 442 return 'repo_accepted_type = %s' % self.val
429 443
430 444 phash = text
431 445
432 446 def __call__(self, info, request):
433 447 if hasattr(request, 'vcs_call'):
434 448 # skip vcs calls
435 449 return
436 450
437 451 rhodecode_db_repo = request.db_repo
438 452
439 453 log.debug(
440 454 '%s checking repo type for %s in %s',
441 455 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
442 456
443 457 if rhodecode_db_repo.repo_type in self.val:
444 458 return True
445 459 else:
446 460 log.warning('Current view is not supported for repo type:%s',
447 461 rhodecode_db_repo.repo_type)
448 462 #
449 463 # h.flash(h.literal(
450 464 # _('Action not supported for %s.' % rhodecode_repo.alias)),
451 465 # category='warning')
452 466 # return redirect(
453 467 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
454 468
455 469 return False
456 470
457 471
458 472 class RepoGroupRoutePredicate(object):
459 473 def __init__(self, val, config):
460 474 self.val = val
461 475
462 476 def text(self):
463 477 return 'repo_group_route = %s' % self.val
464 478
465 479 phash = text
466 480
467 481 def __call__(self, info, request):
468 482 if hasattr(request, 'vcs_call'):
469 483 # skip vcs calls
470 484 return
471 485
472 486 repo_group_name = info['match']['repo_group_name']
473 487 repo_group_model = repo_group.RepoGroupModel()
474 488 by_name_match = repo_group_model.get_by_group_name(
475 489 repo_group_name, cache=True)
476 490
477 491 if by_name_match:
478 492 # register this as request object we can re-use later
479 493 request.db_repo_group = by_name_match
480 494 return True
481 495
482 496 return False
483 497
484 498
485 499 class UserGroupRoutePredicate(object):
486 500 def __init__(self, val, config):
487 501 self.val = val
488 502
489 503 def text(self):
490 504 return 'user_group_route = %s' % self.val
491 505
492 506 phash = text
493 507
494 508 def __call__(self, info, request):
495 509 if hasattr(request, 'vcs_call'):
496 510 # skip vcs calls
497 511 return
498 512
499 513 user_group_id = info['match']['user_group_id']
500 514 user_group_model = user_group.UserGroup()
501 515 by_id_match = user_group_model.get(
502 516 user_group_id, cache=True)
503 517
504 518 if by_id_match:
505 519 # register this as request object we can re-use later
506 520 request.db_user_group = by_id_match
507 521 return True
508 522
509 523 return False
510 524
511 525
512 526 class UserRoutePredicateBase(object):
513 527 supports_default = None
514 528
515 529 def __init__(self, val, config):
516 530 self.val = val
517 531
518 532 def text(self):
519 533 raise NotImplementedError()
520 534
521 535 def __call__(self, info, request):
522 536 if hasattr(request, 'vcs_call'):
523 537 # skip vcs calls
524 538 return
525 539
526 540 user_id = info['match']['user_id']
527 541 user_model = user.User()
528 542 by_id_match = user_model.get(
529 543 user_id, cache=True)
530 544
531 545 if by_id_match:
532 546 # register this as request object we can re-use later
533 547 request.db_user = by_id_match
534 548 request.db_user_supports_default = self.supports_default
535 549 return True
536 550
537 551 return False
538 552
539 553
540 554 class UserRoutePredicate(UserRoutePredicateBase):
541 555 supports_default = False
542 556
543 557 def text(self):
544 558 return 'user_route = %s' % self.val
545 559
546 560 phash = text
547 561
548 562
549 563 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
550 564 supports_default = True
551 565
552 566 def text(self):
553 567 return 'user_with_default_route = %s' % self.val
554 568
555 569 phash = text
556 570
557 571
558 572 def includeme(config):
559 573 config.add_route_predicate(
560 574 'repo_route', RepoRoutePredicate)
561 575 config.add_route_predicate(
562 576 'repo_accepted_types', RepoTypeRoutePredicate)
563 577 config.add_route_predicate(
564 578 'repo_group_route', RepoGroupRoutePredicate)
565 579 config.add_route_predicate(
566 580 'user_group_route', UserGroupRoutePredicate)
567 581 config.add_route_predicate(
568 582 'user_route_with_default', UserRouteWithDefaultPredicate)
569 583 config.add_route_predicate(
570 584 'user_route', UserRoutePredicate) No newline at end of file
@@ -1,312 +1,325 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 Admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 33 name='admin_audit_logs',
34 34 pattern='/audit_logs')
35 35
36 36 config.add_route(
37 37 name='admin_audit_log_entry',
38 38 pattern='/audit_logs/{audit_log_id}')
39 39
40 40 config.add_route(
41 41 name='pull_requests_global_0', # backward compat
42 42 pattern='/pull_requests/{pull_request_id:\d+}')
43 43 config.add_route(
44 44 name='pull_requests_global_1', # backward compat
45 45 pattern='/pull-requests/{pull_request_id:\d+}')
46 46 config.add_route(
47 47 name='pull_requests_global',
48 48 pattern='/pull-request/{pull_request_id:\d+}')
49 49
50 50 config.add_route(
51 51 name='admin_settings_open_source',
52 52 pattern='/settings/open_source')
53 53 config.add_route(
54 54 name='admin_settings_vcs_svn_generate_cfg',
55 55 pattern='/settings/vcs/svn_generate_cfg')
56 56
57 57 config.add_route(
58 58 name='admin_settings_system',
59 59 pattern='/settings/system')
60 60 config.add_route(
61 61 name='admin_settings_system_update',
62 62 pattern='/settings/system/updates')
63 63
64 64 config.add_route(
65 65 name='admin_settings_sessions',
66 66 pattern='/settings/sessions')
67 67 config.add_route(
68 68 name='admin_settings_sessions_cleanup',
69 69 pattern='/settings/sessions/cleanup')
70 70
71 71 config.add_route(
72 72 name='admin_settings_process_management',
73 73 pattern='/settings/process_management')
74 74 config.add_route(
75 75 name='admin_settings_process_management_signal',
76 76 pattern='/settings/process_management/signal')
77 77
78 78 # default settings
79 79 config.add_route(
80 80 name='admin_defaults_repositories',
81 81 pattern='/defaults/repositories')
82 82 config.add_route(
83 83 name='admin_defaults_repositories_update',
84 84 pattern='/defaults/repositories/update')
85 85
86 86 # global permissions
87 87
88 88 config.add_route(
89 89 name='admin_permissions_application',
90 90 pattern='/permissions/application')
91 91 config.add_route(
92 92 name='admin_permissions_application_update',
93 93 pattern='/permissions/application/update')
94 94
95 95 config.add_route(
96 96 name='admin_permissions_global',
97 97 pattern='/permissions/global')
98 98 config.add_route(
99 99 name='admin_permissions_global_update',
100 100 pattern='/permissions/global/update')
101 101
102 102 config.add_route(
103 103 name='admin_permissions_object',
104 104 pattern='/permissions/object')
105 105 config.add_route(
106 106 name='admin_permissions_object_update',
107 107 pattern='/permissions/object/update')
108 108
109 109 config.add_route(
110 110 name='admin_permissions_ips',
111 111 pattern='/permissions/ips')
112 112
113 113 config.add_route(
114 114 name='admin_permissions_overview',
115 115 pattern='/permissions/overview')
116 116
117 117 config.add_route(
118 118 name='admin_permissions_auth_token_access',
119 119 pattern='/permissions/auth_token_access')
120 120
121 121 config.add_route(
122 122 name='admin_permissions_ssh_keys',
123 123 pattern='/permissions/ssh_keys')
124 124 config.add_route(
125 125 name='admin_permissions_ssh_keys_data',
126 126 pattern='/permissions/ssh_keys/data')
127 127 config.add_route(
128 128 name='admin_permissions_ssh_keys_update',
129 129 pattern='/permissions/ssh_keys/update')
130 130
131 131 # users admin
132 132 config.add_route(
133 133 name='users',
134 134 pattern='/users')
135 135
136 136 config.add_route(
137 137 name='users_data',
138 138 pattern='/users_data')
139 139
140 140 config.add_route(
141 141 name='users_create',
142 142 pattern='/users/create')
143 143
144 144 config.add_route(
145 145 name='users_new',
146 146 pattern='/users/new')
147 147
148 148 # user management
149 149 config.add_route(
150 150 name='user_edit',
151 151 pattern='/users/{user_id:\d+}/edit',
152 152 user_route=True)
153 153 config.add_route(
154 154 name='user_edit_advanced',
155 155 pattern='/users/{user_id:\d+}/edit/advanced',
156 156 user_route=True)
157 157 config.add_route(
158 158 name='user_edit_global_perms',
159 159 pattern='/users/{user_id:\d+}/edit/global_permissions',
160 160 user_route=True)
161 161 config.add_route(
162 162 name='user_edit_global_perms_update',
163 163 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
164 164 user_route=True)
165 165 config.add_route(
166 166 name='user_update',
167 167 pattern='/users/{user_id:\d+}/update',
168 168 user_route=True)
169 169 config.add_route(
170 170 name='user_delete',
171 171 pattern='/users/{user_id:\d+}/delete',
172 172 user_route=True)
173 173 config.add_route(
174 174 name='user_force_password_reset',
175 175 pattern='/users/{user_id:\d+}/password_reset',
176 176 user_route=True)
177 177 config.add_route(
178 178 name='user_create_personal_repo_group',
179 179 pattern='/users/{user_id:\d+}/create_repo_group',
180 180 user_route=True)
181 181
182 182 # user auth tokens
183 183 config.add_route(
184 184 name='edit_user_auth_tokens',
185 185 pattern='/users/{user_id:\d+}/edit/auth_tokens',
186 186 user_route=True)
187 187 config.add_route(
188 188 name='edit_user_auth_tokens_add',
189 189 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
190 190 user_route=True)
191 191 config.add_route(
192 192 name='edit_user_auth_tokens_delete',
193 193 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
194 194 user_route=True)
195 195
196 196 # user ssh keys
197 197 config.add_route(
198 198 name='edit_user_ssh_keys',
199 199 pattern='/users/{user_id:\d+}/edit/ssh_keys',
200 200 user_route=True)
201 201 config.add_route(
202 202 name='edit_user_ssh_keys_generate_keypair',
203 203 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
204 204 user_route=True)
205 205 config.add_route(
206 206 name='edit_user_ssh_keys_add',
207 207 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
208 208 user_route=True)
209 209 config.add_route(
210 210 name='edit_user_ssh_keys_delete',
211 211 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
212 212 user_route=True)
213 213
214 214 # user emails
215 215 config.add_route(
216 216 name='edit_user_emails',
217 217 pattern='/users/{user_id:\d+}/edit/emails',
218 218 user_route=True)
219 219 config.add_route(
220 220 name='edit_user_emails_add',
221 221 pattern='/users/{user_id:\d+}/edit/emails/new',
222 222 user_route=True)
223 223 config.add_route(
224 224 name='edit_user_emails_delete',
225 225 pattern='/users/{user_id:\d+}/edit/emails/delete',
226 226 user_route=True)
227 227
228 228 # user IPs
229 229 config.add_route(
230 230 name='edit_user_ips',
231 231 pattern='/users/{user_id:\d+}/edit/ips',
232 232 user_route=True)
233 233 config.add_route(
234 234 name='edit_user_ips_add',
235 235 pattern='/users/{user_id:\d+}/edit/ips/new',
236 236 user_route_with_default=True) # enabled for default user too
237 237 config.add_route(
238 238 name='edit_user_ips_delete',
239 239 pattern='/users/{user_id:\d+}/edit/ips/delete',
240 240 user_route_with_default=True) # enabled for default user too
241 241
242 242 # user perms
243 243 config.add_route(
244 244 name='edit_user_perms_summary',
245 245 pattern='/users/{user_id:\d+}/edit/permissions_summary',
246 246 user_route=True)
247 247 config.add_route(
248 248 name='edit_user_perms_summary_json',
249 249 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
250 250 user_route=True)
251 251
252 252 # user user groups management
253 253 config.add_route(
254 254 name='edit_user_groups_management',
255 255 pattern='/users/{user_id:\d+}/edit/groups_management',
256 256 user_route=True)
257 257
258 258 config.add_route(
259 259 name='edit_user_groups_management_updates',
260 260 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
261 261 user_route=True)
262 262
263 263 # user audit logs
264 264 config.add_route(
265 265 name='edit_user_audit_logs',
266 266 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
267 267
268 268 # user-groups admin
269 269 config.add_route(
270 270 name='user_groups',
271 271 pattern='/user_groups')
272 272
273 273 config.add_route(
274 274 name='user_groups_data',
275 275 pattern='/user_groups_data')
276 276
277 277 config.add_route(
278 278 name='user_groups_new',
279 279 pattern='/user_groups/new')
280 280
281 281 config.add_route(
282 282 name='user_groups_create',
283 283 pattern='/user_groups/create')
284 284
285 285 # repos admin
286 286 config.add_route(
287 287 name='repos',
288 288 pattern='/repos')
289 289
290 290 config.add_route(
291 291 name='repo_new',
292 292 pattern='/repos/new')
293 293
294 294 config.add_route(
295 295 name='repo_create',
296 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 312 def includeme(config):
300 313 settings = config.get_settings()
301 314
302 315 # Create admin navigation registry and add it to the pyramid registry.
303 316 labs_active = str2bool(settings.get('labs_settings_active', False))
304 317 navigation_registry = NavigationRegistry(labs_active=labs_active)
305 318 config.registry.registerUtility(navigation_registry)
306 319
307 320 # main admin routes
308 321 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
309 322 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
310 323
311 324 # Scan module for configuration decorators.
312 325 config.scan('.views', ignore='.tests')
@@ -1,33 +1,61 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 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 55 add_route_with_slash(
27 56 config,
28 57 name='repo_group_home',
29 58 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
30 59
31 60 # Scan module for configuration decorators.
32 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 25 # repo creating checks, special cases that aren't repo routes
26 26 config.add_route(
27 27 name='repo_creating',
28 28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29 29
30 30 config.add_route(
31 31 name='repo_creating_check',
32 32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33 33
34 34 # Summary
35 35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 36 # all pattern
37 37 config.add_route(
38 38 name='repo_summary_explicit',
39 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 40 config.add_route(
41 41 name='repo_summary_commits',
42 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43 43
44 44 # Commits
45 45 config.add_route(
46 46 name='repo_commit',
47 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48 48
49 49 config.add_route(
50 50 name='repo_commit_children',
51 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52 52
53 53 config.add_route(
54 54 name='repo_commit_parents',
55 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56 56
57 57 config.add_route(
58 58 name='repo_commit_raw',
59 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60 60
61 61 config.add_route(
62 62 name='repo_commit_patch',
63 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64 64
65 65 config.add_route(
66 66 name='repo_commit_download',
67 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68 68
69 69 config.add_route(
70 70 name='repo_commit_data',
71 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72 72
73 73 config.add_route(
74 74 name='repo_commit_comment_create',
75 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76 76
77 77 config.add_route(
78 78 name='repo_commit_comment_preview',
79 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80 80
81 81 config.add_route(
82 82 name='repo_commit_comment_delete',
83 83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84 84
85 85 # still working url for backward compat.
86 86 config.add_route(
87 87 name='repo_commit_raw_deprecated',
88 88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89 89
90 90 # Files
91 91 config.add_route(
92 92 name='repo_archivefile',
93 93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94 94
95 95 config.add_route(
96 96 name='repo_files_diff',
97 97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 98 config.add_route( # legacy route to make old links work
99 99 name='repo_files_diff_2way_redirect',
100 100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101 101
102 102 config.add_route(
103 103 name='repo_files',
104 104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 105 config.add_route(
106 106 name='repo_files:default_path',
107 107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 108 config.add_route(
109 109 name='repo_files:default_commit',
110 110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111 111
112 112 config.add_route(
113 113 name='repo_files:rendered',
114 114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115 115
116 116 config.add_route(
117 117 name='repo_files:annotated',
118 118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 119 config.add_route(
120 120 name='repo_files:annotated_previous',
121 121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122 122
123 123 config.add_route(
124 124 name='repo_nodetree_full',
125 125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 126 config.add_route(
127 127 name='repo_nodetree_full:default_path',
128 128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129 129
130 130 config.add_route(
131 131 name='repo_files_nodelist',
132 132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133 133
134 134 config.add_route(
135 135 name='repo_file_raw',
136 136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137 137
138 138 config.add_route(
139 139 name='repo_file_download',
140 140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 141 config.add_route( # backward compat to keep old links working
142 142 name='repo_file_download:legacy',
143 143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 144 repo_route=True)
145 145
146 146 config.add_route(
147 147 name='repo_file_history',
148 148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149 149
150 150 config.add_route(
151 151 name='repo_file_authors',
152 152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153 153
154 154 config.add_route(
155 155 name='repo_files_remove_file',
156 156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 157 repo_route=True)
158 158 config.add_route(
159 159 name='repo_files_delete_file',
160 160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 161 repo_route=True)
162 162 config.add_route(
163 163 name='repo_files_edit_file',
164 164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 165 repo_route=True)
166 166 config.add_route(
167 167 name='repo_files_update_file',
168 168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 169 repo_route=True)
170 170 config.add_route(
171 171 name='repo_files_add_file',
172 172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 173 repo_route=True)
174 174 config.add_route(
175 175 name='repo_files_create_file',
176 176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 177 repo_route=True)
178 178
179 179 # Refs data
180 180 config.add_route(
181 181 name='repo_refs_data',
182 182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183 183
184 184 config.add_route(
185 185 name='repo_refs_changelog_data',
186 186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187 187
188 188 config.add_route(
189 189 name='repo_stats',
190 190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191 191
192 192 # Changelog
193 193 config.add_route(
194 194 name='repo_changelog',
195 195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 196 config.add_route(
197 197 name='repo_changelog_file',
198 198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 199 config.add_route(
200 200 name='repo_changelog_elements',
201 201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 202 config.add_route(
203 203 name='repo_changelog_elements_file',
204 204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205 205
206 206 # Compare
207 207 config.add_route(
208 208 name='repo_compare_select',
209 209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210 210
211 211 config.add_route(
212 212 name='repo_compare',
213 213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214 214
215 215 # Tags
216 216 config.add_route(
217 217 name='tags_home',
218 218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219 219
220 220 # Branches
221 221 config.add_route(
222 222 name='branches_home',
223 223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224 224
225 225 # Bookmarks
226 226 config.add_route(
227 227 name='bookmarks_home',
228 228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229 229
230 230 # Forks
231 231 config.add_route(
232 232 name='repo_fork_new',
233 233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 234 repo_accepted_types=['hg', 'git'])
235 235
236 236 config.add_route(
237 237 name='repo_fork_create',
238 238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 239 repo_accepted_types=['hg', 'git'])
240 240
241 241 config.add_route(
242 242 name='repo_forks_show_all',
243 243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
244 244 repo_accepted_types=['hg', 'git'])
245 245 config.add_route(
246 246 name='repo_forks_data',
247 247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
248 248 repo_accepted_types=['hg', 'git'])
249 249
250 250 # Pull Requests
251 251 config.add_route(
252 252 name='pullrequest_show',
253 253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
254 254 repo_route=True)
255 255
256 256 config.add_route(
257 257 name='pullrequest_show_all',
258 258 pattern='/{repo_name:.*?[^/]}/pull-request',
259 259 repo_route=True, repo_accepted_types=['hg', 'git'])
260 260
261 261 config.add_route(
262 262 name='pullrequest_show_all_data',
263 263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
264 264 repo_route=True, repo_accepted_types=['hg', 'git'])
265 265
266 266 config.add_route(
267 267 name='pullrequest_repo_refs',
268 268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
269 269 repo_route=True)
270 270
271 271 config.add_route(
272 272 name='pullrequest_repo_destinations',
273 273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
274 274 repo_route=True)
275 275
276 276 config.add_route(
277 277 name='pullrequest_new',
278 278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
279 279 repo_route=True, repo_accepted_types=['hg', 'git'])
280 280
281 281 config.add_route(
282 282 name='pullrequest_create',
283 283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
284 284 repo_route=True, repo_accepted_types=['hg', 'git'])
285 285
286 286 config.add_route(
287 287 name='pullrequest_update',
288 288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
289 289 repo_route=True)
290 290
291 291 config.add_route(
292 292 name='pullrequest_merge',
293 293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
294 294 repo_route=True)
295 295
296 296 config.add_route(
297 297 name='pullrequest_delete',
298 298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
299 299 repo_route=True)
300 300
301 301 config.add_route(
302 302 name='pullrequest_comment_create',
303 303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
304 304 repo_route=True)
305 305
306 306 config.add_route(
307 307 name='pullrequest_comment_delete',
308 308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
309 309 repo_route=True, repo_accepted_types=['hg', 'git'])
310 310
311 311 # Settings
312 312 config.add_route(
313 313 name='edit_repo',
314 314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
315 # update is POST on edit_repo
315 316
316 317 # Settings advanced
317 318 config.add_route(
318 319 name='edit_repo_advanced',
319 320 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
320 321 config.add_route(
321 322 name='edit_repo_advanced_delete',
322 323 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
323 324 config.add_route(
324 325 name='edit_repo_advanced_locking',
325 326 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
326 327 config.add_route(
327 328 name='edit_repo_advanced_journal',
328 329 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
329 330 config.add_route(
330 331 name='edit_repo_advanced_fork',
331 332 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
332 333
333 334 # Caches
334 335 config.add_route(
335 336 name='edit_repo_caches',
336 337 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
337 338
338 339 # Permissions
339 340 config.add_route(
340 341 name='edit_repo_perms',
341 342 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
342 343
343 344 # Maintenance
344 345 config.add_route(
345 346 name='edit_repo_maintenance',
346 347 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
347 348
348 349 config.add_route(
349 350 name='edit_repo_maintenance_execute',
350 351 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
351 352
352 353 # Fields
353 354 config.add_route(
354 355 name='edit_repo_fields',
355 356 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
356 357 config.add_route(
357 358 name='edit_repo_fields_create',
358 359 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
359 360 config.add_route(
360 361 name='edit_repo_fields_delete',
361 362 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
362 363
363 364 # Locking
364 365 config.add_route(
365 366 name='repo_edit_toggle_locking',
366 367 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
367 368
368 369 # Remote
369 370 config.add_route(
370 371 name='edit_repo_remote',
371 372 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
372 373 config.add_route(
373 374 name='edit_repo_remote_pull',
374 375 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
375 376
376
377 377 # Statistics
378 378 config.add_route(
379 379 name='edit_repo_statistics',
380 380 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
381 381 config.add_route(
382 382 name='edit_repo_statistics_reset',
383 383 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
384 384
385 385 # Issue trackers
386 386 config.add_route(
387 387 name='edit_repo_issuetracker',
388 388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
389 389 config.add_route(
390 390 name='edit_repo_issuetracker_test',
391 391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
392 392 config.add_route(
393 393 name='edit_repo_issuetracker_delete',
394 394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
395 395 config.add_route(
396 396 name='edit_repo_issuetracker_update',
397 397 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
398 398
399 399 # VCS Settings
400 400 config.add_route(
401 401 name='edit_repo_vcs',
402 402 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
403 403 config.add_route(
404 404 name='edit_repo_vcs_update',
405 405 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
406 406
407 407 # svn pattern
408 408 config.add_route(
409 409 name='edit_repo_vcs_svn_pattern_delete',
410 410 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
411 411
412 412 # Repo Review Rules (EE feature)
413 413 config.add_route(
414 414 name='repo_reviewers',
415 415 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
416 416
417 417 config.add_route(
418 418 name='repo_default_reviewers_data',
419 419 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
420 420
421 421 # Strip
422 422 config.add_route(
423 423 name='edit_repo_strip',
424 424 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
425 425
426 426 config.add_route(
427 427 name='strip_check',
428 428 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
429 429
430 430 config.add_route(
431 431 name='strip_execute',
432 432 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
433 433
434 434 # Audit logs
435 435 config.add_route(
436 436 name='edit_repo_audit_logs',
437 437 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
438 438
439 439 # ATOM/RSS Feed
440 440 config.add_route(
441 441 name='rss_feed_home',
442 442 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
443 443
444 444 config.add_route(
445 445 name='atom_feed_home',
446 446 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
447 447
448 448 # NOTE(marcink): needs to be at the end for catch-all
449 449 add_route_with_slash(
450 450 config,
451 451 name='repo_summary',
452 452 pattern='/{repo_name:.*?[^/]}', repo_route=True)
453 453
454 454 # Scan module for configuration decorators.
455 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.utils2 import str2bool
25 25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.tests import (
29 29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
30 30 from rhodecode.tests.fixture import Fixture
31 31
32 32 fixture = Fixture()
33 33
34 34
35 35 def route_path(name, params=None, **kwargs):
36 36 import urllib
37 37
38 38 base_url = {
39 39 'edit_repo': '/{repo_name}/settings',
40 40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 41 'edit_repo_caches': '/{repo_name}/settings/caches',
42 42 'edit_repo_perms': '/{repo_name}/settings/permissions',
43 43 'edit_repo_vcs': '/{repo_name}/settings/vcs',
44 44 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
45 45 'edit_repo_fields': '/{repo_name}/settings/fields',
46 46 'edit_repo_remote': '/{repo_name}/settings/remote',
47 47 'edit_repo_statistics': '/{repo_name}/settings/statistics',
48 48 }[name].format(**kwargs)
49 49
50 50 if params:
51 51 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
52 52 return base_url
53 53
54 54
55 55 def _get_permission_for_user(user, repo):
56 56 perm = UserRepoToPerm.query()\
57 57 .filter(UserRepoToPerm.repository ==
58 58 Repository.get_by_repo_name(repo))\
59 59 .filter(UserRepoToPerm.user == User.get_by_username(user))\
60 60 .all()
61 61 return perm
62 62
63 63
64 64 @pytest.mark.usefixtures('autologin_user', 'app')
65 65 class TestAdminRepoSettings(object):
66 66 @pytest.mark.parametrize('urlname', [
67 67 'edit_repo',
68 68 'edit_repo_caches',
69 69 'edit_repo_perms',
70 70 'edit_repo_advanced',
71 71 'edit_repo_vcs',
72 72 'edit_repo_issuetracker',
73 73 'edit_repo_fields',
74 74 'edit_repo_remote',
75 75 'edit_repo_statistics',
76 76 ])
77 77 def test_show_page(self, urlname, app, backend):
78 78 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
79 79
80 80 def test_edit_accessible_when_missing_requirements(
81 81 self, backend_hg, autologin_user):
82 82 scm_patcher = mock.patch.object(
83 83 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
84 84 with scm_patcher:
85 85 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
86 86
87 87 @pytest.mark.parametrize('update_settings', [
88 88 {'repo_description': 'alter-desc'},
89 89 {'repo_owner': TEST_USER_REGULAR_LOGIN},
90 90 {'repo_private': 'true'},
91 91 {'repo_enable_locking': 'true'},
92 92 {'repo_enable_downloads': 'true'},
93 93 ])
94 94 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
95 95 repo = user_util.create_repo(repo_type=backend.alias)
96 96 repo_name = repo.repo_name
97 97
98 98 params = fixture._get_repo_create_params(
99 99 csrf_token=csrf_token,
100 100 repo_name=repo_name,
101 101 repo_type=backend.alias,
102 102 repo_owner=TEST_USER_ADMIN_LOGIN,
103 103 repo_description='DESC',
104 104
105 105 repo_private='false',
106 106 repo_enable_locking='false',
107 107 repo_enable_downloads='false')
108 108 params.update(update_settings)
109 109 self.app.post(
110 110 route_path('edit_repo', repo_name=repo_name),
111 111 params=params, status=302)
112 112
113 113 repo = Repository.get_by_repo_name(repo_name)
114 114 assert repo.user.username == \
115 115 update_settings.get('repo_owner', repo.user.username)
116 116
117 117 assert repo.description == \
118 118 update_settings.get('repo_description', repo.description)
119 119
120 120 assert repo.private == \
121 121 str2bool(update_settings.get(
122 122 'repo_private', repo.private))
123 123
124 124 assert repo.enable_locking == \
125 125 str2bool(update_settings.get(
126 126 'repo_enable_locking', repo.enable_locking))
127 127
128 128 assert repo.enable_downloads == \
129 129 str2bool(update_settings.get(
130 130 'repo_enable_downloads', repo.enable_downloads))
131 131
132 132 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
133 133 repo = user_util.create_repo(repo_type=backend.alias)
134 134 repo_name = repo.repo_name
135 135
136 136 repo_group = user_util.create_repo_group()
137 137 repo_group_name = repo_group.group_name
138 138 new_name = repo_group_name + '_' + repo_name
139 139
140 140 params = fixture._get_repo_create_params(
141 141 csrf_token=csrf_token,
142 142 repo_name=new_name,
143 143 repo_type=backend.alias,
144 144 repo_owner=TEST_USER_ADMIN_LOGIN,
145 145 repo_description='DESC',
146 146 repo_private='false',
147 147 repo_enable_locking='false',
148 148 repo_enable_downloads='false')
149 149 self.app.post(
150 150 route_path('edit_repo', repo_name=repo_name),
151 151 params=params, status=302)
152 152 repo = Repository.get_by_repo_name(new_name)
153 153 assert repo.repo_name == new_name
154 154
155 155 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
156 156 repo = user_util.create_repo(repo_type=backend.alias)
157 157 repo_name = repo.repo_name
158 158
159 159 repo_group = user_util.create_repo_group()
160 160 repo_group_name = repo_group.group_name
161 161 repo_group_id = repo_group.group_id
162 162
163 163 new_name = repo_group_name + '/' + repo_name
164 164 params = fixture._get_repo_create_params(
165 165 csrf_token=csrf_token,
166 166 repo_name=repo_name,
167 167 repo_type=backend.alias,
168 168 repo_owner=TEST_USER_ADMIN_LOGIN,
169 169 repo_description='DESC',
170 170 repo_group=repo_group_id,
171 171 repo_private='false',
172 172 repo_enable_locking='false',
173 173 repo_enable_downloads='false')
174 174 self.app.post(
175 175 route_path('edit_repo', repo_name=repo_name),
176 176 params=params, status=302)
177 177 repo = Repository.get_by_repo_name(new_name)
178 178 assert repo.repo_name == new_name
179 179
180 180 def test_set_private_flag_sets_default_user_permissions_to_none(
181 181 self, autologin_user, backend, csrf_token):
182 182
183 183 # initially repository perm should be read
184 184 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
185 185 assert len(perm) == 1
186 186 assert perm[0].permission.permission_name == 'repository.read'
187 187 assert not backend.repo.private
188 188
189 189 response = self.app.post(
190 190 route_path('edit_repo', repo_name=backend.repo_name),
191 191 params=fixture._get_repo_create_params(
192 192 repo_private='true',
193 193 repo_name=backend.repo_name,
194 194 repo_type=backend.alias,
195 195 repo_owner=TEST_USER_ADMIN_LOGIN,
196 196 csrf_token=csrf_token), status=302)
197 197
198 198 assert_session_flash(
199 199 response,
200 msg='Repository %s updated successfully' % (backend.repo_name))
200 msg='Repository `%s` updated successfully' % (backend.repo_name))
201 201
202 202 repo = Repository.get_by_repo_name(backend.repo_name)
203 203 assert repo.private is True
204 204
205 205 # now the repo default permission should be None
206 206 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
207 207 assert len(perm) == 1
208 208 assert perm[0].permission.permission_name == 'repository.none'
209 209
210 210 response = self.app.post(
211 211 route_path('edit_repo', repo_name=backend.repo_name),
212 212 params=fixture._get_repo_create_params(
213 213 repo_private='false',
214 214 repo_name=backend.repo_name,
215 215 repo_type=backend.alias,
216 216 repo_owner=TEST_USER_ADMIN_LOGIN,
217 217 csrf_token=csrf_token), status=302)
218 218
219 219 assert_session_flash(
220 220 response,
221 msg='Repository %s updated successfully' % (backend.repo_name))
221 msg='Repository `%s` updated successfully' % (backend.repo_name))
222 222 assert backend.repo.private is False
223 223
224 224 # we turn off private now the repo default permission should stay None
225 225 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
226 226 assert len(perm) == 1
227 227 assert perm[0].permission.permission_name == 'repository.none'
228 228
229 229 # update this permission back
230 230 perm[0].permission = Permission.get_by_key('repository.read')
231 231 Session().add(perm[0])
232 232 Session().commit()
@@ -1,251 +1,251 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import deform
24 24 from pyramid.httpexceptions import HTTPFound
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import RepoAppView
28 28 from rhodecode.forms import RcForm
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.scm import RepoGroupList, ScmModel
37 37 from rhodecode.model.validation_schema.schemas import repo_schema
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class RepoSettingsView(RepoAppView):
43 43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46 46
47 47 acl_groups = RepoGroupList(
48 48 RepoGroup.query().all(),
49 49 perm_set=['group.write', 'group.admin'])
50 50 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
51 51 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
52 52
53 53 # in case someone no longer have a group.write access to a repository
54 54 # pre fill the list with this entry, we don't care if this is the same
55 55 # but it will allow saving repo data properly.
56 56 repo_group = self.db_repo.group
57 57 if repo_group and repo_group.group_id not in c.repo_groups_choices:
58 58 c.repo_groups_choices.append(repo_group.group_id)
59 59 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
60 60
61 61 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
62 62 # we might be in missing requirement state, so we load things
63 63 # without touching scm_instance()
64 64 c.landing_revs_choices, c.landing_revs = \
65 65 ScmModel().get_repo_landing_revs()
66 66 else:
67 67 c.landing_revs_choices, c.landing_revs = \
68 68 ScmModel().get_repo_landing_revs(self.db_repo)
69 69
70 70 c.personal_repo_group = c.auth_user.personal_repo_group
71 71 c.repo_fields = RepositoryField.query()\
72 72 .filter(RepositoryField.repository == self.db_repo).all()
73 73
74 74 self._register_global_c(c)
75 75 return c
76 76
77 77 def _get_schema(self, c, old_values=None):
78 78 return repo_schema.RepoSettingsSchema().bind(
79 79 repo_type=self.db_repo.repo_type,
80 80 repo_type_options=[self.db_repo.repo_type],
81 81 repo_ref_options=c.landing_revs_choices,
82 82 repo_ref_items=c.landing_revs,
83 83 repo_repo_group_options=c.repo_groups_choices,
84 84 repo_repo_group_items=c.repo_groups,
85 85 # user caller
86 86 user=self._rhodecode_user,
87 87 old_values=old_values
88 88 )
89 89
90 90 @LoginRequired()
91 91 @HasRepoPermissionAnyDecorator('repository.admin')
92 92 @view_config(
93 93 route_name='edit_repo', request_method='GET',
94 94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
95 95 def edit_settings(self):
96 96 c = self.load_default_context()
97 97 c.active = 'settings'
98 98
99 99 defaults = RepoModel()._get_defaults(self.db_repo_name)
100 100 defaults['repo_owner'] = defaults['user']
101 101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
102 102
103 103 schema = self._get_schema(c)
104 104 c.form = RcForm(schema, appstruct=defaults)
105 105 return self._get_template_context(c)
106 106
107 107 @LoginRequired()
108 108 @HasRepoPermissionAnyDecorator('repository.admin')
109 109 @CSRFRequired()
110 110 @view_config(
111 111 route_name='edit_repo', request_method='POST',
112 112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 113 def edit_settings_update(self):
114 114 _ = self.request.translate
115 115 c = self.load_default_context()
116 116 c.active = 'settings'
117 117 old_repo_name = self.db_repo_name
118 118
119 119 old_values = self.db_repo.get_api_data()
120 120 schema = self._get_schema(c, old_values=old_values)
121 121
122 122 c.form = RcForm(schema)
123 123 pstruct = self.request.POST.items()
124 124 pstruct.append(('repo_type', self.db_repo.repo_type))
125 125 try:
126 126 schema_data = c.form.validate(pstruct)
127 127 except deform.ValidationFailure as err_form:
128 128 return self._get_template_context(c)
129 129
130 130 # data is now VALID, proceed with updates
131 131 # save validated data back into the updates dict
132 132 validated_updates = dict(
133 133 repo_name=schema_data['repo_group']['repo_name_without_group'],
134 134 repo_group=schema_data['repo_group']['repo_group_id'],
135 135
136 136 user=schema_data['repo_owner'],
137 137 repo_description=schema_data['repo_description'],
138 138 repo_private=schema_data['repo_private'],
139 139 clone_uri=schema_data['repo_clone_uri'],
140 140 repo_landing_rev=schema_data['repo_landing_commit_ref'],
141 141 repo_enable_statistics=schema_data['repo_enable_statistics'],
142 142 repo_enable_locking=schema_data['repo_enable_locking'],
143 143 repo_enable_downloads=schema_data['repo_enable_downloads'],
144 144 )
145 145 # detect if CLONE URI changed, if we get OLD means we keep old values
146 146 if schema_data['repo_clone_uri_change'] == 'OLD':
147 147 validated_updates['clone_uri'] = self.db_repo.clone_uri
148 148
149 149 # use the new full name for redirect
150 150 new_repo_name = schema_data['repo_group']['repo_name_with_group']
151 151
152 152 # save extra fields into our validated data
153 153 for key, value in pstruct:
154 154 if key.startswith(RepositoryField.PREFIX):
155 155 validated_updates[key] = value
156 156
157 157 try:
158 158 RepoModel().update(self.db_repo, **validated_updates)
159 159 ScmModel().mark_for_invalidation(new_repo_name)
160 160
161 161 audit_logger.store_web(
162 162 'repo.edit', action_data={'old_data': old_values},
163 163 user=self._rhodecode_user, repo=self.db_repo)
164 164
165 165 Session().commit()
166 166
167 h.flash(_('Repository {} updated successfully').format(
167 h.flash(_('Repository `{}` updated successfully').format(
168 168 old_repo_name), category='success')
169 169 except Exception:
170 170 log.exception("Exception during update of repository")
171 171 h.flash(_('Error occurred during update of repository {}').format(
172 172 old_repo_name), category='error')
173 173
174 174 raise HTTPFound(
175 175 h.route_path('edit_repo', repo_name=new_repo_name))
176 176
177 177 @LoginRequired()
178 178 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
179 179 @view_config(
180 180 route_name='repo_edit_toggle_locking', request_method='GET',
181 181 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
182 182 def toggle_locking(self):
183 183 """
184 184 Toggle locking of repository by simple GET call to url
185 185 """
186 186 _ = self.request.translate
187 187 repo = self.db_repo
188 188
189 189 try:
190 190 if repo.enable_locking:
191 191 if repo.locked[0]:
192 192 Repository.unlock(repo)
193 193 action = _('Unlocked')
194 194 else:
195 195 Repository.lock(
196 196 repo, self._rhodecode_user.user_id,
197 197 lock_reason=Repository.LOCK_WEB)
198 198 action = _('Locked')
199 199
200 200 h.flash(_('Repository has been %s') % action,
201 201 category='success')
202 202 except Exception:
203 203 log.exception("Exception during unlocking")
204 204 h.flash(_('An error occurred during unlocking'),
205 205 category='error')
206 206 raise HTTPFound(
207 207 h.route_path('repo_summary', repo_name=self.db_repo_name))
208 208
209 209 @LoginRequired()
210 210 @HasRepoPermissionAnyDecorator('repository.admin')
211 211 @view_config(
212 212 route_name='edit_repo_statistics', request_method='GET',
213 213 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
214 214 def edit_statistics_form(self):
215 215 c = self.load_default_context()
216 216
217 217 if self.db_repo.stats:
218 218 # this is on what revision we ended up so we add +1 for count
219 219 last_rev = self.db_repo.stats.stat_on_revision + 1
220 220 else:
221 221 last_rev = 0
222 222
223 223 c.active = 'statistics'
224 224 c.stats_revision = last_rev
225 225 c.repo_last_rev = self.rhodecode_vcs_repo.count()
226 226
227 227 if last_rev == 0 or c.repo_last_rev == 0:
228 228 c.stats_percentage = 0
229 229 else:
230 230 c.stats_percentage = '%.2f' % (
231 231 (float((last_rev)) / c.repo_last_rev) * 100)
232 232 return self._get_template_context(c)
233 233
234 234 @LoginRequired()
235 235 @HasRepoPermissionAnyDecorator('repository.admin')
236 236 @CSRFRequired()
237 237 @view_config(
238 238 route_name='edit_repo_statistics_reset', request_method='POST',
239 239 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
240 240 def repo_statistics_reset(self):
241 241 _ = self.request.translate
242 242
243 243 try:
244 244 RepoModel().delete_stats(self.db_repo_name)
245 245 Session().commit()
246 246 except Exception:
247 247 log.exception('Edit statistics failure')
248 248 h.flash(_('An error occurred during deletion of repository stats'),
249 249 category='error')
250 250 raise HTTPFound(
251 251 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,311 +1,264 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('show_user', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(
108 108 directory=config['pylons.paths']['controllers'],
109 109 always_scan=config['debug'])
110 110 rmap.minimization = False
111 111 rmap.explicit = False
112 112
113 113 from rhodecode.lib.utils2 import str2bool
114 114 from rhodecode.model import repo, repo_group
115 115
116 116 def check_repo(environ, match_dict):
117 117 """
118 118 check for valid repository for proper 404 handling
119 119
120 120 :param environ:
121 121 :param match_dict:
122 122 """
123 123 repo_name = match_dict.get('repo_name')
124 124
125 125 if match_dict.get('f_path'):
126 126 # fix for multiple initial slashes that causes errors
127 127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 128 repo_model = repo.RepoModel()
129 129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 130 # if we match quickly from database, short circuit the operation,
131 131 # and validate repo based on the type.
132 132 if by_name_match:
133 133 return True
134 134
135 135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 136 if by_id_match:
137 137 repo_name = by_id_match.repo_name
138 138 match_dict['repo_name'] = repo_name
139 139 return True
140 140
141 141 return False
142 142
143 143 def check_group(environ, match_dict):
144 144 """
145 145 check for valid repository group path for proper 404 handling
146 146
147 147 :param environ:
148 148 :param match_dict:
149 149 """
150 150 repo_group_name = match_dict.get('group_name')
151 151 repo_group_model = repo_group.RepoGroupModel()
152 152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 153 if by_name_match:
154 154 return True
155 155
156 156 return False
157 157
158 158 def check_user_group(environ, match_dict):
159 159 """
160 160 check for valid user group for proper 404 handling
161 161
162 162 :param environ:
163 163 :param match_dict:
164 164 """
165 165 return True
166 166
167 167 def check_int(environ, match_dict):
168 168 return match_dict.get('id').isdigit()
169 169
170 170
171 171 #==========================================================================
172 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 175 # ADMIN SETTINGS ROUTES
223 176 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 177 controller='admin/settings') as m:
225 178
226 179 # default
227 180 m.connect('admin_settings', '/settings',
228 181 action='settings_global_update',
229 182 conditions={'method': ['POST']})
230 183 m.connect('admin_settings', '/settings',
231 184 action='settings_global', conditions={'method': ['GET']})
232 185
233 186 m.connect('admin_settings_vcs', '/settings/vcs',
234 187 action='settings_vcs_update',
235 188 conditions={'method': ['POST']})
236 189 m.connect('admin_settings_vcs', '/settings/vcs',
237 190 action='settings_vcs',
238 191 conditions={'method': ['GET']})
239 192 m.connect('admin_settings_vcs', '/settings/vcs',
240 193 action='delete_svn_pattern',
241 194 conditions={'method': ['DELETE']})
242 195
243 196 m.connect('admin_settings_mapping', '/settings/mapping',
244 197 action='settings_mapping_update',
245 198 conditions={'method': ['POST']})
246 199 m.connect('admin_settings_mapping', '/settings/mapping',
247 200 action='settings_mapping', conditions={'method': ['GET']})
248 201
249 202 m.connect('admin_settings_global', '/settings/global',
250 203 action='settings_global_update',
251 204 conditions={'method': ['POST']})
252 205 m.connect('admin_settings_global', '/settings/global',
253 206 action='settings_global', conditions={'method': ['GET']})
254 207
255 208 m.connect('admin_settings_visual', '/settings/visual',
256 209 action='settings_visual_update',
257 210 conditions={'method': ['POST']})
258 211 m.connect('admin_settings_visual', '/settings/visual',
259 212 action='settings_visual', conditions={'method': ['GET']})
260 213
261 214 m.connect('admin_settings_issuetracker',
262 215 '/settings/issue-tracker', action='settings_issuetracker',
263 216 conditions={'method': ['GET']})
264 217 m.connect('admin_settings_issuetracker_save',
265 218 '/settings/issue-tracker/save',
266 219 action='settings_issuetracker_save',
267 220 conditions={'method': ['POST']})
268 221 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
269 222 action='settings_issuetracker_test',
270 223 conditions={'method': ['POST']})
271 224 m.connect('admin_issuetracker_delete',
272 225 '/settings/issue-tracker/delete',
273 226 action='settings_issuetracker_delete',
274 227 conditions={'method': ['DELETE']})
275 228
276 229 m.connect('admin_settings_email', '/settings/email',
277 230 action='settings_email_update',
278 231 conditions={'method': ['POST']})
279 232 m.connect('admin_settings_email', '/settings/email',
280 233 action='settings_email', conditions={'method': ['GET']})
281 234
282 235 m.connect('admin_settings_hooks', '/settings/hooks',
283 236 action='settings_hooks_update',
284 237 conditions={'method': ['POST', 'DELETE']})
285 238 m.connect('admin_settings_hooks', '/settings/hooks',
286 239 action='settings_hooks', conditions={'method': ['GET']})
287 240
288 241 m.connect('admin_settings_search', '/settings/search',
289 242 action='settings_search', conditions={'method': ['GET']})
290 243
291 244 m.connect('admin_settings_supervisor', '/settings/supervisor',
292 245 action='settings_supervisor', conditions={'method': ['GET']})
293 246 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
294 247 action='settings_supervisor_log', conditions={'method': ['GET']})
295 248
296 249 m.connect('admin_settings_labs', '/settings/labs',
297 250 action='settings_labs_update',
298 251 conditions={'method': ['POST']})
299 252 m.connect('admin_settings_labs', '/settings/labs',
300 253 action='settings_labs', conditions={'method': ['GET']})
301 254
302 255 # ADMIN MY ACCOUNT
303 256 with rmap.submapper(path_prefix=ADMIN_PREFIX,
304 257 controller='admin/my_account') as m:
305 258
306 259 # NOTE(marcink): this needs to be kept for password force flag to be
307 260 # handled in pylons controllers, remove after full migration to pyramid
308 261 m.connect('my_account_password', '/my_account/password',
309 262 action='my_account_password', conditions={'method': ['GET']})
310 263
311 264 return rmap
@@ -1,230 +1,230 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
24 24 from rhodecode.lib.utils2 import safe_int
25 25 from rhodecode.model.db import Repository, Integration, RepoGroup
26 26 from rhodecode.integrations import integration_type_registry
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 def includeme(config):
32 32
33 33 # global integrations
34 34 config.add_route('global_integrations_new',
35 35 ADMIN_PREFIX + '/integrations/new')
36 36 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
37 37 attr='new_integration',
38 38 renderer='rhodecode:templates/admin/integrations/new.mako',
39 39 request_method='GET',
40 40 route_name='global_integrations_new')
41 41
42 42 config.add_route('global_integrations_home',
43 43 ADMIN_PREFIX + '/integrations')
44 44 config.add_route('global_integrations_list',
45 45 ADMIN_PREFIX + '/integrations/{integration}')
46 46 for route_name in ['global_integrations_home', 'global_integrations_list']:
47 47 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
48 48 attr='integration_list',
49 49 renderer='rhodecode:templates/admin/integrations/list.mako',
50 50 request_method='GET',
51 51 route_name=route_name)
52 52
53 53 config.add_route('global_integrations_create',
54 54 ADMIN_PREFIX + '/integrations/{integration}/new',
55 55 custom_predicates=(valid_integration,))
56 56 config.add_route('global_integrations_edit',
57 57 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
58 58 custom_predicates=(valid_integration,))
59 59
60 60 for route_name in ['global_integrations_create', 'global_integrations_edit']:
61 61 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
62 62 attr='settings_get',
63 63 renderer='rhodecode:templates/admin/integrations/form.mako',
64 64 request_method='GET',
65 65 route_name=route_name)
66 66 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
67 67 attr='settings_post',
68 68 renderer='rhodecode:templates/admin/integrations/form.mako',
69 69 request_method='POST',
70 70 route_name=route_name)
71 71
72 72 # repo group integrations
73 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 75 repo_group_route=True)
76 76
77 77 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
78 78 attr='integration_list',
79 79 renderer='rhodecode:templates/admin/integrations/list.mako',
80 80 request_method='GET',
81 81 route_name='repo_group_integrations_home')
82 82
83 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 85 repo_group_route=True)
86 86 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
87 87 attr='new_integration',
88 88 renderer='rhodecode:templates/admin/integrations/new.mako',
89 89 request_method='GET',
90 90 route_name='repo_group_integrations_new')
91 91
92 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 94 repo_group_route=True,
95 95 custom_predicates=(valid_integration,))
96 96 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
97 97 attr='integration_list',
98 98 renderer='rhodecode:templates/admin/integrations/list.mako',
99 99 request_method='GET',
100 100 route_name='repo_group_integrations_list')
101 101
102 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 104 repo_group_route=True,
105 105 custom_predicates=(valid_integration,))
106 106 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
107 107 attr='settings_get',
108 108 renderer='rhodecode:templates/admin/integrations/form.mako',
109 109 request_method='GET',
110 110 route_name='repo_group_integrations_create')
111 111 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
112 112 attr='settings_post',
113 113 renderer='rhodecode:templates/admin/integrations/form.mako',
114 114 request_method='POST',
115 115 route_name='repo_group_integrations_create')
116 116
117 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 119 repo_group_route=True,
120 120 custom_predicates=(valid_integration,))
121 121
122 122 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
123 123 attr='settings_get',
124 124 renderer='rhodecode:templates/admin/integrations/form.mako',
125 125 request_method='GET',
126 126 route_name='repo_group_integrations_edit')
127 127 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
128 128 attr='settings_post',
129 129 renderer='rhodecode:templates/admin/integrations/form.mako',
130 130 request_method='POST',
131 131 route_name='repo_group_integrations_edit')
132 132
133 133 # repo integrations
134 134 config.add_route('repo_integrations_home',
135 135 add_route_requirements('/{repo_name}/settings/integrations'),
136 136 repo_route=True)
137 137 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
138 138 attr='integration_list',
139 139 request_method='GET',
140 140 renderer='rhodecode:templates/admin/integrations/list.mako',
141 141 route_name='repo_integrations_home')
142 142
143 143 config.add_route('repo_integrations_new',
144 144 add_route_requirements('/{repo_name}/settings/integrations/new'),
145 145 repo_route=True)
146 146 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
147 147 attr='new_integration',
148 148 renderer='rhodecode:templates/admin/integrations/new.mako',
149 149 request_method='GET',
150 150 route_name='repo_integrations_new')
151 151
152 152 config.add_route('repo_integrations_list',
153 153 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
154 154 repo_route=True,
155 155 custom_predicates=(valid_integration,))
156 156 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
157 157 attr='integration_list',
158 158 request_method='GET',
159 159 renderer='rhodecode:templates/admin/integrations/list.mako',
160 160 route_name='repo_integrations_list')
161 161
162 162 config.add_route('repo_integrations_create',
163 163 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
164 164 repo_route=True,
165 165 custom_predicates=(valid_integration,))
166 166 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
167 167 attr='settings_get',
168 168 renderer='rhodecode:templates/admin/integrations/form.mako',
169 169 request_method='GET',
170 170 route_name='repo_integrations_create')
171 171 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
172 172 attr='settings_post',
173 173 renderer='rhodecode:templates/admin/integrations/form.mako',
174 174 request_method='POST',
175 175 route_name='repo_integrations_create')
176 176
177 177 config.add_route('repo_integrations_edit',
178 178 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
179 179 repo_route=True,
180 180 custom_predicates=(valid_integration,))
181 181 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
182 182 attr='settings_get',
183 183 renderer='rhodecode:templates/admin/integrations/form.mako',
184 184 request_method='GET',
185 185 route_name='repo_integrations_edit')
186 186 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
187 187 attr='settings_post',
188 188 renderer='rhodecode:templates/admin/integrations/form.mako',
189 189 request_method='POST',
190 190 route_name='repo_integrations_edit')
191 191
192 192
193 193
194 194 def valid_integration(info, request):
195 195 integration_type = info['match']['integration']
196 196 integration_id = info['match'].get('integration_id')
197 197
198 198 if integration_type not in integration_type_registry:
199 199 return False
200 200
201 201 if integration_id:
202 202 if not safe_int(integration_id):
203 203 return False
204 204
205 205 integration = Integration.get(integration_id)
206 206 if not integration:
207 207 return False
208 208 if integration.integration_type != integration_type:
209 209 return False
210 210
211 211 # match types to repo or repo group
212 212 repo_name = info['match'].get('repo_name')
213 213 repo_group_name = info['match'].get('repo_group_name')
214 214 repo, repo_group = None, None
215 215 if repo_name:
216 216 repo = Repository.get_by_repo_name(repo_name)
217 217 if not repo:
218 218 return False
219 219
220 220 if repo_group_name:
221 221 repo_group = RepoGroup.get_by_group_name(repo_group_name)
222 222 if not repo_group:
223 223 return False
224 224
225 225 if repo and repo.repo_id != integration.repo_id:
226 226 return False
227 227 if repo_group and repo_group.group_id != integration.repo_group_id:
228 228 return False
229 229
230 230 return True
@@ -1,269 +1,269 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.apps._base import ADMIN_PREFIX
24 24 from rhodecode.model.db import Integration
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.integrations import integration_type_registry
27 27
28 28
29 29 def route_path(name, **kwargs):
30 30 return {
31 31 'home': '/',
32 32 }[name].format(**kwargs)
33 33
34 34
35 35 @pytest.mark.usefixtures('app', 'autologin_user')
36 36 class TestIntegrationsView(object):
37 37 pass
38 38
39 39
40 40 class TestGlobalIntegrationsView(TestIntegrationsView):
41 41 def test_index_no_integrations(self):
42 42 url = ADMIN_PREFIX + '/integrations'
43 43 response = self.app.get(url)
44 44
45 45 assert response.status_code == 200
46 46 response.mustcontain('exist yet')
47 47
48 48 def test_index_with_integrations(self, global_integration_stub):
49 49 url = ADMIN_PREFIX + '/integrations'
50 50 response = self.app.get(url)
51 51
52 52 assert response.status_code == 200
53 53 response.mustcontain(no=['exist yet'])
54 54 response.mustcontain(global_integration_stub.name)
55 55
56 56 @pytest.mark.parametrize(
57 57 'IntegrationType', integration_type_registry.values())
58 58 def test_new_integration_page(self, IntegrationType):
59 59 url = ADMIN_PREFIX + '/integrations/new'
60 60
61 61 response = self.app.get(url, status=200)
62 62 if not IntegrationType.is_dummy:
63 63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
64 64 integration=IntegrationType.key)
65 65 response.mustcontain(url)
66 66
67 67 @pytest.mark.parametrize(
68 68 'IntegrationType', integration_type_registry.values())
69 69 def test_get_create_integration_page(self, IntegrationType):
70 70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
71 71 integration_key=IntegrationType.key)
72 72 if IntegrationType.is_dummy:
73 73 self.app.get(url, status=404)
74 74 else:
75 75 response = self.app.get(url, status=200)
76 76 response.mustcontain(IntegrationType.display_name)
77 77
78 78 def test_post_integration_page(self, StubIntegrationType, csrf_token,
79 79 test_repo_group, backend_random):
80 80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
81 81 integration_key=StubIntegrationType.key)
82 82
83 83 _post_integration_test_helper(
84 84 self.app, url, csrf_token, admin_view=True,
85 85 repo=backend_random.repo, repo_group=test_repo_group)
86 86
87 87
88 88 class TestRepoIntegrationsView(TestIntegrationsView):
89 89 def test_index_no_integrations(self, backend_random):
90 90 url = '/{repo_name}/settings/integrations'.format(
91 91 repo_name=backend_random.repo.repo_name)
92 92 response = self.app.get(url)
93 93
94 94 assert response.status_code == 200
95 95 response.mustcontain('exist yet')
96 96
97 97 def test_index_with_integrations(self, repo_integration_stub):
98 98 url = '/{repo_name}/settings/integrations'.format(
99 99 repo_name=repo_integration_stub.repo.repo_name)
100 100 stub_name = repo_integration_stub.name
101 101
102 102 response = self.app.get(url)
103 103
104 104 assert response.status_code == 200
105 105 response.mustcontain(stub_name)
106 106 response.mustcontain(no=['exist yet'])
107 107
108 108 @pytest.mark.parametrize(
109 109 'IntegrationType', integration_type_registry.values())
110 110 def test_new_integration_page(self, backend_random, IntegrationType):
111 111 repo_name = backend_random.repo.repo_name
112 112 url = '/{repo_name}/settings/integrations/new'.format(
113 113 repo_name=repo_name)
114 114
115 115 response = self.app.get(url, status=200)
116 116
117 117 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
118 118 repo_name=repo_name,
119 119 integration=IntegrationType.key)
120 120 if not IntegrationType.is_dummy:
121 121 response.mustcontain(url)
122 122
123 123 @pytest.mark.parametrize(
124 124 'IntegrationType', integration_type_registry.values())
125 125 def test_get_create_integration_page(self, backend_random, IntegrationType):
126 126 repo_name = backend_random.repo.repo_name
127 127 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
128 128 repo_name=repo_name, integration_key=IntegrationType.key)
129 129 if IntegrationType.is_dummy:
130 130 self.app.get(url, status=404)
131 131 else:
132 132 response = self.app.get(url, status=200)
133 133 response.mustcontain(IntegrationType.display_name)
134 134
135 135 def test_post_integration_page(self, backend_random, test_repo_group,
136 136 StubIntegrationType, csrf_token):
137 137 repo_name = backend_random.repo.repo_name
138 138 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
139 139 repo_name=repo_name, integration_key=StubIntegrationType.key)
140 140
141 141 _post_integration_test_helper(
142 142 self.app, url, csrf_token, admin_view=False,
143 143 repo=backend_random.repo, repo_group=test_repo_group)
144 144
145 145
146 146 class TestRepoGroupIntegrationsView(TestIntegrationsView):
147 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 149 repo_group_name=test_repo_group.group_name)
150 150 response = self.app.get(url)
151 151
152 152 assert response.status_code == 200
153 153 response.mustcontain('exist yet')
154 154
155 155 def test_index_with_integrations(
156 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 159 repo_group_name=test_repo_group.group_name)
160 160
161 161 stub_name = repogroup_integration_stub.name
162 162 response = self.app.get(url)
163 163
164 164 assert response.status_code == 200
165 165 response.mustcontain(no=['exist yet'])
166 166 response.mustcontain(stub_name)
167 167
168 168 def test_new_integration_page(self, test_repo_group):
169 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 171 repo_group_name=test_repo_group.group_name)
172 172
173 173 response = self.app.get(url)
174 174
175 175 assert response.status_code == 200
176 176
177 177 for integration_key, integration_obj in integration_type_registry.items():
178 178 if not integration_obj.is_dummy:
179 179 nurl = (
180 '/{repo_group_name}/settings/integrations/{integration}/new').format(
180 '/{repo_group_name}/_settings/integrations/{integration}/new').format(
181 181 repo_group_name=repo_group_name,
182 182 integration=integration_key)
183 183 response.mustcontain(nurl)
184 184
185 185 @pytest.mark.parametrize(
186 186 'IntegrationType', integration_type_registry.values())
187 187 def test_get_create_integration_page(
188 188 self, test_repo_group, IntegrationType):
189 189
190 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 192 ).format(repo_group_name=repo_group_name,
193 193 integration_key=IntegrationType.key)
194 194
195 195 if not IntegrationType.is_dummy:
196 196 response = self.app.get(url, status=200)
197 197 response.mustcontain(IntegrationType.display_name)
198 198 else:
199 199 self.app.get(url, status=404)
200 200
201 201 def test_post_integration_page(self, test_repo_group, backend_random,
202 202 StubIntegrationType, csrf_token):
203 203
204 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 206 ).format(repo_group_name=repo_group_name,
207 207 integration_key=StubIntegrationType.key)
208 208
209 209 _post_integration_test_helper(
210 210 self.app, url, csrf_token, admin_view=False,
211 211 repo=backend_random.repo, repo_group=test_repo_group)
212 212
213 213
214 214 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
215 215 admin_view):
216 216 """
217 217 Posts form data to create integration at the url given then deletes it and
218 218 checks if the redirect url is correct.
219 219 """
220 220 repo_name = repo.repo_name
221 221 repo_group_name = repo_group.group_name
222 222 app.post(url, params={}, status=403) # missing csrf check
223 223 response = app.post(url, params={'csrf_token': csrf_token})
224 224 assert response.status_code == 200
225 225 response.mustcontain('Errors exist')
226 226
227 227 scopes_destinations = [
228 228 ('global',
229 229 ADMIN_PREFIX + '/integrations'),
230 230 ('root-repos',
231 231 ADMIN_PREFIX + '/integrations'),
232 232 ('repo:%s' % repo_name,
233 233 '/%s/settings/integrations' % repo_name),
234 234 ('repogroup:%s' % repo_group_name,
235 '/%s/settings/integrations' % repo_group_name),
235 '/%s/_settings/integrations' % repo_group_name),
236 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 240 for scope, destination in scopes_destinations:
241 241 if admin_view:
242 242 destination = ADMIN_PREFIX + '/integrations'
243 243
244 244 form_data = [
245 245 ('csrf_token', csrf_token),
246 246 ('__start__', 'options:mapping'),
247 247 ('name', 'test integration'),
248 248 ('scope', scope),
249 249 ('enabled', 'true'),
250 250 ('__end__', 'options:mapping'),
251 251 ('__start__', 'settings:mapping'),
252 252 ('test_int_field', '34'),
253 253 ('test_string_field', ''), # empty value on purpose as it's required
254 254 ('__end__', 'settings:mapping'),
255 255 ]
256 256 errors_response = app.post(url, form_data)
257 257 assert 'Errors exist' in errors_response.body
258 258
259 259 form_data[-2] = ('test_string_field', 'data!')
260 260 assert Session().query(Integration).count() == 0
261 261 created_response = app.post(url, form_data)
262 262 assert Session().query(Integration).count() == 1
263 263
264 264 delete_response = app.post(
265 265 created_response.location,
266 266 params={'csrf_token': csrf_token, 'delete': 'delete'})
267 267
268 268 assert Session().query(Integration).count() == 0
269 269 assert delete_response.location.endswith(destination)
@@ -1,738 +1,764 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 repo group model for RhodeCode
24 24 """
25 25
26 26 import os
27 27 import datetime
28 28 import itertools
29 29 import logging
30 30 import shutil
31 31 import traceback
32 32 import string
33 33
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 36 from rhodecode import events
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import (_hash_key,
39 39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 40 UserGroup, Repository)
41 41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 42 from rhodecode.lib.caching_query import FromCache
43 43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoGroupModel(BaseModel):
49 49
50 50 cls = RepoGroup
51 51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 52 PERSONAL_GROUP_PATTERN = '${username}' # default
53 53
54 54 def _get_user_group(self, users_group):
55 55 return self._get_instance(UserGroup, users_group,
56 56 callback=UserGroup.get_by_group_name)
57 57
58 58 def _get_repo_group(self, repo_group):
59 59 return self._get_instance(RepoGroup, repo_group,
60 60 callback=RepoGroup.get_by_group_name)
61 61
62 62 @LazyProperty
63 63 def repos_path(self):
64 64 """
65 65 Gets the repositories root path from database
66 66 """
67 67
68 68 settings_model = VcsSettingsModel(sa=self.sa)
69 69 return settings_model.get_repos_location()
70 70
71 71 def get_by_group_name(self, repo_group_name, cache=None):
72 72 repo = self.sa.query(RepoGroup) \
73 73 .filter(RepoGroup.group_name == repo_group_name)
74 74
75 75 if cache:
76 76 name_key = _hash_key(repo_group_name)
77 77 repo = repo.options(
78 78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 79 return repo.scalar()
80 80
81 81 def get_default_create_personal_repo_group(self):
82 82 value = SettingsModel().get_setting_by_name(
83 83 'create_personal_repo_group')
84 84 return value.app_settings_value if value else None or False
85 85
86 86 def get_personal_group_name_pattern(self):
87 87 value = SettingsModel().get_setting_by_name(
88 88 'personal_repo_group_pattern')
89 89 val = value.app_settings_value if value else None
90 90 group_template = val or self.PERSONAL_GROUP_PATTERN
91 91
92 92 group_template = group_template.lstrip('/')
93 93 return group_template
94 94
95 95 def get_personal_group_name(self, user):
96 96 template = self.get_personal_group_name_pattern()
97 97 return string.Template(template).safe_substitute(
98 98 username=user.username,
99 99 user_id=user.user_id,
100 100 )
101 101
102 102 def create_personal_repo_group(self, user, commit_early=True):
103 103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 104 personal_repo_group_name = self.get_personal_group_name(user)
105 105
106 106 # create a new one
107 107 RepoGroupModel().create(
108 108 group_name=personal_repo_group_name,
109 109 group_description=desc,
110 110 owner=user.username,
111 111 personal=True,
112 112 commit_early=commit_early)
113 113
114 114 def _create_default_perms(self, new_group):
115 115 # create default permission
116 116 default_perm = 'group.read'
117 117 def_user = User.get_default_user()
118 118 for p in def_user.user_perms:
119 119 if p.permission.permission_name.startswith('group.'):
120 120 default_perm = p.permission.permission_name
121 121 break
122 122
123 123 repo_group_to_perm = UserRepoGroupToPerm()
124 124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125 125
126 126 repo_group_to_perm.group = new_group
127 127 repo_group_to_perm.user_id = def_user.user_id
128 128 return repo_group_to_perm
129 129
130 130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 131 get_object=False):
132 132 """
133 133 Get's the group name and a parent group name from given group name.
134 134 If repo_in_path is set to truth, we asume the full path also includes
135 135 repo name, in such case we clean the last element.
136 136
137 137 :param group_name_full:
138 138 """
139 139 split_paths = 1
140 140 if repo_in_path:
141 141 split_paths = 2
142 142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143 143
144 144 if repo_in_path and len(_parts) > 1:
145 145 # such case last element is the repo_name
146 146 _parts.pop(-1)
147 147 group_name_cleaned = _parts[-1] # just the group name
148 148 parent_repo_group_name = None
149 149
150 150 if len(_parts) > 1:
151 151 parent_repo_group_name = _parts[0]
152 152
153 153 parent_group = None
154 154 if parent_repo_group_name:
155 155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156 156
157 157 if get_object:
158 158 return group_name_cleaned, parent_repo_group_name, parent_group
159 159
160 160 return group_name_cleaned, parent_repo_group_name
161 161
162 162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 163 create_path = os.path.join(self.repos_path, group_name)
164 164 log.debug('creating new group in %s', create_path)
165 165
166 166 if os.path.isdir(create_path):
167 167 if exc_on_failure:
168 168 abs_create_path = os.path.abspath(create_path)
169 169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 170 return False
171 171 return True
172 172
173 173 def _create_group(self, group_name):
174 174 """
175 175 makes repository group on filesystem
176 176
177 177 :param repo_name:
178 178 :param parent_id:
179 179 """
180 180
181 181 self.check_exist_filesystem(group_name)
182 182 create_path = os.path.join(self.repos_path, group_name)
183 183 log.debug('creating new group in %s', create_path)
184 184 os.makedirs(create_path, mode=0755)
185 185 log.debug('created group in %s', create_path)
186 186
187 187 def _rename_group(self, old, new):
188 188 """
189 189 Renames a group on filesystem
190 190
191 191 :param group_name:
192 192 """
193 193
194 194 if old == new:
195 195 log.debug('skipping group rename')
196 196 return
197 197
198 198 log.debug('renaming repository group from %s to %s', old, new)
199 199
200 200 old_path = os.path.join(self.repos_path, old)
201 201 new_path = os.path.join(self.repos_path, new)
202 202
203 203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204 204
205 205 if os.path.isdir(new_path):
206 206 raise Exception('Was trying to rename to already '
207 207 'existing dir %s' % new_path)
208 208 shutil.move(old_path, new_path)
209 209
210 210 def _delete_filesystem_group(self, group, force_delete=False):
211 211 """
212 212 Deletes a group from a filesystem
213 213
214 214 :param group: instance of group from database
215 215 :param force_delete: use shutil rmtree to remove all objects
216 216 """
217 217 paths = group.full_path.split(RepoGroup.url_sep())
218 218 paths = os.sep.join(paths)
219 219
220 220 rm_path = os.path.join(self.repos_path, paths)
221 221 log.info("Removing group %s", rm_path)
222 222 # delete only if that path really exists
223 223 if os.path.isdir(rm_path):
224 224 if force_delete:
225 225 shutil.rmtree(rm_path)
226 226 else:
227 227 # archive that group`
228 228 _now = datetime.datetime.now()
229 229 _ms = str(_now.microsecond).rjust(6, '0')
230 230 _d = 'rm__%s_GROUP_%s' % (
231 231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233 233
234 234 def create(self, group_name, group_description, owner, just_db=False,
235 235 copy_permissions=False, personal=None, commit_early=True):
236 236
237 237 (group_name_cleaned,
238 238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239 239
240 240 parent_group = None
241 241 if parent_group_name:
242 242 parent_group = self._get_repo_group(parent_group_name)
243 243 if not parent_group:
244 244 # we tried to create a nested group, but the parent is not
245 245 # existing
246 246 raise ValueError(
247 247 'Parent group `%s` given in `%s` group name '
248 248 'is not yet existing.' % (parent_group_name, group_name))
249 249
250 250 # because we are doing a cleanup, we need to check if such directory
251 251 # already exists. If we don't do that we can accidentally delete
252 252 # existing directory via cleanup that can cause data issues, since
253 253 # delete does a folder rename to special syntax later cleanup
254 254 # functions can delete this
255 255 cleanup_group = self.check_exist_filesystem(group_name,
256 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 261 try:
258 user = self._get_user(owner)
259 262 new_repo_group = RepoGroup()
260 263 new_repo_group.user = user
261 264 new_repo_group.group_description = group_description or group_name
262 265 new_repo_group.parent_group = parent_group
263 266 new_repo_group.group_name = group_name
264 267 new_repo_group.personal = personal
265 268
266 269 self.sa.add(new_repo_group)
267 270
268 271 # create an ADMIN permission for owner except if we're super admin,
269 272 # later owner should go into the owner field of groups
270 273 if not user.is_admin:
271 274 self.grant_user_permission(repo_group=new_repo_group,
272 275 user=owner, perm='group.admin')
273 276
274 277 if parent_group and copy_permissions:
275 278 # copy permissions from parent
276 279 user_perms = UserRepoGroupToPerm.query() \
277 280 .filter(UserRepoGroupToPerm.group == parent_group).all()
278 281
279 282 group_perms = UserGroupRepoGroupToPerm.query() \
280 283 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281 284
282 285 for perm in user_perms:
283 286 # don't copy over the permission for user who is creating
284 287 # this group, if he is not super admin he get's admin
285 288 # permission set above
286 289 if perm.user != user or user.is_admin:
287 290 UserRepoGroupToPerm.create(
288 291 perm.user, new_repo_group, perm.permission)
289 292
290 293 for perm in group_perms:
291 294 UserGroupRepoGroupToPerm.create(
292 295 perm.users_group, new_repo_group, perm.permission)
293 296 else:
294 297 perm_obj = self._create_default_perms(new_repo_group)
295 298 self.sa.add(perm_obj)
296 299
297 300 # now commit the changes, earlier so we are sure everything is in
298 301 # the database.
299 302 if commit_early:
300 303 self.sa.commit()
301 304 if not just_db:
302 305 self._create_group(new_repo_group.group_name)
303 306
304 307 # trigger the post hook
305 308 from rhodecode.lib.hooks_base import log_create_repository_group
306 309 repo_group = RepoGroup.get_by_group_name(group_name)
307 310 log_create_repository_group(
308 311 created_by=user.username, **repo_group.get_dict())
309 312
310 313 # Trigger create event.
311 314 events.trigger(events.RepoGroupCreateEvent(repo_group))
312 315
313 316 return new_repo_group
314 317 except Exception:
315 318 self.sa.rollback()
316 319 log.exception('Exception occurred when creating repository group, '
317 320 'doing cleanup...')
318 321 # rollback things manually !
319 322 repo_group = RepoGroup.get_by_group_name(group_name)
320 323 if repo_group:
321 324 RepoGroup.delete(repo_group.group_id)
322 325 self.sa.commit()
323 326 if cleanup_group:
324 327 RepoGroupModel()._delete_filesystem_group(repo_group)
325 328 raise
326 329
327 330 def update_permissions(
328 331 self, repo_group, perm_additions=None, perm_updates=None,
329 332 perm_deletions=None, recursive=None, check_perms=True,
330 333 cur_user=None):
331 334 from rhodecode.model.repo import RepoModel
332 335 from rhodecode.lib.auth import HasUserGroupPermissionAny
333 336
334 337 if not perm_additions:
335 338 perm_additions = []
336 339 if not perm_updates:
337 340 perm_updates = []
338 341 if not perm_deletions:
339 342 perm_deletions = []
340 343
341 344 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342 345
343 346 changes = {
344 347 'added': [],
345 348 'updated': [],
346 349 'deleted': []
347 350 }
348 351
349 352 def _set_perm_user(obj, user, perm):
350 353 if isinstance(obj, RepoGroup):
351 354 self.grant_user_permission(
352 355 repo_group=obj, user=user, perm=perm)
353 356 elif isinstance(obj, Repository):
354 357 # private repos will not allow to change the default
355 358 # permissions using recursive mode
356 359 if obj.private and user == User.DEFAULT_USER:
357 360 return
358 361
359 362 # we set group permission but we have to switch to repo
360 363 # permission
361 364 perm = perm.replace('group.', 'repository.')
362 365 RepoModel().grant_user_permission(
363 366 repo=obj, user=user, perm=perm)
364 367
365 368 def _set_perm_group(obj, users_group, perm):
366 369 if isinstance(obj, RepoGroup):
367 370 self.grant_user_group_permission(
368 371 repo_group=obj, group_name=users_group, perm=perm)
369 372 elif isinstance(obj, Repository):
370 373 # we set group permission but we have to switch to repo
371 374 # permission
372 375 perm = perm.replace('group.', 'repository.')
373 376 RepoModel().grant_user_group_permission(
374 377 repo=obj, group_name=users_group, perm=perm)
375 378
376 379 def _revoke_perm_user(obj, user):
377 380 if isinstance(obj, RepoGroup):
378 381 self.revoke_user_permission(repo_group=obj, user=user)
379 382 elif isinstance(obj, Repository):
380 383 RepoModel().revoke_user_permission(repo=obj, user=user)
381 384
382 385 def _revoke_perm_group(obj, user_group):
383 386 if isinstance(obj, RepoGroup):
384 387 self.revoke_user_group_permission(
385 388 repo_group=obj, group_name=user_group)
386 389 elif isinstance(obj, Repository):
387 390 RepoModel().revoke_user_group_permission(
388 391 repo=obj, group_name=user_group)
389 392
390 393 # start updates
391 394 log.debug('Now updating permissions for %s in recursive mode:%s',
392 395 repo_group, recursive)
393 396
394 397 # initialize check function, we'll call that multiple times
395 398 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396 399
397 400 for obj in repo_group.recursive_groups_and_repos():
398 401 # iterated obj is an instance of a repos group or repository in
399 402 # that group, recursive option can be: none, repos, groups, all
400 403 if recursive == 'all':
401 404 obj = obj
402 405 elif recursive == 'repos':
403 406 # skip groups, other than this one
404 407 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 408 continue
406 409 elif recursive == 'groups':
407 410 # skip repos
408 411 if isinstance(obj, Repository):
409 412 continue
410 413 else: # recursive == 'none':
411 414 # DEFAULT option - don't apply to iterated objects
412 415 # also we do a break at the end of this loop. if we are not
413 416 # in recursive mode
414 417 obj = repo_group
415 418
416 419 change_obj = obj.get_api_data()
417 420
418 421 # update permissions
419 422 for member_id, perm, member_type in perm_updates:
420 423 member_id = int(member_id)
421 424 if member_type == 'user':
422 425 member_name = User.get(member_id).username
423 426 # this updates also current one if found
424 427 _set_perm_user(obj, user=member_id, perm=perm)
425 428 else: # set for user group
426 429 member_name = UserGroup.get(member_id).users_group_name
427 430 if not check_perms or has_group_perm(member_name,
428 431 user=cur_user):
429 432 _set_perm_group(obj, users_group=member_id, perm=perm)
430 433
431 434 changes['updated'].append(
432 435 {'change_obj': change_obj, 'type': member_type,
433 436 'id': member_id, 'name': member_name, 'new_perm': perm})
434 437
435 438 # set new permissions
436 439 for member_id, perm, member_type in perm_additions:
437 440 member_id = int(member_id)
438 441 if member_type == 'user':
439 442 member_name = User.get(member_id).username
440 443 _set_perm_user(obj, user=member_id, perm=perm)
441 444 else: # set for user group
442 445 # check if we have permissions to alter this usergroup
443 446 member_name = UserGroup.get(member_id).users_group_name
444 447 if not check_perms or has_group_perm(member_name,
445 448 user=cur_user):
446 449 _set_perm_group(obj, users_group=member_id, perm=perm)
447 450
448 451 changes['added'].append(
449 452 {'change_obj': change_obj, 'type': member_type,
450 453 'id': member_id, 'name': member_name, 'new_perm': perm})
451 454
452 455 # delete permissions
453 456 for member_id, perm, member_type in perm_deletions:
454 457 member_id = int(member_id)
455 458 if member_type == 'user':
456 459 member_name = User.get(member_id).username
457 460 _revoke_perm_user(obj, user=member_id)
458 461 else: # set for user group
459 462 # check if we have permissions to alter this usergroup
460 463 member_name = UserGroup.get(member_id).users_group_name
461 464 if not check_perms or has_group_perm(member_name,
462 465 user=cur_user):
463 466 _revoke_perm_group(obj, user_group=member_id)
464 467
465 468 changes['deleted'].append(
466 469 {'change_obj': change_obj, 'type': member_type,
467 470 'id': member_id, 'name': member_name, 'new_perm': perm})
468 471
469 472 # if it's not recursive call for all,repos,groups
470 473 # break the loop and don't proceed with other changes
471 474 if recursive not in ['all', 'repos', 'groups']:
472 475 break
473 476
474 477 return changes
475 478
476 479 def update(self, repo_group, form_data):
477 480 try:
478 481 repo_group = self._get_repo_group(repo_group)
479 482 old_path = repo_group.full_path
480 483
481 484 # change properties
482 485 if 'group_description' in form_data:
483 486 repo_group.group_description = form_data['group_description']
484 487
485 488 if 'enable_locking' in form_data:
486 489 repo_group.enable_locking = form_data['enable_locking']
487 490
488 491 if 'group_parent_id' in form_data:
489 492 parent_group = (
490 493 self._get_repo_group(form_data['group_parent_id']))
491 494 repo_group.group_parent_id = (
492 495 parent_group.group_id if parent_group else None)
493 496 repo_group.parent_group = parent_group
494 497
495 498 # mikhail: to update the full_path, we have to explicitly
496 499 # update group_name
497 500 group_name = form_data.get('group_name', repo_group.name)
498 501 repo_group.group_name = repo_group.get_new_name(group_name)
499 502
500 503 new_path = repo_group.full_path
501 504
502 505 if 'user' in form_data:
503 506 repo_group.user = User.get_by_username(form_data['user'])
504 507 repo_group.updated_on = datetime.datetime.now()
505 508 self.sa.add(repo_group)
506 509
507 510 # iterate over all members of this groups and do fixes
508 511 # set locking if given
509 512 # if obj is a repoGroup also fix the name of the group according
510 513 # to the parent
511 514 # if obj is a Repo fix it's name
512 515 # this can be potentially heavy operation
513 516 for obj in repo_group.recursive_groups_and_repos():
514 517 # set the value from it's parent
515 518 obj.enable_locking = repo_group.enable_locking
516 519 if isinstance(obj, RepoGroup):
517 520 new_name = obj.get_new_name(obj.name)
518 521 log.debug('Fixing group %s to new name %s',
519 522 obj.group_name, new_name)
520 523 obj.group_name = new_name
521 524 obj.updated_on = datetime.datetime.now()
522 525 elif isinstance(obj, Repository):
523 526 # we need to get all repositories from this new group and
524 527 # rename them accordingly to new group path
525 528 new_name = obj.get_new_name(obj.just_name)
526 529 log.debug('Fixing repo %s to new name %s',
527 530 obj.repo_name, new_name)
528 531 obj.repo_name = new_name
529 532 obj.updated_on = datetime.datetime.now()
530 533 self.sa.add(obj)
531 534
532 535 self._rename_group(old_path, new_path)
533 536
534 537 # Trigger update event.
535 538 events.trigger(events.RepoGroupUpdateEvent(repo_group))
536 539
537 540 return repo_group
538 541 except Exception:
539 542 log.error(traceback.format_exc())
540 543 raise
541 544
542 545 def delete(self, repo_group, force_delete=False, fs_remove=True):
543 546 repo_group = self._get_repo_group(repo_group)
544 547 if not repo_group:
545 548 return False
546 549 try:
547 550 self.sa.delete(repo_group)
548 551 if fs_remove:
549 552 self._delete_filesystem_group(repo_group, force_delete)
550 553 else:
551 554 log.debug('skipping removal from filesystem')
552 555
553 556 # Trigger delete event.
554 557 events.trigger(events.RepoGroupDeleteEvent(repo_group))
555 558 return True
556 559
557 560 except Exception:
558 561 log.error('Error removing repo_group %s', repo_group)
559 562 raise
560 563
561 564 def grant_user_permission(self, repo_group, user, perm):
562 565 """
563 566 Grant permission for user on given repository group, or update
564 567 existing one if found
565 568
566 569 :param repo_group: Instance of RepoGroup, repositories_group_id,
567 570 or repositories_group name
568 571 :param user: Instance of User, user_id or username
569 572 :param perm: Instance of Permission, or permission_name
570 573 """
571 574
572 575 repo_group = self._get_repo_group(repo_group)
573 576 user = self._get_user(user)
574 577 permission = self._get_perm(perm)
575 578
576 579 # check if we have that permission already
577 580 obj = self.sa.query(UserRepoGroupToPerm)\
578 581 .filter(UserRepoGroupToPerm.user == user)\
579 582 .filter(UserRepoGroupToPerm.group == repo_group)\
580 583 .scalar()
581 584 if obj is None:
582 585 # create new !
583 586 obj = UserRepoGroupToPerm()
584 587 obj.group = repo_group
585 588 obj.user = user
586 589 obj.permission = permission
587 590 self.sa.add(obj)
588 591 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
589 592 action_logger_generic(
590 593 'granted permission: {} to user: {} on repogroup: {}'.format(
591 594 perm, user, repo_group), namespace='security.repogroup')
592 595 return obj
593 596
594 597 def revoke_user_permission(self, repo_group, user):
595 598 """
596 599 Revoke permission for user on given repository group
597 600
598 601 :param repo_group: Instance of RepoGroup, repositories_group_id,
599 602 or repositories_group name
600 603 :param user: Instance of User, user_id or username
601 604 """
602 605
603 606 repo_group = self._get_repo_group(repo_group)
604 607 user = self._get_user(user)
605 608
606 609 obj = self.sa.query(UserRepoGroupToPerm)\
607 610 .filter(UserRepoGroupToPerm.user == user)\
608 611 .filter(UserRepoGroupToPerm.group == repo_group)\
609 612 .scalar()
610 613 if obj:
611 614 self.sa.delete(obj)
612 615 log.debug('Revoked perm on %s on %s', repo_group, user)
613 616 action_logger_generic(
614 617 'revoked permission from user: {} on repogroup: {}'.format(
615 618 user, repo_group), namespace='security.repogroup')
616 619
617 620 def grant_user_group_permission(self, repo_group, group_name, perm):
618 621 """
619 622 Grant permission for user group on given repository group, or update
620 623 existing one if found
621 624
622 625 :param repo_group: Instance of RepoGroup, repositories_group_id,
623 626 or repositories_group name
624 627 :param group_name: Instance of UserGroup, users_group_id,
625 628 or user group name
626 629 :param perm: Instance of Permission, or permission_name
627 630 """
628 631 repo_group = self._get_repo_group(repo_group)
629 632 group_name = self._get_user_group(group_name)
630 633 permission = self._get_perm(perm)
631 634
632 635 # check if we have that permission already
633 636 obj = self.sa.query(UserGroupRepoGroupToPerm)\
634 637 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
635 638 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
636 639 .scalar()
637 640
638 641 if obj is None:
639 642 # create new
640 643 obj = UserGroupRepoGroupToPerm()
641 644
642 645 obj.group = repo_group
643 646 obj.users_group = group_name
644 647 obj.permission = permission
645 648 self.sa.add(obj)
646 649 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
647 650 action_logger_generic(
648 651 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
649 652 perm, group_name, repo_group), namespace='security.repogroup')
650 653 return obj
651 654
652 655 def revoke_user_group_permission(self, repo_group, group_name):
653 656 """
654 657 Revoke permission for user group on given repository group
655 658
656 659 :param repo_group: Instance of RepoGroup, repositories_group_id,
657 660 or repositories_group name
658 661 :param group_name: Instance of UserGroup, users_group_id,
659 662 or user group name
660 663 """
661 664 repo_group = self._get_repo_group(repo_group)
662 665 group_name = self._get_user_group(group_name)
663 666
664 667 obj = self.sa.query(UserGroupRepoGroupToPerm)\
665 668 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
666 669 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
667 670 .scalar()
668 671 if obj:
669 672 self.sa.delete(obj)
670 673 log.debug('Revoked perm to %s on %s', repo_group, group_name)
671 674 action_logger_generic(
672 675 'revoked permission from usergroup: {} on repogroup: {}'.format(
673 676 group_name, repo_group), namespace='security.repogroup')
674 677
675 678 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
676 679 super_user_actions=False):
677 680
678 681 from pyramid.threadlocal import get_current_request
679 682 _render = get_current_request().get_partial_renderer(
680 683 'data_table/_dt_elements.mako')
681 684 c = _render.get_call_context()
682 685 h = _render.get_helpers()
683 686
684 687 def quick_menu(repo_group_name):
685 688 return _render('quick_repo_group_menu', repo_group_name)
686 689
687 690 def repo_group_lnk(repo_group_name):
688 691 return _render('repo_group_name', repo_group_name)
689 692
690 693 def last_change(last_change):
691 694 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
692 695 last_change = last_change + datetime.timedelta(seconds=
693 696 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
694 697 return _render("last_change", last_change)
695 698
696 699 def desc(desc, personal):
697 700 return _render(
698 701 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
699 702
700 703 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
701 704 return _render(
702 705 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
703 706
704 707 def repo_group_name(repo_group_name, children_groups):
705 708 return _render("repo_group_name", repo_group_name, children_groups)
706 709
707 710 def user_profile(username):
708 711 return _render('user_profile', username)
709 712
710 713 repo_group_data = []
711 714 for group in repo_group_list:
712 715
713 716 row = {
714 717 "menu": quick_menu(group.group_name),
715 718 "name": repo_group_lnk(group.group_name),
716 719 "name_raw": group.group_name,
717 720 "last_change": last_change(group.last_db_change),
718 721 "last_change_raw": datetime_to_time(group.last_db_change),
719 722 "desc": desc(group.description_safe, group.personal),
720 723 "top_level_repos": 0,
721 724 "owner": user_profile(group.user.username)
722 725 }
723 726 if admin:
724 727 repo_count = group.repositories.count()
725 728 children_groups = map(
726 729 h.safe_unicode,
727 730 itertools.chain((g.name for g in group.parents),
728 731 (x.name for x in [group])))
729 732 row.update({
730 733 "action": repo_group_actions(
731 734 group.group_id, group.group_name, repo_count),
732 735 "top_level_repos": repo_count,
733 736 "name": repo_group_name(group.group_name, children_groups),
734 737
735 738 })
736 739 repo_group_data.append(row)
737 740
738 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import colander
23 import deform.widget
23 24
24 25 from rhodecode.translation import _
25 26 from rhodecode.model.validation_schema import validators, preparers, types
26 27
27 28
28 29 def get_group_and_repo(repo_name):
29 30 from rhodecode.model.repo_group import RepoGroupModel
30 31 return RepoGroupModel()._get_group_name_and_parent(
31 32 repo_name, get_object=True)
32 33
33 34
34 35 @colander.deferred
35 36 def deferred_can_write_to_group_validator(node, kw):
36 37 old_values = kw.get('old_values') or {}
37 38 request_user = kw.get('user')
38 39
39 40 def can_write_group_validator(node, value):
40 41 from rhodecode.lib.auth import (
41 42 HasPermissionAny, HasRepoGroupPermissionAny)
42 43 from rhodecode.model.repo_group import RepoGroupModel
43 44
44 45 messages = {
45 46 'invalid_parent_repo_group':
46 47 _(u"Parent repository group `{}` does not exist"),
47 48 # permissions denied we expose as not existing, to prevent
48 49 # resource discovery
49 50 'permission_denied_parent_group':
50 51 _(u"Parent repository group `{}` does not exist"),
51 52 'permission_denied_root':
52 53 _(u"You do not have the permission to store "
53 54 u"repository groups in the root location.")
54 55 }
55 56
56 57 value = value['repo_group_name']
57 58 parent_group_name = value
58 59
59 60 is_root_location = value is types.RootLocation
60 61
61 62 # NOT initialized validators, we must call them
62 63 can_create_repo_groups_at_root = HasPermissionAny(
63 64 'hg.admin', 'hg.repogroup.create.true')
64 65
65 66 if is_root_location:
66 67 if can_create_repo_groups_at_root(user=request_user):
67 68 # we can create repo group inside tool-level. No more checks
68 69 # are required
69 70 return
70 71 else:
71 72 raise colander.Invalid(node, messages['permission_denied_root'])
72 73
73 74 # check if the parent repo group actually exists
74 75 parent_group = None
75 76 if parent_group_name:
76 77 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
77 78 if value and not parent_group:
78 79 raise colander.Invalid(
79 80 node, messages['invalid_parent_repo_group'].format(
80 81 parent_group_name))
81 82
82 83 # check if we have permissions to create new groups under
83 84 # parent repo group
84 85 # create repositories with write permission on group is set to true
85 86 create_on_write = HasPermissionAny(
86 87 'hg.create.write_on_repogroup.true')(user=request_user)
87 88
88 89 group_admin = HasRepoGroupPermissionAny('group.admin')(
89 90 parent_group_name, 'can write into group validator', user=request_user)
90 91 group_write = HasRepoGroupPermissionAny('group.write')(
91 92 parent_group_name, 'can write into group validator', user=request_user)
92 93
93 94 # creation by write access is currently disabled. Needs thinking if
94 95 # we want to allow this...
95 96 forbidden = not (group_admin or (group_write and create_on_write and 0))
96 97
97 98 if parent_group and forbidden:
98 99 msg = messages['permission_denied_parent_group'].format(
99 100 parent_group_name)
100 101 raise colander.Invalid(node, msg)
101 102
102 103 return can_write_group_validator
103 104
104 105
105 106 @colander.deferred
106 107 def deferred_repo_group_owner_validator(node, kw):
107 108
108 109 def repo_owner_validator(node, value):
109 110 from rhodecode.model.db import User
110 111 existing = User.get_by_username(value)
111 112 if not existing:
112 113 msg = _(u'Repo group owner with id `{}` does not exists').format(
113 114 value)
114 115 raise colander.Invalid(node, msg)
115 116
116 117 return repo_owner_validator
117 118
118 119
119 120 @colander.deferred
120 121 def deferred_unique_name_validator(node, kw):
121 122 request_user = kw.get('user')
122 123 old_values = kw.get('old_values') or {}
123 124
124 125 def unique_name_validator(node, value):
125 126 from rhodecode.model.db import Repository, RepoGroup
126 127 name_changed = value != old_values.get('group_name')
127 128
128 129 existing = Repository.get_by_repo_name(value)
129 130 if name_changed and existing:
130 131 msg = _(u'Repository with name `{}` already exists').format(value)
131 132 raise colander.Invalid(node, msg)
132 133
133 134 existing_group = RepoGroup.get_by_group_name(value)
134 135 if name_changed and existing_group:
135 136 msg = _(u'Repository group with name `{}` already exists').format(
136 137 value)
137 138 raise colander.Invalid(node, msg)
138 139 return unique_name_validator
139 140
140 141
141 142 @colander.deferred
142 143 def deferred_repo_group_name_validator(node, kw):
143 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 160 class GroupType(colander.Mapping):
147 161 def _validate(self, node, value):
148 162 try:
149 163 return dict(repo_group_name=value)
150 164 except Exception as e:
151 165 raise colander.Invalid(
152 166 node, '"${val}" is not a mapping type: ${err}'.format(
153 167 val=value, err=e))
154 168
155 169 def deserialize(self, node, cstruct):
156 170 if cstruct is colander.null:
157 171 return cstruct
158 172
159 173 appstruct = super(GroupType, self).deserialize(node, cstruct)
160 174 validated_name = appstruct['repo_group_name']
161 175
162 176 # inject group based on once deserialized data
163 177 (repo_group_name_without_group,
164 178 parent_group_name,
165 179 parent_group) = get_group_and_repo(validated_name)
166 180
167 181 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
168 182 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
169 183 if parent_group:
170 184 appstruct['repo_group_id'] = parent_group.group_id
171 185
172 186 return appstruct
173 187
174 188
175 189 class GroupSchema(colander.SchemaNode):
176 190 schema_type = GroupType
177 191 validator = deferred_can_write_to_group_validator
178 192 missing = colander.null
179 193
180 194
181 195 class RepoGroup(GroupSchema):
182 196 repo_group_name = colander.SchemaNode(
183 197 types.GroupNameType())
184 198 repo_group_id = colander.SchemaNode(
185 199 colander.String(), missing=None)
186 200 repo_group_name_without_group = colander.SchemaNode(
187 201 colander.String(), missing=None)
188 202
189 203
190 204 class RepoGroupAccessSchema(colander.MappingSchema):
191 205 repo_group = RepoGroup()
192 206
193 207
194 208 class RepoGroupNameUniqueSchema(colander.MappingSchema):
195 209 unique_repo_group_name = colander.SchemaNode(
196 210 colander.String(),
197 211 validator=deferred_unique_name_validator)
198 212
199 213
200 214 class RepoGroupSchema(colander.Schema):
201 215
202 216 repo_group_name = colander.SchemaNode(
203 217 types.GroupNameType(),
204 218 validator=deferred_repo_group_name_validator)
205 219
206 220 repo_group_owner = colander.SchemaNode(
207 221 colander.String(),
208 222 validator=deferred_repo_group_owner_validator)
209 223
210 224 repo_group_description = colander.SchemaNode(
211 colander.String(), missing='')
225 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
212 226
213 227 repo_group_copy_permissions = colander.SchemaNode(
214 228 types.StringBooleanType(),
215 missing=False)
229 missing=False, widget=deform.widget.CheckboxWidget())
216 230
217 231 repo_group_enable_locking = colander.SchemaNode(
218 232 types.StringBooleanType(),
219 missing=False)
233 missing=False, widget=deform.widget.CheckboxWidget())
220 234
221 235 def deserialize(self, cstruct):
222 236 """
223 237 Custom deserialize that allows to chain validation, and verify
224 238 permissions, and as last step uniqueness
225 239 """
226 240
227 241 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
228 242 validated_name = appstruct['repo_group_name']
229 243
230 244 # second pass to validate permissions to repo_group
231 245 second = RepoGroupAccessSchema().bind(**self.bindings)
232 246 appstruct_second = second.deserialize({'repo_group': validated_name})
233 247 # save result
234 248 appstruct['repo_group'] = appstruct_second['repo_group']
235 249
236 250 # thirds to validate uniqueness
237 251 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
238 252 third.deserialize({'unique_repo_group_name': validated_name})
239 253
240 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 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 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']);
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']);
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']);
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']);
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']);
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 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 52 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
53 53 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
54 54 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
55 55 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
56 56 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
57 57 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
58 58 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
59 59 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
60 60 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
61 61 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
62 62 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
63 63 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
64 64 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
65 65 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
66 66 pyroutes.register('users', '/_admin/users', []);
67 67 pyroutes.register('users_data', '/_admin/users_data', []);
68 68 pyroutes.register('users_create', '/_admin/users/create', []);
69 69 pyroutes.register('users_new', '/_admin/users/new', []);
70 70 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
71 71 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
72 72 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
73 73 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
74 74 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
75 75 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
76 76 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
77 77 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
78 78 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 79 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 80 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
81 81 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
82 82 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
83 83 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
84 84 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
85 85 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
86 86 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
87 87 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
88 88 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
89 89 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
90 90 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
91 91 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
92 92 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
93 93 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
94 94 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
95 95 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
96 96 pyroutes.register('user_groups', '/_admin/user_groups', []);
97 97 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
98 98 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
99 99 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
100 100 pyroutes.register('repos', '/_admin/repos', []);
101 101 pyroutes.register('repo_new', '/_admin/repos/new', []);
102 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 106 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
104 107 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
105 108 pyroutes.register('channelstream_proxy', '/_channelstream', []);
106 109 pyroutes.register('login', '/_admin/login', []);
107 110 pyroutes.register('logout', '/_admin/logout', []);
108 111 pyroutes.register('register', '/_admin/register', []);
109 112 pyroutes.register('reset_password', '/_admin/password_reset', []);
110 113 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
111 114 pyroutes.register('home', '/', []);
112 115 pyroutes.register('user_autocomplete_data', '/_users', []);
113 116 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
114 117 pyroutes.register('repo_list_data', '/_repos', []);
115 118 pyroutes.register('goto_switcher_data', '/_goto_data', []);
116 119 pyroutes.register('journal', '/_admin/journal', []);
117 120 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
118 121 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
119 122 pyroutes.register('journal_public', '/_admin/public_journal', []);
120 123 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
121 124 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
122 125 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
123 126 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
124 127 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
125 128 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
126 129 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
127 130 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
128 131 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
129 132 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
130 133 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
131 134 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
132 135 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
133 136 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
134 137 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
135 138 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
136 139 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
137 140 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
138 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 142 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
140 143 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
141 144 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
142 145 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
143 146 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 147 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
145 148 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
146 149 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 150 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 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 152 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 153 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
151 154 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
152 155 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
153 156 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
154 157 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 158 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
156 159 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
157 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 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 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 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 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 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 166 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
164 167 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
165 168 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
166 169 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
167 170 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
168 171 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
169 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 173 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
171 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 175 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
173 176 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
174 177 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
175 178 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
176 179 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
177 180 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
178 181 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
179 182 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
180 183 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
181 184 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
182 185 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
183 186 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
184 187 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
185 188 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
186 189 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
187 190 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
188 191 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
189 192 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
190 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 194 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
192 195 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
193 196 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
194 197 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
195 198 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
196 199 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
197 200 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
198 201 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
199 202 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
200 203 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
201 204 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
202 205 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
203 206 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
204 207 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
205 208 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
206 209 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
207 210 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
208 211 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
209 212 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
210 213 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
211 214 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
212 215 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
213 216 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
214 217 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
215 218 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
216 219 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
217 220 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
218 221 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
219 222 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
220 223 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
221 224 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
222 225 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
223 226 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
224 227 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
225 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 234 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
227 235 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
228 236 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
229 237 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
230 238 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
231 239 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
232 240 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
233 241 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
234 242 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
235 243 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
236 244 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
237 245 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
238 246 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
239 247 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
240 248 pyroutes.register('search', '/_admin/search', []);
241 249 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
242 250 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
243 251 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
244 252 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
245 253 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
246 254 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
247 255 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
248 256 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
249 257 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
250 258 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
251 259 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
252 260 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
253 261 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
254 262 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
255 263 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
256 264 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
257 265 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
258 266 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
259 267 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
260 268 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
261 269 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
262 270 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
263 271 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
264 272 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
265 273 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
266 274 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
267 275 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
268 276 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
269 277 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
270 278 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
271 279 pyroutes.register('gists_show', '/_admin/gists', []);
272 280 pyroutes.register('gists_new', '/_admin/gists/new', []);
273 281 pyroutes.register('gists_create', '/_admin/gists/create', []);
274 282 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
275 283 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
276 284 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
277 285 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
278 286 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
279 287 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
280 288 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
281 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 290 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
283 291 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
284 292 pyroutes.register('apiv2', '/_admin/api', []);
285 293 }
@@ -1,69 +1,69 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 &raquo;
8 8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 9 &raquo;
10 10 ${h.link_to(c.current_IntegrationType.display_name,
11 11 request.route_url(route_name='repo_integrations_list',
12 12 repo_name=c.repo.repo_name,
13 13 integration=c.current_IntegrationType.key))}
14 14 %elif c.repo_group:
15 15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
16 16 &raquo;
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
17 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
18 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 20 &raquo;
21 21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
22 22 &raquo;
23 23 ${h.link_to(c.current_IntegrationType.display_name,
24 24 request.route_url(route_name='repo_group_integrations_list',
25 25 repo_group_name=c.repo_group.group_name,
26 26 integration=c.current_IntegrationType.key))}
27 27 %else:
28 28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 29 &raquo;
30 30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 31 &raquo;
32 32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
33 33 &raquo;
34 34 ${h.link_to(c.current_IntegrationType.display_name,
35 35 request.route_url(route_name='global_integrations_list',
36 36 integration=c.current_IntegrationType.key))}
37 37 %endif
38 38
39 39 %if c.integration:
40 40 &raquo;
41 41 ${c.integration.name}
42 42 %elif c.current_IntegrationType:
43 43 &raquo;
44 44 ${c.current_IntegrationType.display_name}
45 45 %endif
46 46 </%def>
47 47
48 48 <style>
49 49 .control-inputs.item-options, .control-inputs.item-settings {
50 50 float: left;
51 51 width: 100%;
52 52 }
53 53 </style>
54 54 <div class="panel panel-default">
55 55 <div class="panel-heading">
56 56 <h2 class="panel-title">
57 57 %if c.integration:
58 58 ${c.current_IntegrationType.display_name} - ${c.integration.name}
59 59 %else:
60 60 ${_('Create New %(integration_type)s Integration') % {
61 61 'integration_type': c.current_IntegrationType.display_name
62 62 }}
63 63 %endif
64 64 </h2>
65 65 </div>
66 66 <div class="panel-body">
67 67 ${c.form.render() | n}
68 68 </div>
69 69 </div>
@@ -1,256 +1,256 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 %elif c.repo_group:
8 8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
11 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 13 %else:
14 14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 15 &raquo;
16 16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 17 %endif
18 18 %if c.current_IntegrationType:
19 19 &raquo;
20 20 %if c.repo:
21 21 ${h.link_to(_('Integrations'),
22 22 request.route_path(route_name='repo_integrations_home',
23 23 repo_name=c.repo.repo_name))}
24 24 %elif c.repo_group:
25 25 ${h.link_to(_('Integrations'),
26 26 request.route_path(route_name='repo_group_integrations_home',
27 27 repo_group_name=c.repo_group.group_name))}
28 28 %else:
29 29 ${h.link_to(_('Integrations'),
30 30 request.route_path(route_name='global_integrations_home'))}
31 31 %endif
32 32 &raquo;
33 33 ${c.current_IntegrationType.display_name}
34 34 %else:
35 35 &raquo;
36 36 ${_('Integrations')}
37 37 %endif
38 38 </%def>
39 39
40 40 <div class="panel panel-default">
41 41 <div class="panel-heading">
42 42 <h3 class="panel-title">
43 43 %if c.repo:
44 44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 45 %elif c.repo_group:
46 46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 47 %else:
48 48 ${_('Current Integrations')}
49 49 %endif
50 50 </h3>
51 51 </div>
52 52 <div class="panel-body">
53 53 <%
54 54 if c.repo:
55 55 home_url = request.route_path('repo_integrations_home',
56 56 repo_name=c.repo.repo_name)
57 57 elif c.repo_group:
58 58 home_url = request.route_path('repo_group_integrations_home',
59 59 repo_group_name=c.repo_group.group_name)
60 60 else:
61 61 home_url = request.route_path('global_integrations_home')
62 62 %>
63 63
64 64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65 65
66 66 %for integration_key, IntegrationType in c.available_integrations.items():
67 67 % if not IntegrationType.is_dummy:
68 68 <%
69 69 if c.repo:
70 70 list_url = request.route_path('repo_integrations_list',
71 71 repo_name=c.repo.repo_name,
72 72 integration=integration_key)
73 73 elif c.repo_group:
74 74 list_url = request.route_path('repo_group_integrations_list',
75 75 repo_group_name=c.repo_group.group_name,
76 76 integration=integration_key)
77 77 else:
78 78 list_url = request.route_path('global_integrations_list',
79 79 integration=integration_key)
80 80 %>
81 81 <a href="${list_url}"
82 82 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
83 83 ${IntegrationType.display_name}
84 84 </a>
85 85 % endif
86 86 %endfor
87 87
88 88 <%
89 89 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
90 90
91 91 if c.repo:
92 92 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
93 93 elif c.repo_group:
94 94 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
95 95 else:
96 96 create_url = h.route_path('global_integrations_new')
97 97 %>
98 98 <p class="pull-right">
99 99 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
100 100 </p>
101 101
102 102 <table class="rctable integrations">
103 103 <thead>
104 104 <tr>
105 105 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
106 106 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
107 107 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
108 108 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
109 109 <th>${_('Actions')}</th>
110 110 <th></th>
111 111 </tr>
112 112 </thead>
113 113 <tbody>
114 114 %if not c.integrations_list:
115 115 <tr>
116 116 <td colspan="7">
117 117
118 118 %if c.repo:
119 119 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
120 120 %elif c.repo_group:
121 121 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
122 122 %else:
123 123 ${_('No {type} integrations exist yet.').format(type=integration_type)}
124 124 %endif
125 125
126 126 %if c.current_IntegrationType:
127 127 <%
128 128 if c.repo:
129 129 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
130 130 elif c.repo_group:
131 131 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
132 132 else:
133 133 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
134 134 %>
135 135 %endif
136 136
137 137 <a href="${create_url}">${_(u'Create one')}</a>
138 138 </td>
139 139 </tr>
140 140 %endif
141 141 %for IntegrationType, integration in c.integrations_list:
142 142 <tr id="integration_${integration.integration_id}">
143 143 <td class="td-enabled">
144 144 %if integration.enabled:
145 145 <div class="flag_status approved pull-left"></div>
146 146 %else:
147 147 <div class="flag_status rejected pull-left"></div>
148 148 %endif
149 149 </td>
150 150 <td class="td-description">
151 151 ${integration.name}
152 152 </td>
153 153 <td class="td-icon">
154 154 %if integration.integration_type in c.available_integrations:
155 155 <div class="integration-icon">
156 156 ${c.available_integrations[integration.integration_type].icon|n}
157 157 </div>
158 158 %else:
159 159 ?
160 160 %endif
161 161 </td>
162 162 <td class="td-type">
163 163 ${integration.integration_type}
164 164 </td>
165 165 <td class="td-scope">
166 166 %if integration.repo:
167 167 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
168 168 ${_('repo')}:${integration.repo.repo_name}
169 169 </a>
170 170 %elif integration.repo_group:
171 171 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
172 172 ${_('repogroup')}:${integration.repo_group.group_name}
173 173 %if integration.child_repos_only:
174 174 ${_('child repos only')}
175 175 %else:
176 176 ${_('cascade to all')}
177 177 %endif
178 178 </a>
179 179 %else:
180 180 %if integration.child_repos_only:
181 181 ${_('top level repos only')}
182 182 %else:
183 183 ${_('global')}
184 184 %endif
185 185 </td>
186 186 %endif
187 187 <td class="td-action">
188 188 %if not IntegrationType:
189 189 ${_('unknown integration')}
190 190 %else:
191 191 <%
192 192 if c.repo:
193 193 edit_url = request.route_path('repo_integrations_edit',
194 194 repo_name=c.repo.repo_name,
195 195 integration=integration.integration_type,
196 196 integration_id=integration.integration_id)
197 197 elif c.repo_group:
198 198 edit_url = request.route_path('repo_group_integrations_edit',
199 199 repo_group_name=c.repo_group.group_name,
200 200 integration=integration.integration_type,
201 201 integration_id=integration.integration_id)
202 202 else:
203 203 edit_url = request.route_path('global_integrations_edit',
204 204 integration=integration.integration_type,
205 205 integration_id=integration.integration_id)
206 206 %>
207 207 <div class="grid_edit">
208 208 <a href="${edit_url}">${_('Edit')}</a>
209 209 </div>
210 210 <div class="grid_delete">
211 211 <a href="${edit_url}"
212 212 class="btn btn-link btn-danger delete_integration_entry"
213 213 data-desc="${integration.name}"
214 214 data-uid="${integration.integration_id}">
215 215 ${_('Delete')}
216 216 </a>
217 217 </div>
218 218 %endif
219 219 </td>
220 220 </tr>
221 221 %endfor
222 222 <tr id="last-row"></tr>
223 223 </tbody>
224 224 </table>
225 225 <div class="integrations-paginator">
226 226 <div class="pagination-wh pagination-left">
227 227 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
228 228 </div>
229 229 </div>
230 230 </div>
231 231 </div>
232 232 <script type="text/javascript">
233 233 var delete_integration = function(entry) {
234 234 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
235 235 var request = $.ajax({
236 236 type: "POST",
237 237 url: $(entry).attr('href'),
238 238 data: {
239 239 'delete': 'delete',
240 240 'csrf_token': CSRF_TOKEN
241 241 },
242 242 success: function(){
243 243 location.reload();
244 244 },
245 245 error: function(data, textStatus, errorThrown){
246 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 252 $('.delete_integration_entry').on('click', function(e){
253 253 e.preventDefault();
254 254 delete_integration(this);
255 255 });
256 256 </script> No newline at end of file
@@ -1,68 +1,68 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="widgets" file="/widgets.mako"/>
4 4
5 5 <%def name="breadcrumbs_links()">
6 6 %if c.repo:
7 7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 8 &raquo;
9 9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 10 %elif c.repo_group:
11 11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
13 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
14 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 16 &raquo;
17 17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 18 %else:
19 19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 20 &raquo;
21 21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 22 &raquo;
23 23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 24 %endif
25 25 &raquo;
26 26 ${_('Create new integration')}
27 27 </%def>
28 28 <%widgets:panel class_='integrations'>
29 29 <%def name="title()">
30 30 %if c.repo:
31 31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 32 %elif c.repo_group:
33 33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 34 %else:
35 35 ${_('Create New Global Integration')}
36 36 %endif
37 37 </%def>
38 38
39 39 %for integration, IntegrationObject in c.available_integrations.items():
40 40 <%
41 41 if c.repo:
42 42 create_url = request.route_path('repo_integrations_create',
43 43 repo_name=c.repo.repo_name,
44 44 integration=integration)
45 45 elif c.repo_group:
46 46 create_url = request.route_path('repo_group_integrations_create',
47 47 repo_group_name=c.repo_group.group_name,
48 48 integration=integration)
49 49 else:
50 50 create_url = request.route_path('global_integrations_create',
51 51 integration=integration)
52 52 if IntegrationObject.is_dummy:
53 53 create_url = request.current_route_path()
54 54 %>
55 55 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
56 56 <%widgets:panel>
57 57 <h2>
58 58 <div class="integration-icon">
59 59 ${IntegrationObject.icon|n}
60 60 </div>
61 61 ${IntegrationObject.display_name}
62 62 </h2>
63 63 ${IntegrationObject.description or _('No description available')}
64 64 </%widgets:panel>
65 65 </a>
66 66 %endfor
67 67 <div style="clear:both"></div>
68 68 </%widgets:panel>
@@ -1,106 +1,106 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repository group')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository groups'),h.route_path('repo_groups'))}
15 15 &raquo;
16 16 ${_('Add Repository Group')}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <!-- box / title -->
26 26 <div class="title">
27 27 ${self.breadcrumbs()}
28 28 </div>
29 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 31 <div class="form">
32 32 <!-- fields -->
33 33 <div class="fields">
34 34 <div class="field">
35 35 <div class="label">
36 36 <label for="group_name">${_('Group Name')}:</label>
37 37 </div>
38 38 <div class="input">
39 39 ${h.text('group_name', class_="medium")}
40 40 </div>
41 41 </div>
42 42
43 43 <div class="field">
44 44 <div class="label">
45 45 <label for="group_description">${_('Description')}:</label>
46 46 </div>
47 47 <div class="textarea editor">
48 48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
49 49 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
50 50 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
51 51 <span id="meta-tags-desc" style="display: none">
52 52 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
53 53 ${dt.metatags_help()}
54 54 </span>
55 55 </div>
56 56 </div>
57 57
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="group_parent_id">${_('Group Parent')}:</label>
61 61 </div>
62 62 <div class="select">
63 63 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
64 64 </div>
65 65 </div>
66 66
67 67 <div id="copy_perms" class="field">
68 68 <div class="label label-checkbox">
69 69 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
70 70 </div>
71 71 <div class="checkboxes">
72 72 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
73 73 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
74 74 </div>
75 75 </div>
76 76
77 77 <div class="buttons">
78 78 ${h.submit('save',_('Save'),class_="btn")}
79 79 </div>
80 80 </div>
81 81 </div>
82 82 ${h.end_form()}
83 83 </div>
84 84 <script>
85 85 $(document).ready(function(){
86 86 var setCopyPermsOption = function(group_val){
87 87 if(group_val != "-1"){
88 88 $('#copy_perms').show()
89 89 }
90 90 else{
91 91 $('#copy_perms').hide();
92 92 }
93 93 }
94 94 $("#group_parent_id").select2({
95 95 'containerCssClass': "drop-menu",
96 96 'dropdownCssClass': "drop-menu-dropdown",
97 97 'dropdownAutoWidth': true
98 98 });
99 99 setCopyPermsOption($('#group_parent_id').val())
100 100 $("#group_parent_id").on("change", function(e) {
101 101 setCopyPermsOption(e.val)
102 102 })
103 103 $('#group_name').focus();
104 104 })
105 105 </script>
106 106 </%def>
@@ -1,61 +1,61 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s repository group settings') % c.repo_group.name}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
15 15 %if c.repo_group.parent_group:
16 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 17 %endif
18 18 &raquo; ${c.repo_group.name}
19 19 </%def>
20 20
21 21 <%def name="breadcrumbs_side_links()">
22 22 <ul class="links">
23 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 25 </li>
26 26 </ul>
27 27 </%def>
28 28
29 29 <%def name="menu_bar_nav()">
30 30 ${self.menu_items(active='admin')}
31 31 </%def>
32 32
33 33 <%def name="main_content()">
34 34 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
35 35 </%def>
36 36
37 37 <%def name="main()">
38 38 <div class="box">
39 39 <div class="title">
40 40 ${self.breadcrumbs()}
41 41 ${self.breadcrumbs_side_links()}
42 42 </div>
43 43
44 44 <div class="sidebar-col-wrapper">
45 45 ##main
46 46 <div class="sidebar">
47 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>
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>
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>
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=='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.route_path('edit_repo_group_advanced', repo_group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
51 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 52 </ul>
53 53 </div>
54 54
55 55 <div class="main-content-full-width">
56 56 ${self.main_content()}
57 57 </div>
58 58
59 59 </div>
60 60 </div>
61 61 </%def>
@@ -1,64 +1,64 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
7 7 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
8 8
9 9 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
10 10 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
11 11
12 12 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
13 13 ]
14 14 %>
15 15
16 16 <div class="panel panel-default">
17 17 <div class="panel-heading">
18 18 <h3 class="panel-title">${_('Repository Group: %s') % c.repo_group.group_name}</h3>
19 19 </div>
20 20 <div class="panel-body">
21 21 ${base.dt_info_panel(elems)}
22 22 </div>
23 23
24 24 </div>
25 25
26 26 <div class="panel panel-danger">
27 27 <div class="panel-heading">
28 28 <h3 class="panel-title">${_('Delete repository group')}</h3>
29 29 </div>
30 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 32 <table class="display">
33 33
34 34 <tr>
35 35 <td>
36 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 37 </td>
38 38 <td>
39 39 </td>
40 40 <td>
41 41 </td>
42 42 </tr>
43 43 <tr>
44 44 <td>
45 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 46 </td>
47 47 <td>
48 48 </td>
49 49 <td>
50 50 </td>
51 51 </tr>
52 52
53 53 </table>
54 54 <div style="margin: 0 0 20px 0" class="fake-space"></div>
55 55
56 56 <button class="btn btn-small btn-danger" type="submit"
57 57 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
58 58 ${_('Delete this repository group')}
59 59 </button>
60 60 ${h.end_form()}
61 61 </div>
62 62 </div>
63 63
64 64
@@ -1,148 +1,146 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 6 </div>
7 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 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 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 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 19 %for _user in c.repo_group.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 <span class="user">
29 28 ${h.link_to_user(_user.username)}
30 29 %if getattr(_user, 'admin_row', None):
31 30 (${_('super admin')})
32 31 %endif
33 32 %if getattr(_user, 'owner_row', None):
34 33 (${_('owner')})
35 34 %endif
36 </span>
37 35 </td>
38 36 <td></td>
39 37 </tr>
40 38 %else:
41 ##forbid revoking permission from yourself, except if you're an super admin
42 39 <tr>
40 ##forbid revoking permission from yourself, except if you're an super admin
43 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>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
42 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
43 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
48 46 <td class="td-user">
49 47 ${base.gravatar(_user.email, 16)}
50 48 <span class="user">
51 49 % if _user.username == h.DEFAULT_USER:
52 50 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 51 % else:
54 52 ${h.link_to_user(_user.username)}
55 53 % endif
56 54 </span>
57 55 </td>
58 56 <td class="td-action">
59 57 %if _user.username != h.DEFAULT_USER:
60 58 <span class="btn btn-link btn-danger revoke_perm"
61 59 member="${_user.user_id}" member_type="user">
62 60 <i class="icon-remove"></i> ${_('Revoke')}
63 61 </span>
64 62 %endif
65 63 </td>
66 64 %else:
67 65 ## special case for current user permissions, we make sure he cannot take his own permissions
68 66 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
69 67 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
70 68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
71 69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
72 70 <td class="td-user">
73 71 ${base.gravatar(_user.email, 16)}
74 72 <span class="user">
75 73 % if _user.username == h.DEFAULT_USER:
76 74 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 75 % else:
78 76 ${h.link_to_user(_user.username)}
79 77 % endif
80 78 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 79 </span>
82 80 </td>
83 81 <td></td>
84 82 %endif
85 83 </tr>
86 84 %endif
87 85 %endfor
88 86
89 87 ## USER GROUPS
90 88 %for _user_group in c.repo_group.permission_user_groups():
91 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>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</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>
91 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</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>
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 94 <td class="td-componentname">
97 95 <i class="icon-group" ></i>
98 96 %if h.HasPermissionAny('hg.admin')():
99 97 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
100 98 ${_user_group.users_group_name}
101 99 </a>
102 100 %else:
103 101 ${_user_group.users_group_name}
104 102 %endif
105 103 </td>
106 104 <td class="td-action">
107 105 <span class="btn btn-link btn-danger revoke_perm"
108 106 member="${_user_group.users_group_id}" member_type="user_group">
109 107 <i class="icon-remove"></i> ${_('Revoke')}
110 108 </span>
111 109 </td>
112 110 </tr>
113 111 %endfor
114 112
115 113 <tr class="new_members" id="add_perm_input"></tr>
116 114 </table>
117 115 <div id="add_perm" class="link">
118 116 ${_('Add new')}
119 117 </div>
120 118 <div class="fields">
121 119 <div class="field">
122 120 <div class="label label-radio">
123 121 ${_('Apply to children')}:
124 122 </div>
125 123 <div class="radios">
126 124 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
127 125 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
128 126 ${h.radio('recursive', 'repos', label=_('Repositories'))}
129 127 ${h.radio('recursive', 'all', label=_('Both'))}
130 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 129 </div>
132 130 </div>
133 131 </div>
134 132 <div class="buttons">
135 133 ${h.submit('save',_('Save'),class_="btn btn-primary")}
136 134 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
137 135 </div>
138 136 ${h.end_form()}
139 137 </div>
140 138 </div>
141 139 <script type="text/javascript">
142 140 $('#add_perm').on('click', function(e){
143 141 addNewPermInput($(this), 'group');
144 142 });
145 143 $('.revoke_perm').on('click', function(e){
146 144 markRevokePermInput($(this), 'group');
147 145 })
148 146 </script>
@@ -1,90 +1,95 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 <div class="panel panel-default">
5 5 <div class="panel-heading">
6 6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
7 7 </div>
8 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 10 <div class="form">
11 11 <!-- fields -->
12 12 <div class="fields">
13 13 <div class="field">
14 14 <div class="label">
15 15 <label for="group_name">${_('Group Name')}:</label>
16 16 </div>
17 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 32 </div>
20 33 </div>
21 34
22 35 <div class="field badged-field">
23 36 <div class="label">
24 <label for="user">${_('Owner')}:</label>
37 <label for="repo_group_owner">${_('Owner')}:</label>
25 38 </div>
26 39 <div class="input">
27 40 <div class="badge-input-container">
28 41 <div class="user-badge">
29 42 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
30 43 </div>
31 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 46 </div>
34 47 </div>
35 <form:error name="user"/>
48 ${c.form.render_error(request, c.form['repo_group_owner'])|n}
36 49 <p class="help-block">${_('Change owner of this repository group.')}</p>
37 50 </div>
38 51 </div>
39 52
40 53 <div class="field">
41 54 <div class="label label-textarea">
42 <label for="group_description">${_('Description')}:</label>
55 <label for="repo_group_description">${_('Description')}:</label>
43 56 </div>
44 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 61 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
47 62 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
48 63 <span id="meta-tags-desc" style="display: none">
49 64 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
50 65 ${dt.metatags_help()}
51 66 </span>
52 67 </div>
53 68 </div>
54 69
55 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 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 73 </div>
67 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 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 79 </div>
71 80 </div>
81
72 82 <div class="buttons">
73 83 ${h.submit('save',_('Save'),class_="btn")}
74 84 ${h.reset('reset',_('Reset'),class_="btn")}
75 85 </div>
76 86 </div>
77 87 </div>
78 88 ${h.end_form()}
79 89 </div>
80 90 </div>
81 91 <script>
82 92 $(document).ready(function(){
83 $("#group_parent_id").select2({
84 'containerCssClass': "drop-menu",
85 'dropdownCssClass': "drop-menu-dropdown",
86 'dropdownAutoWidth': true
87 });
88 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
93 UsersAutoComplete('repo_group_owner', '${c.rhodecode_user.user_id}');
89 94 })
90 95 </script>
@@ -1,97 +1,97 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 <ul class="links">
25 25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 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 28 </li>
29 29 %endif
30 30 </ul>
31 31 </div>
32 32 <div id="repos_list_wrap">
33 33 <table id="group_list_table" class="display"></table>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $(document).ready(function() {
39 39
40 40 var get_datatable_count = function(){
41 41 var api = $('#group_list_table').dataTable().api();
42 42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 43 };
44 44
45 45 // repo group list
46 46 $('#group_list_table').DataTable({
47 47 data: ${c.data|n},
48 48 dom: 'rtp',
49 49 pageLength: ${c.visual.admin_grid_items},
50 50 order: [[ 0, "asc" ]],
51 51 columns: [
52 52 { data: {"_": "name",
53 53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 55 { data: {"_": "desc",
56 56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 57 { data: {"_": "last_change",
58 58 "sort": "last_change_raw",
59 59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 60 { data: {"_": "top_level_repos",
61 61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
62 62 { data: {"_": "owner",
63 63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 64 { data: {"_": "action",
65 65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
66 66 ],
67 67 language: {
68 68 paginate: DEFAULT_GRID_PAGINATION,
69 69 emptyTable: _gettext("No repository groups available yet.")
70 70 },
71 71 "initComplete": function( settings, json ) {
72 72 get_datatable_count();
73 73 quick_repo_menu();
74 74 }
75 75 });
76 76
77 77 // update the counter when doing search
78 78 $('#group_list_table').on( 'search.dt', function (e,settings) {
79 79 get_datatable_count();
80 80 });
81 81
82 82 // filter, filter both grids
83 83 $('#q_filter').on( 'keyup', function () {
84 84
85 85 var repo_group_api = $('#group_list_table').dataTable().api();
86 86 repo_group_api
87 87 .columns(0)
88 88 .search(this.value)
89 89 .draw();
90 90 });
91 91
92 92 // refilter table if page load via back button
93 93 $("#q_filter").trigger('keyup');
94 94 });
95 95 </script>
96 96 </%def>
97 97
@@ -1,609 +1,609 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 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 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 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 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 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 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 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 232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
233 233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
234 234 <li class="${is_active('showpullrequest')}">
235 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 236 %if c.repository_pull_requests:
237 237 <span class="pr_notifications">${c.repository_pull_requests}</span>
238 238 %endif
239 239 <div class="menulabel">${_('Pull Requests')}</div>
240 240 </a>
241 241 </li>
242 242 %endif
243 243 <li class="${is_active('options')}">
244 244 <a class="menulink dropdown">
245 245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
246 246 </a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li>
253 253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 254 href="${h.route_path('repo_compare',
255 255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 260 _query=dict(merge=1))}"
261 261 >
262 262 ${_('Compare fork')}
263 263 </a>
264 264 </li>
265 265 %endif
266 266
267 267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
268 268
269 269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
270 270 %if c.rhodecode_db_repo.locked[0]:
271 271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
272 272 %else:
273 273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
274 274 %endif
275 275 %endif
276 276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 280 %endif
281 281 %endif
282 282 </ul>
283 283 </li>
284 284 </ul>
285 285 </div>
286 286 <div class="clear"></div>
287 287 </div>
288 288 <!--- END CONTEXT BAR -->
289 289
290 290 </%def>
291 291
292 292 <%def name="usermenu(active=False)">
293 293 ## USER MENU
294 294 <li id="quick_login_li" class="${'active' if active else ''}">
295 295 <a id="quick_login_link" class="menulink childs">
296 296 ${gravatar(c.rhodecode_user.email, 20)}
297 297 <span class="user">
298 298 %if c.rhodecode_user.username != h.DEFAULT_USER:
299 299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
300 300 %else:
301 301 <span>${_('Sign in')}</span>
302 302 %endif
303 303 </span>
304 304 </a>
305 305
306 306 <div class="user-menu submenu">
307 307 <div id="quick_login">
308 308 %if c.rhodecode_user.username == h.DEFAULT_USER:
309 309 <h4>${_('Sign in to your account')}</h4>
310 310 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
311 311 <div class="form form-vertical">
312 312 <div class="fields">
313 313 <div class="field">
314 314 <div class="label">
315 315 <label for="username">${_('Username')}:</label>
316 316 </div>
317 317 <div class="input">
318 318 ${h.text('username',class_='focus',tabindex=1)}
319 319 </div>
320 320
321 321 </div>
322 322 <div class="field">
323 323 <div class="label">
324 324 <label for="password">${_('Password')}:</label>
325 325 %if h.HasPermissionAny('hg.password_reset.enabled')():
326 326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
327 327 %endif
328 328 </div>
329 329 <div class="input">
330 330 ${h.password('password',class_='focus',tabindex=2)}
331 331 </div>
332 332 </div>
333 333 <div class="buttons">
334 334 <div class="register">
335 335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
336 336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
337 337 %endif
338 338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
339 339 </div>
340 340 <div class="submit">
341 341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
342 342 </div>
343 343 </div>
344 344 </div>
345 345 </div>
346 346 ${h.end_form()}
347 347 %else:
348 348 <div class="">
349 349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
350 350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
351 351 <div class="email">${c.rhodecode_user.email}</div>
352 352 </div>
353 353 <div class="">
354 354 <ol class="links">
355 355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
356 356 % if c.rhodecode_user.personal_repo_group:
357 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 358 % endif
359 359 <li class="logout">
360 360 ${h.secure_form(h.route_path('logout'), request=request)}
361 361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
362 362 ${h.end_form()}
363 363 </li>
364 364 </ol>
365 365 </div>
366 366 %endif
367 367 </div>
368 368 </div>
369 369 %if c.rhodecode_user.username != h.DEFAULT_USER:
370 370 <div class="pill_container">
371 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 372 </div>
373 373 % endif
374 374 </li>
375 375 </%def>
376 376
377 377 <%def name="menu_items(active=None)">
378 378 <%
379 379 def is_active(selected):
380 380 if selected == active:
381 381 return "active"
382 382 return ""
383 383 %>
384 384 <ul id="quick" class="main_nav navigation horizontal-list">
385 385 <!-- repo switcher -->
386 386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
387 387 <input id="repo_switcher" name="repo_switcher" type="hidden">
388 388 </li>
389 389
390 390 ## ROOT MENU
391 391 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 392 <li class="${is_active('journal')}">
393 393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
394 394 <div class="menulabel">${_('Journal')}</div>
395 395 </a>
396 396 </li>
397 397 %else:
398 398 <li class="${is_active('journal')}">
399 399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
400 400 <div class="menulabel">${_('Public journal')}</div>
401 401 </a>
402 402 </li>
403 403 %endif
404 404 <li class="${is_active('gists')}">
405 405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
406 406 <div class="menulabel">${_('Gists')}</div>
407 407 </a>
408 408 </li>
409 409 <li class="${is_active('search')}">
410 410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
411 411 <div class="menulabel">${_('Search')}</div>
412 412 </a>
413 413 </li>
414 414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
415 415 <li class="${is_active('admin')}">
416 416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
417 417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 418 </a>
419 419 ${admin_menu()}
420 420 </li>
421 421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
422 422 <li class="${is_active('admin')}">
423 423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
424 424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
425 425 </a>
426 426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
427 427 c.rhodecode_user.repository_groups_admin,
428 428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
429 429 </li>
430 430 % endif
431 431 % if c.debug_style:
432 432 <li class="${is_active('debug_style')}">
433 433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
434 434 <div class="menulabel">${_('Style')}</div>
435 435 </a>
436 436 </li>
437 437 % endif
438 438 ## render extra user menu
439 439 ${usermenu(active=(active=='my_account'))}
440 440 </ul>
441 441
442 442 <script type="text/javascript">
443 443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
444 444
445 445 /*format the look of items in the list*/
446 446 var format = function(state, escapeMarkup){
447 447 if (!state.id){
448 448 return state.text; // optgroup
449 449 }
450 450 var obj_dict = state.obj;
451 451 var tmpl = '';
452 452
453 453 if(obj_dict && state.type == 'repo'){
454 454 if(obj_dict['repo_type'] === 'hg'){
455 455 tmpl += '<i class="icon-hg"></i> ';
456 456 }
457 457 else if(obj_dict['repo_type'] === 'git'){
458 458 tmpl += '<i class="icon-git"></i> ';
459 459 }
460 460 else if(obj_dict['repo_type'] === 'svn'){
461 461 tmpl += '<i class="icon-svn"></i> ';
462 462 }
463 463 if(obj_dict['private']){
464 464 tmpl += '<i class="icon-lock" ></i> ';
465 465 }
466 466 else if(visual_show_public_icon){
467 467 tmpl += '<i class="icon-unlock-alt"></i> ';
468 468 }
469 469 }
470 470 if(obj_dict && state.type == 'commit') {
471 471 tmpl += '<i class="icon-tag"></i>';
472 472 }
473 473 if(obj_dict && state.type == 'group'){
474 474 tmpl += '<i class="icon-folder-close"></i> ';
475 475 }
476 476 tmpl += escapeMarkup(state.text);
477 477 return tmpl;
478 478 };
479 479
480 480 var formatResult = function(result, container, query, escapeMarkup) {
481 481 return format(result, escapeMarkup);
482 482 };
483 483
484 484 var formatSelection = function(data, container, escapeMarkup) {
485 485 return format(data, escapeMarkup);
486 486 };
487 487
488 488 $("#repo_switcher").select2({
489 489 cachedDataSource: {},
490 490 minimumInputLength: 2,
491 491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
492 492 dropdownAutoWidth: true,
493 493 formatResult: formatResult,
494 494 formatSelection: formatSelection,
495 495 containerCssClass: "repo-switcher",
496 496 dropdownCssClass: "repo-switcher-dropdown",
497 497 escapeMarkup: function(m){
498 498 // don't escape our custom placeholder
499 499 if(m.substr(0,23) == '<div class="menulabel">'){
500 500 return m;
501 501 }
502 502
503 503 return Select2.util.escapeMarkup(m);
504 504 },
505 505 query: $.debounce(250, function(query){
506 506 self = this;
507 507 var cacheKey = query.term;
508 508 var cachedData = self.cachedDataSource[cacheKey];
509 509
510 510 if (cachedData) {
511 511 query.callback({results: cachedData.results});
512 512 } else {
513 513 $.ajax({
514 514 url: pyroutes.url('goto_switcher_data'),
515 515 data: {'query': query.term},
516 516 dataType: 'json',
517 517 type: 'GET',
518 518 success: function(data) {
519 519 self.cachedDataSource[cacheKey] = data;
520 520 query.callback({results: data.results});
521 521 },
522 522 error: function(data, textStatus, errorThrown) {
523 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 530 $("#repo_switcher").on('select2-selecting', function(e){
531 531 e.preventDefault();
532 532 window.location = e.choice.url;
533 533 });
534 534
535 535 </script>
536 536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
537 537 </%def>
538 538
539 539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
540 540 <div class="modal-dialog">
541 541 <div class="modal-content">
542 542 <div class="modal-header">
543 543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
544 544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
545 545 </div>
546 546 <div class="modal-body">
547 547 <div class="block-left">
548 548 <table class="keyboard-mappings">
549 549 <tbody>
550 550 <tr>
551 551 <th></th>
552 552 <th>${_('Site-wide shortcuts')}</th>
553 553 </tr>
554 554 <%
555 555 elems = [
556 556 ('/', 'Open quick search box'),
557 557 ('g h', 'Goto home page'),
558 558 ('g g', 'Goto my private gists page'),
559 559 ('g G', 'Goto my public gists page'),
560 560 ('n r', 'New repository page'),
561 561 ('n g', 'New gist page'),
562 562 ]
563 563 %>
564 564 %for key, desc in elems:
565 565 <tr>
566 566 <td class="keys">
567 567 <span class="key tag">${key}</span>
568 568 </td>
569 569 <td>${desc}</td>
570 570 </tr>
571 571 %endfor
572 572 </tbody>
573 573 </table>
574 574 </div>
575 575 <div class="block-left">
576 576 <table class="keyboard-mappings">
577 577 <tbody>
578 578 <tr>
579 579 <th></th>
580 580 <th>${_('Repositories')}</th>
581 581 </tr>
582 582 <%
583 583 elems = [
584 584 ('g s', 'Goto summary page'),
585 585 ('g c', 'Goto changelog page'),
586 586 ('g f', 'Goto files page'),
587 587 ('g F', 'Goto files page with file search activated'),
588 588 ('g p', 'Goto pull requests page'),
589 589 ('g o', 'Goto repository settings'),
590 590 ('g O', 'Goto repository permissions settings'),
591 591 ]
592 592 %>
593 593 %for key, desc in elems:
594 594 <tr>
595 595 <td class="keys">
596 596 <span class="key tag">${key}</span>
597 597 </td>
598 598 <td>${desc}</td>
599 599 </tr>
600 600 %endfor
601 601 </tbody>
602 602 </table>
603 603 </div>
604 604 </div>
605 605 <div class="modal-footer">
606 606 </div>
607 607 </div><!-- /.modal-content -->
608 608 </div><!-- /.modal-dialog -->
609 609 </div><!-- /.modal -->
@@ -1,268 +1,268 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 7 <div id="perms" class="table fields">
8 8 %for section in sorted(permissions.keys()):
9 9 <div class="panel panel-default">
10 10 <div class="panel-heading">
11 11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 12 % if side_link:
13 13 <div class="pull-right">
14 14 <a href="${side_link}">${_('in JSON format')}</a>
15 15 </div>
16 16 % endif
17 17 </div>
18 18 <div class="panel-body">
19 19 <div class="perms_section_head field">
20 20 <div class="radios">
21 21 %if section != 'global':
22 22 <span class="permissions_boxes">
23 23 <span class="desc">${_('show')}: </span>
24 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 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 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 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 28 </span>
29 29 %endif
30 30 </div>
31 31 </div>
32 32 <div class="field">
33 33 %if not permissions[section]:
34 34 <p class="empty_data help-block">${_('No permissions defined')}</p>
35 35 %else:
36 36 <div id='tbl_list_wrap_${section}'>
37 37 <table id="tbl_list_${section}" class="rctable">
38 38 ## global permission box
39 39 %if section == 'global':
40 40 <thead>
41 41 <tr>
42 42 <th colspan="2" class="left">${_('Permission')}</th>
43 43 %if actions:
44 44 <th colspan="2">${_('Edit Permission')}</th>
45 45 %endif
46 46 </thead>
47 47 <tbody>
48 48
49 49 <%
50 50 def get_section_perms(prefix, opts):
51 51 _selected = []
52 52 for op in opts:
53 53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
54 54 _selected.append(op)
55 55 admin = 'hg.admin' in opts
56 56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
57 57 return admin, _selected_vals, _selected
58 58 %>
59 59
60 60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
61 61 <tr>
62 62 <td class="td-tags">
63 63 ${lbl}
64 64 </td>
65 65 <td class="td-tags">
66 66 %if val[0]:
67 67 %if not val_lbl:
68 68 ## super admin case
69 69 True
70 70 %else:
71 71 <span class="perm_tag admin">${val_lbl}.admin</span>
72 72 %endif
73 73 %else:
74 74 %if not val_lbl:
75 75 ${
76 76 {'false': False,
77 77 'true': True,
78 78 'none': False,
79 79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
80 80 }
81 81 %else:
82 82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
83 83 %endif
84 84 %endif
85 85 </td>
86 86 %if actions:
87 87
88 88 % if edit_url or edit_global_url:
89 89
90 90 <td class="td-action">
91 91 % if edit_url:
92 92 <a href="${edit_url}">${_('edit')}</a>
93 93 % else:
94 94 -
95 95 % endif
96 96 </td>
97 97
98 98 <td class="td-action">
99 99 % if edit_global_url:
100 100 <a href="${edit_global_url}">${_('edit global')}</a>
101 101 % else:
102 102 -
103 103 % endif
104 104 </td>
105 105
106 106 % else:
107 107 <td class="td-action"></td>
108 108 <td class="td-action">
109 109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
110 110 <td class="td-action">
111 111 % endif
112 112
113 113 %endif
114 114 </tr>
115 115 </%def>
116 116
117 117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
118 118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
119 119
120 120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
121 121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
122 122
123 123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
124 124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
125 125
126 126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
127 127 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
128 128
129 129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
130 130 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
131 131
132 132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
133 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 135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
136 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 138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
139 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 141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
142 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 144 </tbody>
145 145 %else:
146 146 ## none/read/write/admin permissions on groups/repos etc
147 147 <thead>
148 148 <tr>
149 149 <th>${_('Name')}</th>
150 150 <th>${_('Permission')}</th>
151 151 %if actions:
152 152 <th>${_('Edit Permission')}</th>
153 153 %endif
154 154 </thead>
155 155 <tbody class="section_${section}">
156 156 <%
157 157 def sorter(permissions):
158 158 def custom_sorter(item):
159 159 ## read/write/admin
160 160 section = item[1].split('.')[-1]
161 161 section_importance = {'none': u'0',
162 162 'read': u'1',
163 163 'write':u'2',
164 164 'admin':u'3'}.get(section)
165 165 ## sort by group importance+name
166 166 return section_importance+item[0]
167 167 return sorted(permissions, key=custom_sorter)
168 168 %>
169 169 %for k, section_perm in sorter(permissions[section].items()):
170 170 %if section_perm.split('.')[-1] != 'none' or show_all:
171 171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
172 172 <td class="td-name">
173 173 %if section == 'repositories':
174 174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
175 175 %elif section == 'repositories_groups':
176 176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
177 177 %elif section == 'user_groups':
178 178 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
179 179 ${k}
180 180 %endif
181 181 </td>
182 182 <td class="td-tags">
183 183 %if hasattr(permissions[section], 'perm_origin_stack'):
184 184 <div>
185 185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
186 186
187 187 % if i > 0:
188 188 <div style="color: #979797">
189 189 <i class="icon-arrow_up"></i>
190 190 ${_('overridden by')}
191 191 <i class="icon-arrow_up"></i>
192 192 </div>
193 193 % endif
194 194
195 195 <div>
196 196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
197 197 ${perm} (${origin})
198 198 </span>
199 199 </div>
200 200
201 201 %endfor
202 202 </div>
203 203 %else:
204 204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
205 205 %endif
206 206 </td>
207 207 %if actions:
208 208 <td class="td-action">
209 209 %if section == 'repositories':
210 210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
211 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 213 %elif section == 'user_groups':
214 214 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
215 215 %endif
216 216 </td>
217 217 %endif
218 218 </tr>
219 219 %endif
220 220 %endfor
221 221
222 222 <tr id="empty_${section}" class="noborder" style="display:none;">
223 223 <td colspan="6">${_('No permission defined')}</td>
224 224 </tr>
225 225
226 226 </tbody>
227 227 %endif
228 228 </table>
229 229 </div>
230 230 %endif
231 231 </div>
232 232 </div>
233 233 </div>
234 234 %endfor
235 235 </div>
236 236
237 237 <script>
238 238 $(document).ready(function(){
239 239 var show_empty = function(section){
240 240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
241 241 if(visible == 0){
242 242 $('#empty_{0}'.format(section)).show();
243 243 }
244 244 else{
245 245 $('#empty_{0}'.format(section)).hide();
246 246 }
247 247 };
248 248 $('.perm_filter').on('change', function(e){
249 249 var self = this;
250 250 var section = $(this).attr('section');
251 251
252 252 var opts = {};
253 253 var elems = $('.filter_' + section).each(function(el){
254 254 var perm_type = $(this).attr('perm_type');
255 255 var checked = this.checked;
256 256 opts[perm_type] = checked;
257 257 if(checked){
258 258 $('.'+section+'_'+perm_type).show();
259 259 }
260 260 else{
261 261 $('.'+section+'_'+perm_type).hide();
262 262 }
263 263 });
264 264 show_empty(section);
265 265 })
266 266 })
267 267 </script>
268 268 </%def>
@@ -1,377 +1,377 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 <%def name="metatags_help()">
7 7 <table>
8 8 <%
9 9 example_tags = [
10 10 ('state','[stable]'),
11 11 ('state','[stale]'),
12 12 ('state','[featured]'),
13 13 ('state','[dev]'),
14 14 ('state','[dead]'),
15 15 ('state','[deprecated]'),
16 16
17 17 ('label','[personal]'),
18 18 ('generic','[v2.0.0]'),
19 19
20 20 ('lang','[lang =&gt; JavaScript]'),
21 21 ('license','[license =&gt; LicenseName]'),
22 22
23 23 ('ref','[requires =&gt; RepoName]'),
24 24 ('ref','[recommends =&gt; GroupName]'),
25 25 ('ref','[conflicts =&gt; SomeName]'),
26 26 ('ref','[base =&gt; SomeName]'),
27 27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 28 ('see','[see =&gt; http://rhodecode.com]'),
29 29 ]
30 30 %>
31 31 % for tag_type, tag in example_tags:
32 32 <tr>
33 33 <td>${tag|n}</td>
34 34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 35 </tr>
36 36 % endfor
37 37 </table>
38 38 </%def>
39 39
40 40 ## REPOSITORY RENDERERS
41 41 <%def name="quick_menu(repo_name)">
42 42 <i class="icon-more"></i>
43 43 <div class="menu_items_container hidden">
44 44 <ul class="menu_items">
45 45 <li>
46 46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
47 47 <span>${_('Summary')}</span>
48 48 </a>
49 49 </li>
50 50 <li>
51 51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
52 52 <span>${_('Changelog')}</span>
53 53 </a>
54 54 </li>
55 55 <li>
56 56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
57 57 <span>${_('Files')}</span>
58 58 </a>
59 59 </li>
60 60 <li>
61 61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
62 62 <span>${_('Fork')}</span>
63 63 </a>
64 64 </li>
65 65 </ul>
66 66 </div>
67 67 </%def>
68 68
69 69 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
70 70 <%
71 71 def get_name(name,short_name=short_name):
72 72 if short_name:
73 73 return name.split('/')[-1]
74 74 else:
75 75 return name
76 76 %>
77 77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
78 78 ##NAME
79 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 81 ##TYPE OF REPO
82 82 %if h.is_hg(rtype):
83 83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
84 84 %elif h.is_git(rtype):
85 85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
86 86 %elif h.is_svn(rtype):
87 87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
88 88 %endif
89 89
90 90 ##PRIVATE/PUBLIC
91 91 %if private and c.visual.show_private_icon:
92 92 <i class="icon-lock" title="${_('Private repository')}"></i>
93 93 %elif not private and c.visual.show_public_icon:
94 94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
95 95 %else:
96 96 <span></span>
97 97 %endif
98 98 ${get_name(name)}
99 99 </a>
100 100 %if fork_of:
101 101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
102 102 %endif
103 103 %if rstate == 'repo_state_pending':
104 104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
105 105 (${_('creating...')})
106 106 </span>
107 107 %endif
108 108 </div>
109 109 </%def>
110 110
111 111 <%def name="repo_desc(description, stylify_metatags)">
112 112 <%
113 113 tags, description = h.extract_metatags(description)
114 114 %>
115 115
116 116 <div class="truncate-wrap">
117 117 % if stylify_metatags:
118 118 % for tag_type, tag in tags:
119 119 ${h.style_metatag(tag_type, tag)|n}
120 120 % endfor
121 121 % endif
122 122 ${description}
123 123 </div>
124 124
125 125 </%def>
126 126
127 127 <%def name="last_change(last_change)">
128 128 ${h.age_component(last_change)}
129 129 </%def>
130 130
131 131 <%def name="revision(name,rev,tip,author,last_msg)">
132 132 <div>
133 133 %if rev >= 0:
134 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 135 %else:
136 136 ${_('No commits yet')}
137 137 %endif
138 138 </div>
139 139 </%def>
140 140
141 141 <%def name="rss(name)">
142 142 %if c.rhodecode_user.username != h.DEFAULT_USER:
143 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 144 %else:
145 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 146 %endif
147 147 </%def>
148 148
149 149 <%def name="atom(name)">
150 150 %if c.rhodecode_user.username != h.DEFAULT_USER:
151 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 152 %else:
153 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 154 %endif
155 155 </%def>
156 156
157 157 <%def name="user_gravatar(email, size=16)">
158 158 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
159 159 ${base.gravatar(email, 16)}
160 160 </div>
161 161 </%def>
162 162
163 163 <%def name="repo_actions(repo_name, super_user=True)">
164 164 <div>
165 165 <div class="grid_edit">
166 166 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
167 167 <i class="icon-pencil"></i>Edit</a>
168 168 </div>
169 169 <div class="grid_delete">
170 170 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
171 171 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
172 172 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
173 173 ${h.end_form()}
174 174 </div>
175 175 </div>
176 176 </%def>
177 177
178 178 <%def name="repo_state(repo_state)">
179 179 <div>
180 180 %if repo_state == 'repo_state_pending':
181 181 <div class="tag tag4">${_('Creating')}</div>
182 182 %elif repo_state == 'repo_state_created':
183 183 <div class="tag tag1">${_('Created')}</div>
184 184 %else:
185 185 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
186 186 %endif
187 187 </div>
188 188 </%def>
189 189
190 190
191 191 ## REPO GROUP RENDERERS
192 192 <%def name="quick_repo_group_menu(repo_group_name)">
193 193 <i class="icon-more"></i>
194 194 <div class="menu_items_container hidden">
195 195 <ul class="menu_items">
196 196 <li>
197 197 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
198 198 </li>
199 199
200 200 </ul>
201 201 </div>
202 202 </%def>
203 203
204 204 <%def name="repo_group_name(repo_group_name, children_groups=None)">
205 205 <div>
206 206 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
207 207 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
208 208 %if children_groups:
209 209 ${h.literal(' &raquo; '.join(children_groups))}
210 210 %else:
211 211 ${repo_group_name}
212 212 %endif
213 213 </a>
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_group_desc(description, personal, stylify_metatags)">
218 218
219 219 <%
220 220 tags, description = h.extract_metatags(description)
221 221 %>
222 222
223 223 <div class="truncate-wrap">
224 224 % if personal:
225 225 <div class="metatag" tag="personal">${_('personal')}</div>
226 226 % endif
227 227
228 228 % if stylify_metatags:
229 229 % for tag_type, tag in tags:
230 230 ${h.style_metatag(tag_type, tag)|n}
231 231 % endfor
232 232 % endif
233 233 ${description}
234 234 </div>
235 235
236 236 </%def>
237 237
238 238 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
239 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 241 </div>
242 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 244 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
245 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 246 ${h.end_form()}
247 247 </div>
248 248 </%def>
249 249
250 250
251 251 <%def name="user_actions(user_id, username)">
252 252 <div class="grid_edit">
253 253 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
254 254 <i class="icon-pencil"></i>${_('Edit')}</a>
255 255 </div>
256 256 <div class="grid_delete">
257 257 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
258 258 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
259 259 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
260 260 ${h.end_form()}
261 261 </div>
262 262 </%def>
263 263
264 264 <%def name="user_group_actions(user_group_id, user_group_name)">
265 265 <div class="grid_edit">
266 266 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
267 267 </div>
268 268 <div class="grid_delete">
269 269 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
270 270 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
271 271 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
272 272 ${h.end_form()}
273 273 </div>
274 274 </%def>
275 275
276 276
277 277 <%def name="user_name(user_id, username)">
278 278 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
279 279 </%def>
280 280
281 281 <%def name="user_profile(username)">
282 282 ${base.gravatar_with_user(username, 16)}
283 283 </%def>
284 284
285 285 <%def name="user_group_name(user_group_id, user_group_name)">
286 286 <div>
287 287 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
288 288 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
289 289 </div>
290 290 </%def>
291 291
292 292
293 293 ## GISTS
294 294
295 295 <%def name="gist_gravatar(full_contact)">
296 296 <div class="gist_gravatar">
297 297 ${base.gravatar(full_contact, 30)}
298 298 </div>
299 299 </%def>
300 300
301 301 <%def name="gist_access_id(gist_access_id, full_contact)">
302 302 <div>
303 303 <b>
304 304 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
305 305 </b>
306 306 </div>
307 307 </%def>
308 308
309 309 <%def name="gist_author(full_contact, created_on, expires)">
310 310 ${base.gravatar_with_user(full_contact, 16)}
311 311 </%def>
312 312
313 313
314 314 <%def name="gist_created(created_on)">
315 315 <div class="created">
316 316 ${h.age_component(created_on, time_is_local=True)}
317 317 </div>
318 318 </%def>
319 319
320 320 <%def name="gist_expires(expires)">
321 321 <div class="created">
322 322 %if expires == -1:
323 323 ${_('never')}
324 324 %else:
325 325 ${h.age_component(h.time_to_utcdatetime(expires))}
326 326 %endif
327 327 </div>
328 328 </%def>
329 329
330 330 <%def name="gist_type(gist_type)">
331 331 %if gist_type != 'public':
332 332 <div class="tag">${_('Private')}</div>
333 333 %endif
334 334 </%def>
335 335
336 336 <%def name="gist_description(gist_description)">
337 337 ${gist_description}
338 338 </%def>
339 339
340 340
341 341 ## PULL REQUESTS GRID RENDERERS
342 342
343 343 <%def name="pullrequest_target_repo(repo_name)">
344 344 <div class="truncate">
345 345 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
346 346 </div>
347 347 </%def>
348 348 <%def name="pullrequest_status(status)">
349 349 <div class="${'flag_status %s' % status} pull-left"></div>
350 350 </%def>
351 351
352 352 <%def name="pullrequest_title(title, description)">
353 353 ${title} <br/>
354 354 ${h.shorter(description, 40)}
355 355 </%def>
356 356
357 357 <%def name="pullrequest_comments(comments_nr)">
358 358 <i class="icon-comment"></i> ${comments_nr}
359 359 </%def>
360 360
361 361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
362 362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
363 363 % if short:
364 364 #${pull_request_id}
365 365 % else:
366 366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
367 367 % endif
368 368 </a>
369 369 </%def>
370 370
371 371 <%def name="pullrequest_updated_on(updated_on)">
372 372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
373 373 </%def>
374 374
375 375 <%def name="pullrequest_author(full_contact)">
376 376 ${base.gravatar_with_user(full_contact, 16)}
377 377 </%def>
@@ -1,173 +1,173 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="main()">
4 4 <div class="box">
5 5 <!-- box / title -->
6 6 <div class="title">
7 7 <div class="block-left breadcrumbs">
8 8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 9 ${self.breadcrumbs()}
10 10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
11 11 </div>
12 12 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 13 <div class="block-right">
14 14 <%
15 15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19 19
20 20 gr_name = c.repo_group.group_name if c.repo_group else None
21 21 # create repositories with write permission on group is set to true
22 22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 25 %>
26 26
27 27 %if not c.repo_group:
28 28 ## no repository group context here
29 29 %if is_admin or create_repo:
30 30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 31 %endif
32 32
33 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 35 %endif
36 36 %else:
37 37 ##we're inside other repository group other terms apply
38 38 %if is_admin or group_admin or (group_write and create_on_write):
39 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 40 %endif
41 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 43 %endif
44 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 46 %endif
47 47 %endif
48 48 </div>
49 49 %endif
50 50 </div>
51 51 <!-- end box / title -->
52 52 <div class="table">
53 53 <div id="groups_list_wrap">
54 54 <table id="group_list_table" class="display"></table>
55 55 </div>
56 56 </div>
57 57
58 58 <div class="table">
59 59 <div id="repos_list_wrap">
60 60 <table id="repo_list_table" class="display"></table>
61 61 </div>
62 62 </div>
63 63 </div>
64 64 <script>
65 65 $(document).ready(function() {
66 66
67 67 var get_datatable_count = function() {
68 68 var api = $('#repo_list_table').dataTable().api();
69 69 var pageInfo = api.page.info();
70 70 var repos = pageInfo.recordsDisplay;
71 71 var reposTotal = pageInfo.recordsTotal;
72 72
73 73 api = $('#group_list_table').dataTable().api();
74 74 pageInfo = api.page.info();
75 75 var repoGroups = pageInfo.recordsDisplay;
76 76 var repoGroupsTotal = pageInfo.recordsTotal;
77 77
78 78 if (repoGroups !== repoGroupsTotal) {
79 79 $('#match_count').text(repos+repoGroups);
80 80 }
81 81 if (repos !== reposTotal) {
82 82 $('#match_container').show();
83 83 }
84 84 if ($('#q_filter').val() === '') {
85 85 $('#match_container').hide();
86 86 }
87 87 };
88 88
89 89 // repo group list
90 90 $('#group_list_table').DataTable({
91 91 data: ${c.repo_groups_data|n},
92 92 dom: 'rtp',
93 93 pageLength: ${c.visual.dashboard_items},
94 94 order: [[ 0, "asc" ]],
95 95 columns: [
96 96 { data: {"_": "name",
97 97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
98 98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 99 { data: {"_": "desc",
100 100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 101 { data: {"_": "last_change",
102 102 "sort": "last_change_raw",
103 103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
104 104 { data: {"_": "owner",
105 105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
106 106 ],
107 107 language: {
108 108 paginate: DEFAULT_GRID_PAGINATION,
109 109 emptyTable: _gettext("No repository groups available yet.")
110 110 },
111 111 "drawCallback": function( settings, json ) {
112 112 timeagoActivate();
113 113 quick_repo_menu();
114 114 }
115 115 });
116 116
117 117 // repo list
118 118 $('#repo_list_table').DataTable({
119 119 data: ${c.repos_data|n},
120 120 dom: 'rtp',
121 121 order: [[ 0, "asc" ]],
122 122 pageLength: ${c.visual.dashboard_items},
123 123 columns: [
124 124 { data: {"_": "name",
125 125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
126 126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
127 127 { data: {"_": "desc",
128 128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
129 129 { data: {"_": "last_change",
130 130 "sort": "last_change_raw",
131 131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
132 132 { data: {"_": "last_changeset",
133 133 "sort": "last_changeset_raw",
134 134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
135 135 { data: {"_": "owner",
136 136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
137 137 ],
138 138 language: {
139 139 paginate: DEFAULT_GRID_PAGINATION,
140 140 emptyTable: _gettext("No repositories available yet.")
141 141 },
142 142 "drawCallback": function( settings, json ) {
143 143 timeagoActivate();
144 144 quick_repo_menu();
145 145 }
146 146 });
147 147
148 148 // update the counter when doing search
149 149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
150 150 get_datatable_count();
151 151 });
152 152
153 153 // filter, filter both grids
154 154 $('#q_filter').on( 'keyup', function () {
155 155 var repo_api = $('#repo_list_table').dataTable().api();
156 156 repo_api
157 157 .columns( 0 )
158 158 .search( this.value )
159 159 .draw();
160 160
161 161 var repo_group_api = $('#group_list_table').dataTable().api();
162 162 repo_group_api
163 163 .columns( 0 )
164 164 .search( this.value )
165 165 .draw();
166 166 });
167 167
168 168 // refilter table if page load via back button
169 169 $("#q_filter").trigger('keyup');
170 170
171 171 });
172 172 </script>
173 173 </%def>
@@ -1,150 +1,152 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.tests import TestController, url
23 from rhodecode.tests import TestController
24 24 from rhodecode.tests.fixture import Fixture
25 25
26 26
27 27 def route_path(name, params=None, **kwargs):
28 28 import urllib
29 29 from rhodecode.apps._base import ADMIN_PREFIX
30 30
31 31 base_url = {
32 32 'home': '/',
33 33 'repos':
34 34 ADMIN_PREFIX + '/repos',
35 'repo_groups':
36 ADMIN_PREFIX + '/repo_groups',
35 37 'user_groups':
36 38 ADMIN_PREFIX + '/user_groups',
37 39 'user_groups_data':
38 40 ADMIN_PREFIX + '/user_groups_data',
39 41 }[name].format(**kwargs)
40 42
41 43 if params:
42 44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 45 return base_url
44 46
45 47
46 48 fixture = Fixture()
47 49
48 50
49 51 class TestAdminDelegatedUser(TestController):
50 52
51 53 def test_regular_user_cannot_see_admin_interfaces(
52 54 self, user_util, xhr_header):
53 55 user = user_util.create_user(password='qweqwe')
54 56 self.log_user(user.username, 'qweqwe')
55 57
56 58 # check if in home view, such user doesn't see the "admin" menus
57 59 response = self.app.get(route_path('home'))
58 60
59 61 assert_response = response.assert_response()
60 62
61 63 assert_response.no_element_exists('li.local-admin-repos')
62 64 assert_response.no_element_exists('li.local-admin-repo-groups')
63 65 assert_response.no_element_exists('li.local-admin-user-groups')
64 66
65 67 response = self.app.get(route_path('repos'), status=200)
66 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 71 response.mustcontain('data: []')
70 72
71 73 response = self.app.get(route_path('user_groups_data'),
72 74 status=200, extra_environ=xhr_header)
73 75 assert response.json['data'] == []
74 76
75 77 def test_regular_user_can_see_admin_interfaces_if_owner(
76 78 self, user_util, xhr_header):
77 79 user = user_util.create_user(password='qweqwe')
78 80 username = user.username
79 81
80 82 repo = user_util.create_repo(owner=username)
81 83 repo_name = repo.repo_name
82 84
83 85 repo_group = user_util.create_repo_group(owner=username)
84 86 repo_group_name = repo_group.group_name
85 87
86 88 user_group = user_util.create_user_group(owner=username)
87 89 user_group_name = user_group.users_group_name
88 90
89 91 self.log_user(username, 'qweqwe')
90 92 # check if in home view, such user doesn't see the "admin" menus
91 93 response = self.app.get(route_path('home'))
92 94
93 95 assert_response = response.assert_response()
94 96
95 97 assert_response.one_element_exists('li.local-admin-repos')
96 98 assert_response.one_element_exists('li.local-admin-repo-groups')
97 99 assert_response.one_element_exists('li.local-admin-user-groups')
98 100
99 101 # admin interfaces have visible elements
100 102 response = self.app.get(route_path('repos'), status=200)
101 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 106 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
105 107
106 108 response = self.app.get(route_path('user_groups_data'),
107 109 extra_environ=xhr_header, status=200)
108 110 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
109 111
110 112 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
111 113 self, user_util, xhr_header):
112 114 user = user_util.create_user(password='qweqwe')
113 115 username = user.username
114 116
115 117 repo = user_util.create_repo()
116 118 repo_name = repo.repo_name
117 119
118 120 repo_group = user_util.create_repo_group()
119 121 repo_group_name = repo_group.group_name
120 122
121 123 user_group = user_util.create_user_group()
122 124 user_group_name = user_group.users_group_name
123 125
124 126 user_util.grant_user_permission_to_repo(
125 127 repo, user, 'repository.admin')
126 128 user_util.grant_user_permission_to_repo_group(
127 129 repo_group, user, 'group.admin')
128 130 user_util.grant_user_permission_to_user_group(
129 131 user_group, user, 'usergroup.admin')
130 132
131 133 self.log_user(username, 'qweqwe')
132 134 # check if in home view, such user doesn't see the "admin" menus
133 135 response = self.app.get(route_path('home'))
134 136
135 137 assert_response = response.assert_response()
136 138
137 139 assert_response.one_element_exists('li.local-admin-repos')
138 140 assert_response.one_element_exists('li.local-admin-repo-groups')
139 141 assert_response.one_element_exists('li.local-admin-user-groups')
140 142
141 143 # admin interfaces have visible elements
142 144 response = self.app.get(route_path('repos'), status=200)
143 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 148 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
147 149
148 150 response = self.app.get(route_path('user_groups_data'),
149 151 extra_environ=xhr_header, status=200)
150 152 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
@@ -1,468 +1,450 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22 import multiprocessing
23 23 import os
24 24
25 25 import mock
26 26 import py
27 27 import pytest
28 28
29 29 from rhodecode.lib import caching_query
30 30 from rhodecode.lib import utils
31 31 from rhodecode.lib.utils2 import md5
32 32 from rhodecode.model import settings
33 33 from rhodecode.model import db
34 34 from rhodecode.model import meta
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.repo_group import RepoGroupModel
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.settings import UiSetting, SettingsModel
39 39 from rhodecode.tests.fixture import Fixture
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
41 40
42 41
43 42 fixture = Fixture()
44 43
45 44
46 45 def extract_hooks(config):
47 46 """Return a dictionary with the hook entries of the given config."""
48 47 hooks = {}
49 48 config_items = config.serialize()
50 49 for section, name, value in config_items:
51 50 if section != 'hooks':
52 51 continue
53 52 hooks[name] = value
54 53
55 54 return hooks
56 55
57 56
58 57 def disable_hooks(request, hooks):
59 58 """Disables the given hooks from the UI settings."""
60 59 session = meta.Session()
61 60
62 61 model = SettingsModel()
63 62 for hook_key in hooks:
64 63 sett = model.get_ui_by_key(hook_key)
65 64 sett.ui_active = False
66 65 session.add(sett)
67 66
68 67 # Invalidate cache
69 68 ui_settings = session.query(db.RhodeCodeUi).options(
70 69 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
71 70 ui_settings.invalidate()
72 71
73 72 ui_settings = session.query(db.RhodeCodeUi).options(
74 73 caching_query.FromCache(
75 74 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
76 75 ui_settings.invalidate()
77 76
78 77 @request.addfinalizer
79 78 def rollback():
80 79 session.rollback()
81 80
82 81
83 82 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
84 83 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
85 84 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
86 85 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
87 86 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
88 87 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
89 88 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
90 89
91 90 HG_HOOKS = frozenset(
92 91 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
93 92 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
94 93
95 94
96 95 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
97 96 ([], HG_HOOKS),
98 97 (HG_HOOKS, []),
99 98
100 99 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
101 100
102 101 # When a pull/push hook is disabled, its pre-pull/push counterpart should
103 102 # be disabled too.
104 103 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
105 104 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
106 105 HOOK_PUSH_KEY]),
107 106 ])
108 107 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
109 108 expected_hooks):
110 109 disable_hooks(request, disabled_hooks)
111 110
112 111 config = utils.make_db_config()
113 112 hooks = extract_hooks(config)
114 113
115 114 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
116 115
117 116
118 117 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
119 118 ([], ['pull', 'push']),
120 119 ([HOOK_PUSH], ['pull']),
121 120 ([HOOK_PULL], ['push']),
122 121 ([HOOK_PULL, HOOK_PUSH], []),
123 122 ])
124 123 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
125 124 hook_keys = (HOOK_PUSH, HOOK_PULL)
126 125 ui_settings = [
127 126 ('hooks', key, 'some value', key not in disabled_hooks)
128 127 for key in hook_keys]
129 128
130 129 result = utils.get_enabled_hook_classes(ui_settings)
131 130 assert sorted(result) == expected_hooks
132 131
133 132
134 133 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
135 134 _stub_git_repo(tmpdir.ensure('repo', dir=True))
136 135 repos = list(utils.get_filesystem_repos(str(tmpdir)))
137 136 assert repos == [('repo', ('git', tmpdir.join('repo')))]
138 137
139 138
140 139 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
141 140 tmpdir.ensure('not-a-repo', dir=True)
142 141 repos = list(utils.get_filesystem_repos(str(tmpdir)))
143 142 assert repos == []
144 143
145 144
146 145 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
147 146 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
148 147 repos = list(utils.get_filesystem_repos(str(tmpdir)))
149 148 assert repos == []
150 149
151 150
152 151 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
153 152 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
154 153 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
155 154 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
156 155
157 156
158 157 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
159 158 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
160 159 repos = list(utils.get_filesystem_repos(str(tmpdir)))
161 160 assert repos == []
162 161
163 162
164 163 def test_get_filesystem_repos_skips_files(tmpdir):
165 164 tmpdir.ensure('test-file')
166 165 repos = list(utils.get_filesystem_repos(str(tmpdir)))
167 166 assert repos == []
168 167
169 168
170 169 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
171 170 removed_repo_name = 'rm__00000000_000000_000000__.stub'
172 171 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
173 172 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
174 173 repos = list(utils.get_filesystem_repos(str(tmpdir)))
175 174 assert repos == []
176 175
177 176
178 177 def _stub_git_repo(repo_path):
179 178 """
180 179 Make `repo_path` look like a Git repository.
181 180 """
182 181 repo_path.ensure('.git', dir=True)
183 182
184 183
185 184 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
186 185 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
187 186 tmpdir.ensure('test-file')
188 187 dirpaths = utils._get_dirpaths(str_class(tmpdir))
189 188 assert dirpaths == ['test-file']
190 189
191 190
192 191 def test_get_dirpaths_returns_all_paths_bytes(
193 192 tmpdir, platform_encodes_filenames):
194 193 if platform_encodes_filenames:
195 194 pytest.skip("This platform seems to encode filenames.")
196 195 tmpdir.ensure('repo-a-umlaut-\xe4')
197 196 dirpaths = utils._get_dirpaths(str(tmpdir))
198 197 assert dirpaths == ['repo-a-umlaut-\xe4']
199 198
200 199
201 200 def test_get_dirpaths_skips_paths_it_cannot_decode(
202 201 tmpdir, platform_encodes_filenames):
203 202 if platform_encodes_filenames:
204 203 pytest.skip("This platform seems to encode filenames.")
205 204 path_with_latin1 = 'repo-a-umlaut-\xe4'
206 205 tmpdir.ensure(path_with_latin1)
207 206 dirpaths = utils._get_dirpaths(unicode(tmpdir))
208 207 assert dirpaths == []
209 208
210 209
211 210 @pytest.fixture(scope='session')
212 211 def platform_encodes_filenames():
213 212 """
214 213 Boolean indicator if the current platform changes filename encodings.
215 214 """
216 215 path_with_latin1 = 'repo-a-umlaut-\xe4'
217 216 tmpdir = py.path.local.mkdtemp()
218 217 tmpdir.ensure(path_with_latin1)
219 218 read_path = tmpdir.listdir()[0].basename
220 219 tmpdir.remove()
221 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 225 def test_repo2db_mapper_groups(repo_groups):
244 226 session = meta.Session()
245 227 zombie_group, parent_group, child_group = repo_groups
246 228 zombie_path = os.path.join(
247 229 RepoGroupModel().repos_path, zombie_group.full_path)
248 230 os.rmdir(zombie_path)
249 231
250 232 # Avoid removing test repos when calling repo2db_mapper
251 233 repo_list = {
252 234 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
253 235 }
254 236 utils.repo2db_mapper(repo_list, remove_obsolete=True)
255 237
256 238 groups_in_db = session.query(db.RepoGroup).all()
257 239 assert child_group in groups_in_db
258 240 assert parent_group in groups_in_db
259 241 assert zombie_path not in groups_in_db
260 242
261 243
262 244 def test_repo2db_mapper_enables_largefiles(backend):
263 245 repo = backend.create_repo()
264 246 repo_list = {repo.repo_name: 'test'}
265 247 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
266 248 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
267 249 install_git_hook=mock.DEFAULT,
268 250 install_svn_hooks=mock.DEFAULT):
269 251 utils.repo2db_mapper(repo_list, remove_obsolete=False)
270 252 _, kwargs = scm_mock.call_args
271 253 assert kwargs['config'].get('extensions', 'largefiles') == ''
272 254
273 255
274 256 @pytest.mark.backends("git", "svn")
275 257 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
276 258 repo = backend.create_repo()
277 259 repo_list = {repo.repo_name: 'test'}
278 260 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
279 261 utils.repo2db_mapper(repo_list, remove_obsolete=False)
280 262 install_hooks_mock.assert_called_once_with(
281 263 repo.scm_instance(), repo_type=backend.alias)
282 264
283 265
284 266 @pytest.mark.backends("git", "svn")
285 267 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
286 268 repo = backend.create_repo()
287 269 RepoModel().delete(repo, fs_remove=False)
288 270 meta.Session().commit()
289 271 repo_list = {repo.repo_name: repo.scm_instance()}
290 272 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
291 273 utils.repo2db_mapper(repo_list, remove_obsolete=False)
292 274 assert install_hooks_mock.call_count == 1
293 275 install_hooks_args, _ = install_hooks_mock.call_args
294 276 assert install_hooks_args[0].name == repo.repo_name
295 277
296 278
297 279 class TestPasswordChanged(object):
298 280 def setup(self):
299 281 self.session = {
300 282 'rhodecode_user': {
301 283 'password': '0cc175b9c0f1b6a831c399e269772661'
302 284 }
303 285 }
304 286 self.auth_user = mock.Mock()
305 287 self.auth_user.userame = 'test'
306 288 self.auth_user.password = 'abc123'
307 289
308 290 def test_returns_false_for_default_user(self):
309 291 self.auth_user.username = db.User.DEFAULT_USER
310 292 result = utils.password_changed(self.auth_user, self.session)
311 293 assert result is False
312 294
313 295 def test_returns_false_if_password_was_not_changed(self):
314 296 self.session['rhodecode_user']['password'] = md5(
315 297 self.auth_user.password)
316 298 result = utils.password_changed(self.auth_user, self.session)
317 299 assert result is False
318 300
319 301 def test_returns_true_if_password_was_changed(self):
320 302 result = utils.password_changed(self.auth_user, self.session)
321 303 assert result is True
322 304
323 305 def test_returns_true_if_auth_user_password_is_empty(self):
324 306 self.auth_user.password = None
325 307 result = utils.password_changed(self.auth_user, self.session)
326 308 assert result is True
327 309
328 310 def test_returns_true_if_session_password_is_empty(self):
329 311 self.session['rhodecode_user'].pop('password')
330 312 result = utils.password_changed(self.auth_user, self.session)
331 313 assert result is True
332 314
333 315
334 316 class TestReadOpensourceLicenses(object):
335 317 def test_success(self):
336 318 utils._license_cache = None
337 319 json_data = '''
338 320 {
339 321 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
340 322 "python2.7-Markdown-2.6.2": {
341 323 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
342 324 }
343 325 }
344 326 '''
345 327 resource_string_patch = mock.patch.object(
346 328 utils.pkg_resources, 'resource_string', return_value=json_data)
347 329 with resource_string_patch:
348 330 result = utils.read_opensource_licenses()
349 331 assert result == json.loads(json_data)
350 332
351 333 def test_caching(self):
352 334 utils._license_cache = {
353 335 "python2.7-pytest-2.7.1": {
354 336 "UNKNOWN": None
355 337 },
356 338 "python2.7-Markdown-2.6.2": {
357 339 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
358 340 }
359 341 }
360 342 resource_patch = mock.patch.object(
361 343 utils.pkg_resources, 'resource_string', side_effect=Exception)
362 344 json_patch = mock.patch.object(
363 345 utils.json, 'loads', side_effect=Exception)
364 346
365 347 with resource_patch as resource_mock, json_patch as json_mock:
366 348 result = utils.read_opensource_licenses()
367 349
368 350 assert resource_mock.call_count == 0
369 351 assert json_mock.call_count == 0
370 352 assert result == utils._license_cache
371 353
372 354 def test_licenses_file_contains_no_unknown_licenses(self):
373 355 utils._license_cache = None
374 356 result = utils.read_opensource_licenses()
375 357 license_names = []
376 358 for licenses in result.values():
377 359 license_names.extend(licenses.keys())
378 360 assert 'UNKNOWN' not in license_names
379 361
380 362
381 363 class TestMakeDbConfig(object):
382 364 def test_data_from_config_data_from_db_returned(self):
383 365 test_data = [
384 366 ('section1', 'option1', 'value1'),
385 367 ('section2', 'option2', 'value2'),
386 368 ('section3', 'option3', 'value3'),
387 369 ]
388 370 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
389 371 config_mock.return_value = test_data
390 372 kwargs = {'clear_session': False, 'repo': 'test_repo'}
391 373 result = utils.make_db_config(**kwargs)
392 374 config_mock.assert_called_once_with(**kwargs)
393 375 for section, option, expected_value in test_data:
394 376 value = result.get(section, option)
395 377 assert value == expected_value
396 378
397 379
398 380 class TestConfigDataFromDb(object):
399 381 def test_config_data_from_db_returns_active_settings(self):
400 382 test_data = [
401 383 UiSetting('section1', 'option1', 'value1', True),
402 384 UiSetting('section2', 'option2', 'value2', True),
403 385 UiSetting('section3', 'option3', 'value3', False),
404 386 ]
405 387 repo_name = 'test_repo'
406 388
407 389 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
408 390 hooks_patch = mock.patch.object(
409 391 utils, 'get_enabled_hook_classes',
410 392 return_value=['pull', 'push', 'repo_size'])
411 393 with model_patch as model_mock, hooks_patch:
412 394 instance_mock = mock.Mock()
413 395 model_mock.return_value = instance_mock
414 396 instance_mock.get_ui_settings.return_value = test_data
415 397 result = utils.config_data_from_db(
416 398 clear_session=False, repo=repo_name)
417 399
418 400 self._assert_repo_name_passed(model_mock, repo_name)
419 401
420 402 expected_result = [
421 403 ('section1', 'option1', 'value1'),
422 404 ('section2', 'option2', 'value2'),
423 405 ]
424 406 assert result == expected_result
425 407
426 408 def _assert_repo_name_passed(self, model_mock, repo_name):
427 409 assert model_mock.call_count == 1
428 410 call_args, call_kwargs = model_mock.call_args
429 411 assert call_kwargs['repo'] == repo_name
430 412
431 413
432 414 class TestIsDirWritable(object):
433 415 def test_returns_false_when_not_writable(self):
434 416 with mock.patch('__builtin__.open', side_effect=OSError):
435 417 assert not utils._is_dir_writable('/stub-path')
436 418
437 419 def test_returns_true_when_writable(self, tmpdir):
438 420 assert utils._is_dir_writable(str(tmpdir))
439 421
440 422 def test_is_safe_against_race_conditions(self, tmpdir):
441 423 workers = multiprocessing.Pool()
442 424 directories = [str(tmpdir)] * 10
443 425 workers.map(utils._is_dir_writable, directories)
444 426
445 427
446 428 class TestGetEnabledHooks(object):
447 429 def test_only_active_hooks_are_enabled(self):
448 430 ui_settings = [
449 431 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
450 432 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
451 433 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
452 434 ]
453 435 result = utils.get_enabled_hook_classes(ui_settings)
454 436 assert result == ['push', 'repo_size']
455 437
456 438 def test_all_hooks_are_enabled(self):
457 439 ui_settings = [
458 440 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
459 441 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
460 442 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
461 443 ]
462 444 result = utils.get_enabled_hook_classes(ui_settings)
463 445 assert result == ['push', 'repo_size', 'pull']
464 446
465 447 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
466 448 ui_settings = []
467 449 result = utils.get_enabled_hook_classes(ui_settings)
468 450 assert result == []
@@ -1,223 +1,223 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22
23 23 from sqlalchemy.exc import IntegrityError
24 24 import pytest
25 25
26 26 from rhodecode.tests import TESTS_TMP_PATH
27 27 from rhodecode.tests.fixture import Fixture
28 28
29 29 from rhodecode.model.repo_group import RepoGroupModel
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.db import RepoGroup
32 32 from rhodecode.model.meta import Session
33 33
34 34
35 35 fixture = Fixture()
36 36
37 37
38 38 def _update_group(id_, group_name, desc='desc', parent_id=None):
39 39 form_data = fixture._get_group_create_params(group_name=group_name,
40 40 group_desc=desc,
41 41 group_parent_id=parent_id)
42 42 gr = RepoGroupModel().update(id_, form_data)
43 43 return gr
44 44
45 45
46 46 def _update_repo(name, **kwargs):
47 47 form_data = fixture._get_repo_create_params(**kwargs)
48 48 if 'repo_name' not in kwargs:
49 49 form_data['repo_name'] = name
50 50
51 51 if 'perm_additions' not in kwargs:
52 52 form_data['perm_additions'] = []
53 53 if 'perm_updates' not in kwargs:
54 54 form_data['perm_updates'] = []
55 55 if 'perm_deletions' not in kwargs:
56 56 form_data['perm_deletions'] = []
57 57
58 58 r = RepoModel().update(name, **form_data)
59 59 return r
60 60
61 61
62 class TestRepoGroups:
62 class TestRepoGroups(object):
63 63
64 64 @pytest.fixture(autouse=True)
65 65 def prepare(self, request):
66 66 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
67 67 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
68 68 self.g3 = fixture.create_repo_group('test3', skip_if_exists=True)
69 69 request.addfinalizer(Session.remove)
70 70
71 71 def __check_path(self, *path):
72 72 """
73 73 Checks the path for existance !
74 74 """
75 75 path = [TESTS_TMP_PATH] + list(path)
76 76 path = os.path.join(*path)
77 77 return os.path.isdir(path)
78 78
79 79 def __delete_group(self, id_):
80 80 RepoGroupModel().delete(id_)
81 81
82 82 def test_create_group(self):
83 83 g = fixture.create_repo_group('newGroup')
84 84 Session().commit()
85 85 assert g.full_path == 'newGroup'
86 86
87 87 assert self.__check_path('newGroup')
88 88
89 89 def test_create_same_name_group(self):
90 90 with pytest.raises(IntegrityError):
91 91 fixture.create_repo_group('newGroup')
92 92 Session().rollback()
93 93
94 94 def test_same_subgroup(self):
95 95 sg1 = fixture.create_repo_group('test1/sub1')
96 96 assert sg1.parent_group == self.g1
97 97 assert sg1.full_path == 'test1/sub1'
98 98 assert self.__check_path('test1', 'sub1')
99 99
100 100 ssg1 = fixture.create_repo_group('test1/sub1/subsub1')
101 101 assert ssg1.parent_group == sg1
102 102 assert ssg1.full_path == 'test1/sub1/subsub1'
103 103 assert self.__check_path('test1', 'sub1', 'subsub1')
104 104
105 105 def test_remove_group(self):
106 106 sg1 = fixture.create_repo_group('deleteme')
107 107 self.__delete_group(sg1.group_id)
108 108
109 109 assert RepoGroup.get(sg1.group_id) is None
110 110 assert not self.__check_path('deteteme')
111 111
112 112 sg1 = fixture.create_repo_group('test1/deleteme')
113 113 self.__delete_group(sg1.group_id)
114 114
115 115 assert RepoGroup.get(sg1.group_id) is None
116 116 assert not self.__check_path('test1', 'deteteme')
117 117
118 118 def test_rename_single_group(self):
119 119 sg1 = fixture.create_repo_group('initial')
120 120
121 121 _update_group(sg1.group_id, 'after')
122 122 assert self.__check_path('after')
123 123 assert RepoGroup.get_by_group_name('initial') is None
124 124
125 125 def test_update_group_parent(self):
126 126
127 127 sg1 = fixture.create_repo_group('test1/initial')
128 128
129 129 _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
130 130 assert self.__check_path('test1', 'after')
131 131 assert RepoGroup.get_by_group_name('test1/initial') is None
132 132
133 133 _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
134 134 assert self.__check_path('test3', 'after')
135 135 assert RepoGroup.get_by_group_name('test3/initial') is None
136 136
137 137 new_sg1 = _update_group(sg1.group_id, 'hello')
138 138 assert self.__check_path('hello')
139 139
140 140 assert RepoGroup.get_by_group_name('hello') == new_sg1
141 141
142 142 def test_subgrouping_with_repo(self):
143 143
144 144 g1 = fixture.create_repo_group('g1')
145 145 g2 = fixture.create_repo_group('g2')
146 146 # create new repo
147 147 r = fixture.create_repo('john')
148 148
149 149 assert r.repo_name == 'john'
150 150 # put repo into group
151 151 r = _update_repo('john', repo_group=g1.group_id)
152 152 Session().commit()
153 153 assert r.repo_name == 'g1/john'
154 154
155 155 _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
156 156 assert self.__check_path('g2', 'g1')
157 157
158 158 # test repo
159 159 assert r.repo_name == RepoGroup.url_sep().join(
160 160 ['g2', 'g1', r.just_name])
161 161
162 162 def test_move_to_root(self):
163 163 fixture.create_repo_group('t11')
164 164 g2 = fixture.create_repo_group('t11/t22')
165 165
166 166 assert g2.full_path == 't11/t22'
167 167 assert self.__check_path('t11', 't22')
168 168
169 169 g2 = _update_group(g2.group_id, 'g22', parent_id=None)
170 170 Session().commit()
171 171
172 172 assert g2.group_name == 'g22'
173 173 # we moved out group from t1 to '' so it's full path should be 'g2'
174 174 assert g2.full_path == 'g22'
175 175 assert not self.__check_path('t11', 't22')
176 176 assert self.__check_path('g22')
177 177
178 178 def test_rename_top_level_group_in_nested_setup(self):
179 179 g1 = fixture.create_repo_group('L1')
180 180 g2 = fixture.create_repo_group('L1/L2')
181 181 g3 = fixture.create_repo_group('L1/L2/L3')
182 182
183 183 r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
184 184
185 185 # rename L1 all groups should be now changed
186 186 _update_group(g1.group_id, 'L1_NEW')
187 187 Session().commit()
188 188 assert g1.full_path == 'L1_NEW'
189 189 assert g2.full_path == 'L1_NEW/L2'
190 190 assert g3.full_path == 'L1_NEW/L2/L3'
191 191 assert r.repo_name == 'L1_NEW/L2/L3/L3_REPO'
192 192
193 193 def test_change_parent_of_top_level_group_in_nested_setup(self):
194 194 g1 = fixture.create_repo_group('R1')
195 195 g2 = fixture.create_repo_group('R1/R2')
196 196 g3 = fixture.create_repo_group('R1/R2/R3')
197 197 g4 = fixture.create_repo_group('R1_NEW')
198 198
199 199 r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
200 200 # rename L1 all groups should be now changed
201 201 _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
202 202 Session().commit()
203 203 assert g1.full_path == 'R1_NEW/R1'
204 204 assert g2.full_path == 'R1_NEW/R1/R2'
205 205 assert g3.full_path == 'R1_NEW/R1/R2/R3'
206 206 assert r.repo_name == 'R1_NEW/R1/R2/R3/R3_REPO'
207 207
208 208 def test_change_parent_of_top_level_group_in_nested_setup_with_rename(
209 209 self):
210 210 g1 = fixture.create_repo_group('X1')
211 211 g2 = fixture.create_repo_group('X1/X2')
212 212 g3 = fixture.create_repo_group('X1/X2/X3')
213 213 g4 = fixture.create_repo_group('X1_NEW')
214 214
215 215 r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
216 216
217 217 # rename L1 all groups should be now changed
218 218 _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
219 219 Session().commit()
220 220 assert g1.full_path == 'X1_NEW/X1_PRIM'
221 221 assert g2.full_path == 'X1_NEW/X1_PRIM/X2'
222 222 assert g3.full_path == 'X1_NEW/X1_PRIM/X2/X3'
223 223 assert r.repo_name == 'X1_NEW/X1_PRIM/X2/X3/X3_REPO'
@@ -1,1832 +1,1858 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import collections
22 22 import datetime
23 23 import hashlib
24 24 import os
25 25 import re
26 26 import pprint
27 27 import shutil
28 28 import socket
29 29 import subprocess32
30 30 import time
31 31 import uuid
32 32 import dateutil.tz
33 33
34 34 import mock
35 35 import pyramid.testing
36 36 import pytest
37 37 import colander
38 38 import requests
39 39
40 40 import rhodecode
41 41 from rhodecode.lib.utils2 import AttributeDict
42 42 from rhodecode.model.changeset_status import ChangesetStatusModel
43 43 from rhodecode.model.comment import CommentsModel
44 44 from rhodecode.model.db import (
45 45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
46 46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.pull_request import PullRequestModel
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.settings import VcsSettingsModel
53 53 from rhodecode.model.user_group import UserGroupModel
54 54 from rhodecode.model.integration import IntegrationModel
55 55 from rhodecode.integrations import integration_type_registry
56 56 from rhodecode.integrations.types.base import IntegrationTypeBase
57 57 from rhodecode.lib.utils import repo2db_mapper
58 58 from rhodecode.lib.vcs import create_vcsserver_proxy
59 59 from rhodecode.lib.vcs.backends import get_backend
60 60 from rhodecode.lib.vcs.nodes import FileNode
61 61 from rhodecode.tests import (
62 62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 64 TEST_USER_REGULAR_PASS)
65 65 from rhodecode.tests.utils import CustomTestApp, set_anonymous_access, add_test_routes
66 66 from rhodecode.tests.fixture import Fixture
67 67
68 68
69 69 def _split_comma(value):
70 70 return value.split(',')
71 71
72 72
73 73 def pytest_addoption(parser):
74 74 parser.addoption(
75 75 '--keep-tmp-path', action='store_true',
76 76 help="Keep the test temporary directories")
77 77 parser.addoption(
78 78 '--backends', action='store', type=_split_comma,
79 79 default=['git', 'hg', 'svn'],
80 80 help="Select which backends to test for backend specific tests.")
81 81 parser.addoption(
82 82 '--dbs', action='store', type=_split_comma,
83 83 default=['sqlite'],
84 84 help="Select which database to test for database specific tests. "
85 85 "Possible options are sqlite,postgres,mysql")
86 86 parser.addoption(
87 87 '--appenlight', '--ae', action='store_true',
88 88 help="Track statistics in appenlight.")
89 89 parser.addoption(
90 90 '--appenlight-api-key', '--ae-key',
91 91 help="API key for Appenlight.")
92 92 parser.addoption(
93 93 '--appenlight-url', '--ae-url',
94 94 default="https://ae.rhodecode.com",
95 95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
96 96 parser.addoption(
97 97 '--sqlite-connection-string', action='store',
98 98 default='', help="Connection string for the dbs tests with SQLite")
99 99 parser.addoption(
100 100 '--postgres-connection-string', action='store',
101 101 default='', help="Connection string for the dbs tests with Postgres")
102 102 parser.addoption(
103 103 '--mysql-connection-string', action='store',
104 104 default='', help="Connection string for the dbs tests with MySQL")
105 105 parser.addoption(
106 106 '--repeat', type=int, default=100,
107 107 help="Number of repetitions in performance tests.")
108 108
109 109
110 110 def pytest_configure(config):
111 111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
112 112 from rhodecode.config import patches
113 113 patches.kombu_1_5_1_python_2_7_11()
114 114
115 115
116 116 def pytest_collection_modifyitems(session, config, items):
117 117 # nottest marked, compare nose, used for transition from nose to pytest
118 118 remaining = [
119 119 i for i in items if getattr(i.obj, '__test__', True)]
120 120 items[:] = remaining
121 121
122 122
123 123 def pytest_generate_tests(metafunc):
124 124 # Support test generation based on --backend parameter
125 125 if 'backend_alias' in metafunc.fixturenames:
126 126 backends = get_backends_from_metafunc(metafunc)
127 127 scope = None
128 128 if not backends:
129 129 pytest.skip("Not enabled for any of selected backends")
130 130 metafunc.parametrize('backend_alias', backends, scope=scope)
131 131 elif hasattr(metafunc.function, 'backends'):
132 132 backends = get_backends_from_metafunc(metafunc)
133 133 if not backends:
134 134 pytest.skip("Not enabled for any of selected backends")
135 135
136 136
137 137 def get_backends_from_metafunc(metafunc):
138 138 requested_backends = set(metafunc.config.getoption('--backends'))
139 139 if hasattr(metafunc.function, 'backends'):
140 140 # Supported backends by this test function, created from
141 141 # pytest.mark.backends
142 142 backends = metafunc.function.backends.args
143 143 elif hasattr(metafunc.cls, 'backend_alias'):
144 144 # Support class attribute "backend_alias", this is mainly
145 145 # for legacy reasons for tests not yet using pytest.mark.backends
146 146 backends = [metafunc.cls.backend_alias]
147 147 else:
148 148 backends = metafunc.config.getoption('--backends')
149 149 return requested_backends.intersection(backends)
150 150
151 151
152 152 @pytest.fixture(scope='session', autouse=True)
153 153 def activate_example_rcextensions(request):
154 154 """
155 155 Patch in an example rcextensions module which verifies passed in kwargs.
156 156 """
157 157 from rhodecode.tests.other import example_rcextensions
158 158
159 159 old_extensions = rhodecode.EXTENSIONS
160 160 rhodecode.EXTENSIONS = example_rcextensions
161 161
162 162 @request.addfinalizer
163 163 def cleanup():
164 164 rhodecode.EXTENSIONS = old_extensions
165 165
166 166
167 167 @pytest.fixture
168 168 def capture_rcextensions():
169 169 """
170 170 Returns the recorded calls to entry points in rcextensions.
171 171 """
172 172 calls = rhodecode.EXTENSIONS.calls
173 173 calls.clear()
174 174 # Note: At this moment, it is still the empty dict, but that will
175 175 # be filled during the test run and since it is a reference this
176 176 # is enough to make it work.
177 177 return calls
178 178
179 179
180 180 @pytest.fixture(scope='session')
181 181 def http_environ_session():
182 182 """
183 183 Allow to use "http_environ" in session scope.
184 184 """
185 185 return http_environ(
186 186 http_host_stub=http_host_stub())
187 187
188 188
189 189 @pytest.fixture
190 190 def http_host_stub():
191 191 """
192 192 Value of HTTP_HOST in the test run.
193 193 """
194 194 return 'example.com:80'
195 195
196 196
197 197 @pytest.fixture
198 198 def http_host_only_stub():
199 199 """
200 200 Value of HTTP_HOST in the test run.
201 201 """
202 202 return http_host_stub().split(':')[0]
203 203
204 204
205 205 @pytest.fixture
206 206 def http_environ(http_host_stub):
207 207 """
208 208 HTTP extra environ keys.
209 209
210 210 User by the test application and as well for setting up the pylons
211 211 environment. In the case of the fixture "app" it should be possible
212 212 to override this for a specific test case.
213 213 """
214 214 return {
215 215 'SERVER_NAME': http_host_only_stub(),
216 216 'SERVER_PORT': http_host_stub.split(':')[1],
217 217 'HTTP_HOST': http_host_stub,
218 218 'HTTP_USER_AGENT': 'rc-test-agent',
219 219 'REQUEST_METHOD': 'GET'
220 220 }
221 221
222 222
223 223 @pytest.fixture(scope='function')
224 224 def app(request, config_stub, pylonsapp, http_environ):
225 225 app = CustomTestApp(
226 226 pylonsapp,
227 227 extra_environ=http_environ)
228 228 if request.cls:
229 229 request.cls.app = app
230 230 return app
231 231
232 232
233 233 @pytest.fixture(scope='session')
234 234 def app_settings(pylonsapp, pylons_config):
235 235 """
236 236 Settings dictionary used to create the app.
237 237
238 238 Parses the ini file and passes the result through the sanitize and apply
239 239 defaults mechanism in `rhodecode.config.middleware`.
240 240 """
241 241 from paste.deploy.loadwsgi import loadcontext, APP
242 242 from rhodecode.config.middleware import (
243 243 sanitize_settings_and_apply_defaults)
244 244 context = loadcontext(APP, 'config:' + pylons_config)
245 245 settings = sanitize_settings_and_apply_defaults(context.config())
246 246 return settings
247 247
248 248
249 249 @pytest.fixture(scope='session')
250 250 def db(app_settings):
251 251 """
252 252 Initializes the database connection.
253 253
254 254 It uses the same settings which are used to create the ``pylonsapp`` or
255 255 ``app`` fixtures.
256 256 """
257 257 from rhodecode.config.utils import initialize_database
258 258 initialize_database(app_settings)
259 259
260 260
261 261 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
262 262
263 263
264 264 def _autologin_user(app, *args):
265 265 session = login_user_session(app, *args)
266 266 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
267 267 return LoginData(csrf_token, session['rhodecode_user'])
268 268
269 269
270 270 @pytest.fixture
271 271 def autologin_user(app):
272 272 """
273 273 Utility fixture which makes sure that the admin user is logged in
274 274 """
275 275 return _autologin_user(app)
276 276
277 277
278 278 @pytest.fixture
279 279 def autologin_regular_user(app):
280 280 """
281 281 Utility fixture which makes sure that the regular user is logged in
282 282 """
283 283 return _autologin_user(
284 284 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
285 285
286 286
287 287 @pytest.fixture(scope='function')
288 288 def csrf_token(request, autologin_user):
289 289 return autologin_user.csrf_token
290 290
291 291
292 292 @pytest.fixture(scope='function')
293 293 def xhr_header(request):
294 294 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
295 295
296 296
297 297 @pytest.fixture
298 298 def real_crypto_backend(monkeypatch):
299 299 """
300 300 Switch the production crypto backend on for this test.
301 301
302 302 During the test run the crypto backend is replaced with a faster
303 303 implementation based on the MD5 algorithm.
304 304 """
305 305 monkeypatch.setattr(rhodecode, 'is_test', False)
306 306
307 307
308 308 @pytest.fixture(scope='class')
309 309 def index_location(request, pylonsapp):
310 310 index_location = pylonsapp.config['app_conf']['search.location']
311 311 if request.cls:
312 312 request.cls.index_location = index_location
313 313 return index_location
314 314
315 315
316 316 @pytest.fixture(scope='session', autouse=True)
317 317 def tests_tmp_path(request):
318 318 """
319 319 Create temporary directory to be used during the test session.
320 320 """
321 321 if not os.path.exists(TESTS_TMP_PATH):
322 322 os.makedirs(TESTS_TMP_PATH)
323 323
324 324 if not request.config.getoption('--keep-tmp-path'):
325 325 @request.addfinalizer
326 326 def remove_tmp_path():
327 327 shutil.rmtree(TESTS_TMP_PATH)
328 328
329 329 return TESTS_TMP_PATH
330 330
331 331
332 332 @pytest.fixture
333 333 def test_repo_group(request):
334 334 """
335 335 Create a temporary repository group, and destroy it after
336 336 usage automatically
337 337 """
338 338 fixture = Fixture()
339 339 repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '')
340 340 repo_group = fixture.create_repo_group(repogroupid)
341 341
342 342 def _cleanup():
343 343 fixture.destroy_repo_group(repogroupid)
344 344
345 345 request.addfinalizer(_cleanup)
346 346 return repo_group
347 347
348 348
349 349 @pytest.fixture
350 350 def test_user_group(request):
351 351 """
352 352 Create a temporary user group, and destroy it after
353 353 usage automatically
354 354 """
355 355 fixture = Fixture()
356 356 usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '')
357 357 user_group = fixture.create_user_group(usergroupid)
358 358
359 359 def _cleanup():
360 360 fixture.destroy_user_group(user_group)
361 361
362 362 request.addfinalizer(_cleanup)
363 363 return user_group
364 364
365 365
366 366 @pytest.fixture(scope='session')
367 367 def test_repo(request):
368 368 container = TestRepoContainer()
369 369 request.addfinalizer(container._cleanup)
370 370 return container
371 371
372 372
373 373 class TestRepoContainer(object):
374 374 """
375 375 Container for test repositories which are used read only.
376 376
377 377 Repositories will be created on demand and re-used during the lifetime
378 378 of this object.
379 379
380 380 Usage to get the svn test repository "minimal"::
381 381
382 382 test_repo = TestContainer()
383 383 repo = test_repo('minimal', 'svn')
384 384
385 385 """
386 386
387 387 dump_extractors = {
388 388 'git': utils.extract_git_repo_from_dump,
389 389 'hg': utils.extract_hg_repo_from_dump,
390 390 'svn': utils.extract_svn_repo_from_dump,
391 391 }
392 392
393 393 def __init__(self):
394 394 self._cleanup_repos = []
395 395 self._fixture = Fixture()
396 396 self._repos = {}
397 397
398 398 def __call__(self, dump_name, backend_alias, config=None):
399 399 key = (dump_name, backend_alias)
400 400 if key not in self._repos:
401 401 repo = self._create_repo(dump_name, backend_alias, config)
402 402 self._repos[key] = repo.repo_id
403 403 return Repository.get(self._repos[key])
404 404
405 405 def _create_repo(self, dump_name, backend_alias, config):
406 406 repo_name = '%s-%s' % (backend_alias, dump_name)
407 407 backend_class = get_backend(backend_alias)
408 408 dump_extractor = self.dump_extractors[backend_alias]
409 409 repo_path = dump_extractor(dump_name, repo_name)
410 410
411 411 vcs_repo = backend_class(repo_path, config=config)
412 412 repo2db_mapper({repo_name: vcs_repo})
413 413
414 414 repo = RepoModel().get_by_repo_name(repo_name)
415 415 self._cleanup_repos.append(repo_name)
416 416 return repo
417 417
418 418 def _cleanup(self):
419 419 for repo_name in reversed(self._cleanup_repos):
420 420 self._fixture.destroy_repo(repo_name)
421 421
422 422
423 423 @pytest.fixture
424 424 def backend(request, backend_alias, pylonsapp, test_repo):
425 425 """
426 426 Parametrized fixture which represents a single backend implementation.
427 427
428 428 It respects the option `--backends` to focus the test run on specific
429 429 backend implementations.
430 430
431 431 It also supports `pytest.mark.xfail_backends` to mark tests as failing
432 432 for specific backends. This is intended as a utility for incremental
433 433 development of a new backend implementation.
434 434 """
435 435 if backend_alias not in request.config.getoption('--backends'):
436 436 pytest.skip("Backend %s not selected." % (backend_alias, ))
437 437
438 438 utils.check_xfail_backends(request.node, backend_alias)
439 439 utils.check_skip_backends(request.node, backend_alias)
440 440
441 441 repo_name = 'vcs_test_%s' % (backend_alias, )
442 442 backend = Backend(
443 443 alias=backend_alias,
444 444 repo_name=repo_name,
445 445 test_name=request.node.name,
446 446 test_repo_container=test_repo)
447 447 request.addfinalizer(backend.cleanup)
448 448 return backend
449 449
450 450
451 451 @pytest.fixture
452 452 def backend_git(request, pylonsapp, test_repo):
453 453 return backend(request, 'git', pylonsapp, test_repo)
454 454
455 455
456 456 @pytest.fixture
457 457 def backend_hg(request, pylonsapp, test_repo):
458 458 return backend(request, 'hg', pylonsapp, test_repo)
459 459
460 460
461 461 @pytest.fixture
462 462 def backend_svn(request, pylonsapp, test_repo):
463 463 return backend(request, 'svn', pylonsapp, test_repo)
464 464
465 465
466 466 @pytest.fixture
467 467 def backend_random(backend_git):
468 468 """
469 469 Use this to express that your tests need "a backend.
470 470
471 471 A few of our tests need a backend, so that we can run the code. This
472 472 fixture is intended to be used for such cases. It will pick one of the
473 473 backends and run the tests.
474 474
475 475 The fixture `backend` would run the test multiple times for each
476 476 available backend which is a pure waste of time if the test is
477 477 independent of the backend type.
478 478 """
479 479 # TODO: johbo: Change this to pick a random backend
480 480 return backend_git
481 481
482 482
483 483 @pytest.fixture
484 484 def backend_stub(backend_git):
485 485 """
486 486 Use this to express that your tests need a backend stub
487 487
488 488 TODO: mikhail: Implement a real stub logic instead of returning
489 489 a git backend
490 490 """
491 491 return backend_git
492 492
493 493
494 494 @pytest.fixture
495 495 def repo_stub(backend_stub):
496 496 """
497 497 Use this to express that your tests need a repository stub
498 498 """
499 499 return backend_stub.create_repo()
500 500
501 501
502 502 class Backend(object):
503 503 """
504 504 Represents the test configuration for one supported backend
505 505
506 506 Provides easy access to different test repositories based on
507 507 `__getitem__`. Such repositories will only be created once per test
508 508 session.
509 509 """
510 510
511 511 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
512 512 _master_repo = None
513 513 _commit_ids = {}
514 514
515 515 def __init__(self, alias, repo_name, test_name, test_repo_container):
516 516 self.alias = alias
517 517 self.repo_name = repo_name
518 518 self._cleanup_repos = []
519 519 self._test_name = test_name
520 520 self._test_repo_container = test_repo_container
521 521 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
522 522 # Fixture will survive in the end.
523 523 self._fixture = Fixture()
524 524
525 525 def __getitem__(self, key):
526 526 return self._test_repo_container(key, self.alias)
527 527
528 528 def create_test_repo(self, key, config=None):
529 529 return self._test_repo_container(key, self.alias, config)
530 530
531 531 @property
532 532 def repo(self):
533 533 """
534 534 Returns the "current" repository. This is the vcs_test repo or the
535 535 last repo which has been created with `create_repo`.
536 536 """
537 537 from rhodecode.model.db import Repository
538 538 return Repository.get_by_repo_name(self.repo_name)
539 539
540 540 @property
541 541 def default_branch_name(self):
542 542 VcsRepository = get_backend(self.alias)
543 543 return VcsRepository.DEFAULT_BRANCH_NAME
544 544
545 545 @property
546 546 def default_head_id(self):
547 547 """
548 548 Returns the default head id of the underlying backend.
549 549
550 550 This will be the default branch name in case the backend does have a
551 551 default branch. In the other cases it will point to a valid head
552 552 which can serve as the base to create a new commit on top of it.
553 553 """
554 554 vcsrepo = self.repo.scm_instance()
555 555 head_id = (
556 556 vcsrepo.DEFAULT_BRANCH_NAME or
557 557 vcsrepo.commit_ids[-1])
558 558 return head_id
559 559
560 560 @property
561 561 def commit_ids(self):
562 562 """
563 563 Returns the list of commits for the last created repository
564 564 """
565 565 return self._commit_ids
566 566
567 567 def create_master_repo(self, commits):
568 568 """
569 569 Create a repository and remember it as a template.
570 570
571 571 This allows to easily create derived repositories to construct
572 572 more complex scenarios for diff, compare and pull requests.
573 573
574 574 Returns a commit map which maps from commit message to raw_id.
575 575 """
576 576 self._master_repo = self.create_repo(commits=commits)
577 577 return self._commit_ids
578 578
579 579 def create_repo(
580 580 self, commits=None, number_of_commits=0, heads=None,
581 581 name_suffix=u'', **kwargs):
582 582 """
583 583 Create a repository and record it for later cleanup.
584 584
585 585 :param commits: Optional. A sequence of dict instances.
586 586 Will add a commit per entry to the new repository.
587 587 :param number_of_commits: Optional. If set to a number, this number of
588 588 commits will be added to the new repository.
589 589 :param heads: Optional. Can be set to a sequence of of commit
590 590 names which shall be pulled in from the master repository.
591 591
592 592 """
593 593 self.repo_name = self._next_repo_name() + name_suffix
594 594 repo = self._fixture.create_repo(
595 595 self.repo_name, repo_type=self.alias, **kwargs)
596 596 self._cleanup_repos.append(repo.repo_name)
597 597
598 598 commits = commits or [
599 599 {'message': 'Commit %s of %s' % (x, self.repo_name)}
600 600 for x in xrange(number_of_commits)]
601 601 self._add_commits_to_repo(repo.scm_instance(), commits)
602 602 if heads:
603 603 self.pull_heads(repo, heads)
604 604
605 605 return repo
606 606
607 607 def pull_heads(self, repo, heads):
608 608 """
609 609 Make sure that repo contains all commits mentioned in `heads`
610 610 """
611 611 vcsmaster = self._master_repo.scm_instance()
612 612 vcsrepo = repo.scm_instance()
613 613 vcsrepo.config.clear_section('hooks')
614 614 commit_ids = [self._commit_ids[h] for h in heads]
615 615 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
616 616
617 617 def create_fork(self):
618 618 repo_to_fork = self.repo_name
619 619 self.repo_name = self._next_repo_name()
620 620 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
621 621 self._cleanup_repos.append(self.repo_name)
622 622 return repo
623 623
624 624 def new_repo_name(self, suffix=u''):
625 625 self.repo_name = self._next_repo_name() + suffix
626 626 self._cleanup_repos.append(self.repo_name)
627 627 return self.repo_name
628 628
629 629 def _next_repo_name(self):
630 630 return u"%s_%s" % (
631 631 self.invalid_repo_name.sub(u'_', self._test_name),
632 632 len(self._cleanup_repos))
633 633
634 634 def ensure_file(self, filename, content='Test content\n'):
635 635 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
636 636 commits = [
637 637 {'added': [
638 638 FileNode(filename, content=content),
639 639 ]},
640 640 ]
641 641 self._add_commits_to_repo(self.repo.scm_instance(), commits)
642 642
643 643 def enable_downloads(self):
644 644 repo = self.repo
645 645 repo.enable_downloads = True
646 646 Session().add(repo)
647 647 Session().commit()
648 648
649 649 def cleanup(self):
650 650 for repo_name in reversed(self._cleanup_repos):
651 651 self._fixture.destroy_repo(repo_name)
652 652
653 653 def _add_commits_to_repo(self, repo, commits):
654 654 commit_ids = _add_commits_to_repo(repo, commits)
655 655 if not commit_ids:
656 656 return
657 657 self._commit_ids = commit_ids
658 658
659 659 # Creating refs for Git to allow fetching them from remote repository
660 660 if self.alias == 'git':
661 661 refs = {}
662 662 for message in self._commit_ids:
663 663 # TODO: mikhail: do more special chars replacements
664 664 ref_name = 'refs/test-refs/{}'.format(
665 665 message.replace(' ', ''))
666 666 refs[ref_name] = self._commit_ids[message]
667 667 self._create_refs(repo, refs)
668 668
669 669 def _create_refs(self, repo, refs):
670 670 for ref_name in refs:
671 671 repo.set_refs(ref_name, refs[ref_name])
672 672
673 673
674 674 @pytest.fixture
675 675 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
676 676 """
677 677 Parametrized fixture which represents a single vcs backend implementation.
678 678
679 679 See the fixture `backend` for more details. This one implements the same
680 680 concept, but on vcs level. So it does not provide model instances etc.
681 681
682 682 Parameters are generated dynamically, see :func:`pytest_generate_tests`
683 683 for how this works.
684 684 """
685 685 if backend_alias not in request.config.getoption('--backends'):
686 686 pytest.skip("Backend %s not selected." % (backend_alias, ))
687 687
688 688 utils.check_xfail_backends(request.node, backend_alias)
689 689 utils.check_skip_backends(request.node, backend_alias)
690 690
691 691 repo_name = 'vcs_test_%s' % (backend_alias, )
692 692 repo_path = os.path.join(tests_tmp_path, repo_name)
693 693 backend = VcsBackend(
694 694 alias=backend_alias,
695 695 repo_path=repo_path,
696 696 test_name=request.node.name,
697 697 test_repo_container=test_repo)
698 698 request.addfinalizer(backend.cleanup)
699 699 return backend
700 700
701 701
702 702 @pytest.fixture
703 703 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
704 704 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
705 705
706 706
707 707 @pytest.fixture
708 708 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
709 709 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
710 710
711 711
712 712 @pytest.fixture
713 713 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
714 714 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
715 715
716 716
717 717 @pytest.fixture
718 718 def vcsbackend_random(vcsbackend_git):
719 719 """
720 720 Use this to express that your tests need "a vcsbackend".
721 721
722 722 The fixture `vcsbackend` would run the test multiple times for each
723 723 available vcs backend which is a pure waste of time if the test is
724 724 independent of the vcs backend type.
725 725 """
726 726 # TODO: johbo: Change this to pick a random backend
727 727 return vcsbackend_git
728 728
729 729
730 730 @pytest.fixture
731 731 def vcsbackend_stub(vcsbackend_git):
732 732 """
733 733 Use this to express that your test just needs a stub of a vcsbackend.
734 734
735 735 Plan is to eventually implement an in-memory stub to speed tests up.
736 736 """
737 737 return vcsbackend_git
738 738
739 739
740 740 class VcsBackend(object):
741 741 """
742 742 Represents the test configuration for one supported vcs backend.
743 743 """
744 744
745 745 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
746 746
747 747 def __init__(self, alias, repo_path, test_name, test_repo_container):
748 748 self.alias = alias
749 749 self._repo_path = repo_path
750 750 self._cleanup_repos = []
751 751 self._test_name = test_name
752 752 self._test_repo_container = test_repo_container
753 753
754 754 def __getitem__(self, key):
755 755 return self._test_repo_container(key, self.alias).scm_instance()
756 756
757 757 @property
758 758 def repo(self):
759 759 """
760 760 Returns the "current" repository. This is the vcs_test repo of the last
761 761 repo which has been created.
762 762 """
763 763 Repository = get_backend(self.alias)
764 764 return Repository(self._repo_path)
765 765
766 766 @property
767 767 def backend(self):
768 768 """
769 769 Returns the backend implementation class.
770 770 """
771 771 return get_backend(self.alias)
772 772
773 773 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
774 774 repo_name = self._next_repo_name()
775 775 self._repo_path = get_new_dir(repo_name)
776 776 repo_class = get_backend(self.alias)
777 777 src_url = None
778 778 if _clone_repo:
779 779 src_url = _clone_repo.path
780 780 repo = repo_class(self._repo_path, create=True, src_url=src_url)
781 781 self._cleanup_repos.append(repo)
782 782
783 783 commits = commits or [
784 784 {'message': 'Commit %s of %s' % (x, repo_name)}
785 785 for x in xrange(number_of_commits)]
786 786 _add_commits_to_repo(repo, commits)
787 787 return repo
788 788
789 789 def clone_repo(self, repo):
790 790 return self.create_repo(_clone_repo=repo)
791 791
792 792 def cleanup(self):
793 793 for repo in self._cleanup_repos:
794 794 shutil.rmtree(repo.path)
795 795
796 796 def new_repo_path(self):
797 797 repo_name = self._next_repo_name()
798 798 self._repo_path = get_new_dir(repo_name)
799 799 return self._repo_path
800 800
801 801 def _next_repo_name(self):
802 802 return "%s_%s" % (
803 803 self.invalid_repo_name.sub('_', self._test_name),
804 804 len(self._cleanup_repos))
805 805
806 806 def add_file(self, repo, filename, content='Test content\n'):
807 807 imc = repo.in_memory_commit
808 808 imc.add(FileNode(filename, content=content))
809 809 imc.commit(
810 810 message=u'Automatic commit from vcsbackend fixture',
811 811 author=u'Automatic')
812 812
813 813 def ensure_file(self, filename, content='Test content\n'):
814 814 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
815 815 self.add_file(self.repo, filename, content)
816 816
817 817
818 818 def _add_commits_to_repo(vcs_repo, commits):
819 819 commit_ids = {}
820 820 if not commits:
821 821 return commit_ids
822 822
823 823 imc = vcs_repo.in_memory_commit
824 824 commit = None
825 825
826 826 for idx, commit in enumerate(commits):
827 827 message = unicode(commit.get('message', 'Commit %s' % idx))
828 828
829 829 for node in commit.get('added', []):
830 830 imc.add(FileNode(node.path, content=node.content))
831 831 for node in commit.get('changed', []):
832 832 imc.change(FileNode(node.path, content=node.content))
833 833 for node in commit.get('removed', []):
834 834 imc.remove(FileNode(node.path))
835 835
836 836 parents = [
837 837 vcs_repo.get_commit(commit_id=commit_ids[p])
838 838 for p in commit.get('parents', [])]
839 839
840 840 operations = ('added', 'changed', 'removed')
841 841 if not any((commit.get(o) for o in operations)):
842 842 imc.add(FileNode('file_%s' % idx, content=message))
843 843
844 844 commit = imc.commit(
845 845 message=message,
846 846 author=unicode(commit.get('author', 'Automatic')),
847 847 date=commit.get('date'),
848 848 branch=commit.get('branch'),
849 849 parents=parents)
850 850
851 851 commit_ids[commit.message] = commit.raw_id
852 852
853 853 return commit_ids
854 854
855 855
856 856 @pytest.fixture
857 857 def reposerver(request):
858 858 """
859 859 Allows to serve a backend repository
860 860 """
861 861
862 862 repo_server = RepoServer()
863 863 request.addfinalizer(repo_server.cleanup)
864 864 return repo_server
865 865
866 866
867 867 class RepoServer(object):
868 868 """
869 869 Utility to serve a local repository for the duration of a test case.
870 870
871 871 Supports only Subversion so far.
872 872 """
873 873
874 874 url = None
875 875
876 876 def __init__(self):
877 877 self._cleanup_servers = []
878 878
879 879 def serve(self, vcsrepo):
880 880 if vcsrepo.alias != 'svn':
881 881 raise TypeError("Backend %s not supported" % vcsrepo.alias)
882 882
883 883 proc = subprocess32.Popen(
884 884 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
885 885 '--root', vcsrepo.path])
886 886 self._cleanup_servers.append(proc)
887 887 self.url = 'svn://localhost'
888 888
889 889 def cleanup(self):
890 890 for proc in self._cleanup_servers:
891 891 proc.terminate()
892 892
893 893
894 894 @pytest.fixture
895 895 def pr_util(backend, request, config_stub):
896 896 """
897 897 Utility for tests of models and for functional tests around pull requests.
898 898
899 899 It gives an instance of :class:`PRTestUtility` which provides various
900 900 utility methods around one pull request.
901 901
902 902 This fixture uses `backend` and inherits its parameterization.
903 903 """
904 904
905 905 util = PRTestUtility(backend)
906 906
907 907 @request.addfinalizer
908 908 def cleanup():
909 909 util.cleanup()
910 910
911 911 return util
912 912
913 913
914 914 class PRTestUtility(object):
915 915
916 916 pull_request = None
917 917 pull_request_id = None
918 918 mergeable_patcher = None
919 919 mergeable_mock = None
920 920 notification_patcher = None
921 921
922 922 def __init__(self, backend):
923 923 self.backend = backend
924 924
925 925 def create_pull_request(
926 926 self, commits=None, target_head=None, source_head=None,
927 927 revisions=None, approved=False, author=None, mergeable=False,
928 928 enable_notifications=True, name_suffix=u'', reviewers=None,
929 929 title=u"Test", description=u"Description"):
930 930 self.set_mergeable(mergeable)
931 931 if not enable_notifications:
932 932 # mock notification side effect
933 933 self.notification_patcher = mock.patch(
934 934 'rhodecode.model.notification.NotificationModel.create')
935 935 self.notification_patcher.start()
936 936
937 937 if not self.pull_request:
938 938 if not commits:
939 939 commits = [
940 940 {'message': 'c1'},
941 941 {'message': 'c2'},
942 942 {'message': 'c3'},
943 943 ]
944 944 target_head = 'c1'
945 945 source_head = 'c2'
946 946 revisions = ['c2']
947 947
948 948 self.commit_ids = self.backend.create_master_repo(commits)
949 949 self.target_repository = self.backend.create_repo(
950 950 heads=[target_head], name_suffix=name_suffix)
951 951 self.source_repository = self.backend.create_repo(
952 952 heads=[source_head], name_suffix=name_suffix)
953 953 self.author = author or UserModel().get_by_username(
954 954 TEST_USER_ADMIN_LOGIN)
955 955
956 956 model = PullRequestModel()
957 957 self.create_parameters = {
958 958 'created_by': self.author,
959 959 'source_repo': self.source_repository.repo_name,
960 960 'source_ref': self._default_branch_reference(source_head),
961 961 'target_repo': self.target_repository.repo_name,
962 962 'target_ref': self._default_branch_reference(target_head),
963 963 'revisions': [self.commit_ids[r] for r in revisions],
964 964 'reviewers': reviewers or self._get_reviewers(),
965 965 'title': title,
966 966 'description': description,
967 967 }
968 968 self.pull_request = model.create(**self.create_parameters)
969 969 assert model.get_versions(self.pull_request) == []
970 970
971 971 self.pull_request_id = self.pull_request.pull_request_id
972 972
973 973 if approved:
974 974 self.approve()
975 975
976 976 Session().add(self.pull_request)
977 977 Session().commit()
978 978
979 979 return self.pull_request
980 980
981 981 def approve(self):
982 982 self.create_status_votes(
983 983 ChangesetStatus.STATUS_APPROVED,
984 984 *self.pull_request.reviewers)
985 985
986 986 def close(self):
987 987 PullRequestModel().close_pull_request(self.pull_request, self.author)
988 988
989 989 def _default_branch_reference(self, commit_message):
990 990 reference = '%s:%s:%s' % (
991 991 'branch',
992 992 self.backend.default_branch_name,
993 993 self.commit_ids[commit_message])
994 994 return reference
995 995
996 996 def _get_reviewers(self):
997 997 return [
998 998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
999 999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
1000 1000 ]
1001 1001
1002 1002 def update_source_repository(self, head=None):
1003 1003 heads = [head or 'c3']
1004 1004 self.backend.pull_heads(self.source_repository, heads=heads)
1005 1005
1006 1006 def add_one_commit(self, head=None):
1007 1007 self.update_source_repository(head=head)
1008 1008 old_commit_ids = set(self.pull_request.revisions)
1009 1009 PullRequestModel().update_commits(self.pull_request)
1010 1010 commit_ids = set(self.pull_request.revisions)
1011 1011 new_commit_ids = commit_ids - old_commit_ids
1012 1012 assert len(new_commit_ids) == 1
1013 1013 return new_commit_ids.pop()
1014 1014
1015 1015 def remove_one_commit(self):
1016 1016 assert len(self.pull_request.revisions) == 2
1017 1017 source_vcs = self.source_repository.scm_instance()
1018 1018 removed_commit_id = source_vcs.commit_ids[-1]
1019 1019
1020 1020 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1021 1021 # remove the if once that's sorted out.
1022 1022 if self.backend.alias == "git":
1023 1023 kwargs = {'branch_name': self.backend.default_branch_name}
1024 1024 else:
1025 1025 kwargs = {}
1026 1026 source_vcs.strip(removed_commit_id, **kwargs)
1027 1027
1028 1028 PullRequestModel().update_commits(self.pull_request)
1029 1029 assert len(self.pull_request.revisions) == 1
1030 1030 return removed_commit_id
1031 1031
1032 1032 def create_comment(self, linked_to=None):
1033 1033 comment = CommentsModel().create(
1034 1034 text=u"Test comment",
1035 1035 repo=self.target_repository.repo_name,
1036 1036 user=self.author,
1037 1037 pull_request=self.pull_request)
1038 1038 assert comment.pull_request_version_id is None
1039 1039
1040 1040 if linked_to:
1041 1041 PullRequestModel()._link_comments_to_version(linked_to)
1042 1042
1043 1043 return comment
1044 1044
1045 1045 def create_inline_comment(
1046 1046 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1047 1047 comment = CommentsModel().create(
1048 1048 text=u"Test comment",
1049 1049 repo=self.target_repository.repo_name,
1050 1050 user=self.author,
1051 1051 line_no=line_no,
1052 1052 f_path=file_path,
1053 1053 pull_request=self.pull_request)
1054 1054 assert comment.pull_request_version_id is None
1055 1055
1056 1056 if linked_to:
1057 1057 PullRequestModel()._link_comments_to_version(linked_to)
1058 1058
1059 1059 return comment
1060 1060
1061 1061 def create_version_of_pull_request(self):
1062 1062 pull_request = self.create_pull_request()
1063 1063 version = PullRequestModel()._create_version_from_snapshot(
1064 1064 pull_request)
1065 1065 return version
1066 1066
1067 1067 def create_status_votes(self, status, *reviewers):
1068 1068 for reviewer in reviewers:
1069 1069 ChangesetStatusModel().set_status(
1070 1070 repo=self.pull_request.target_repo,
1071 1071 status=status,
1072 1072 user=reviewer.user_id,
1073 1073 pull_request=self.pull_request)
1074 1074
1075 1075 def set_mergeable(self, value):
1076 1076 if not self.mergeable_patcher:
1077 1077 self.mergeable_patcher = mock.patch.object(
1078 1078 VcsSettingsModel, 'get_general_settings')
1079 1079 self.mergeable_mock = self.mergeable_patcher.start()
1080 1080 self.mergeable_mock.return_value = {
1081 1081 'rhodecode_pr_merge_enabled': value}
1082 1082
1083 1083 def cleanup(self):
1084 1084 # In case the source repository is already cleaned up, the pull
1085 1085 # request will already be deleted.
1086 1086 pull_request = PullRequest().get(self.pull_request_id)
1087 1087 if pull_request:
1088 1088 PullRequestModel().delete(pull_request, pull_request.author)
1089 1089 Session().commit()
1090 1090
1091 1091 if self.notification_patcher:
1092 1092 self.notification_patcher.stop()
1093 1093
1094 1094 if self.mergeable_patcher:
1095 1095 self.mergeable_patcher.stop()
1096 1096
1097 1097
1098 1098 @pytest.fixture
1099 1099 def user_admin(pylonsapp):
1100 1100 """
1101 1101 Provides the default admin test user as an instance of `db.User`.
1102 1102 """
1103 1103 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1104 1104 return user
1105 1105
1106 1106
1107 1107 @pytest.fixture
1108 1108 def user_regular(pylonsapp):
1109 1109 """
1110 1110 Provides the default regular test user as an instance of `db.User`.
1111 1111 """
1112 1112 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1113 1113 return user
1114 1114
1115 1115
1116 1116 @pytest.fixture
1117 1117 def user_util(request, pylonsapp):
1118 1118 """
1119 1119 Provides a wired instance of `UserUtility` with integrated cleanup.
1120 1120 """
1121 1121 utility = UserUtility(test_name=request.node.name)
1122 1122 request.addfinalizer(utility.cleanup)
1123 1123 return utility
1124 1124
1125 1125
1126 1126 # TODO: johbo: Split this up into utilities per domain or something similar
1127 1127 class UserUtility(object):
1128 1128
1129 1129 def __init__(self, test_name="test"):
1130 1130 self._test_name = self._sanitize_name(test_name)
1131 1131 self.fixture = Fixture()
1132 1132 self.repo_group_ids = []
1133 1133 self.repos_ids = []
1134 1134 self.user_ids = []
1135 1135 self.user_group_ids = []
1136 1136 self.user_repo_permission_ids = []
1137 1137 self.user_group_repo_permission_ids = []
1138 1138 self.user_repo_group_permission_ids = []
1139 1139 self.user_group_repo_group_permission_ids = []
1140 1140 self.user_user_group_permission_ids = []
1141 1141 self.user_group_user_group_permission_ids = []
1142 1142 self.user_permissions = []
1143 1143
1144 1144 def _sanitize_name(self, name):
1145 1145 for char in ['[', ']']:
1146 1146 name = name.replace(char, '_')
1147 1147 return name
1148 1148
1149 1149 def create_repo_group(
1150 1150 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1151 1151 group_name = "{prefix}_repogroup_{count}".format(
1152 1152 prefix=self._test_name,
1153 1153 count=len(self.repo_group_ids))
1154 1154 repo_group = self.fixture.create_repo_group(
1155 1155 group_name, cur_user=owner)
1156 1156 if auto_cleanup:
1157 1157 self.repo_group_ids.append(repo_group.group_id)
1158 1158 return repo_group
1159 1159
1160 1160 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None,
1161 1161 auto_cleanup=True, repo_type='hg'):
1162 1162 repo_name = "{prefix}_repository_{count}".format(
1163 1163 prefix=self._test_name,
1164 1164 count=len(self.repos_ids))
1165 1165
1166 1166 repository = self.fixture.create_repo(
1167 1167 repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type)
1168 1168 if auto_cleanup:
1169 1169 self.repos_ids.append(repository.repo_id)
1170 1170 return repository
1171 1171
1172 1172 def create_user(self, auto_cleanup=True, **kwargs):
1173 1173 user_name = "{prefix}_user_{count}".format(
1174 1174 prefix=self._test_name,
1175 1175 count=len(self.user_ids))
1176 1176 user = self.fixture.create_user(user_name, **kwargs)
1177 1177 if auto_cleanup:
1178 1178 self.user_ids.append(user.user_id)
1179 1179 return user
1180 1180
1181 1181 def create_user_with_group(self):
1182 1182 user = self.create_user()
1183 1183 user_group = self.create_user_group(members=[user])
1184 1184 return user, user_group
1185 1185
1186 1186 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1187 1187 auto_cleanup=True, **kwargs):
1188 1188 group_name = "{prefix}_usergroup_{count}".format(
1189 1189 prefix=self._test_name,
1190 1190 count=len(self.user_group_ids))
1191 1191 user_group = self.fixture.create_user_group(
1192 1192 group_name, cur_user=owner, **kwargs)
1193 1193
1194 1194 if auto_cleanup:
1195 1195 self.user_group_ids.append(user_group.users_group_id)
1196 1196 if members:
1197 1197 for user in members:
1198 1198 UserGroupModel().add_user_to_group(user_group, user)
1199 1199 return user_group
1200 1200
1201 1201 def grant_user_permission(self, user_name, permission_name):
1202 1202 self._inherit_default_user_permissions(user_name, False)
1203 1203 self.user_permissions.append((user_name, permission_name))
1204 1204
1205 1205 def grant_user_permission_to_repo_group(
1206 1206 self, repo_group, user, permission_name):
1207 1207 permission = RepoGroupModel().grant_user_permission(
1208 1208 repo_group, user, permission_name)
1209 1209 self.user_repo_group_permission_ids.append(
1210 1210 (repo_group.group_id, user.user_id))
1211 1211 return permission
1212 1212
1213 1213 def grant_user_group_permission_to_repo_group(
1214 1214 self, repo_group, user_group, permission_name):
1215 1215 permission = RepoGroupModel().grant_user_group_permission(
1216 1216 repo_group, user_group, permission_name)
1217 1217 self.user_group_repo_group_permission_ids.append(
1218 1218 (repo_group.group_id, user_group.users_group_id))
1219 1219 return permission
1220 1220
1221 1221 def grant_user_permission_to_repo(
1222 1222 self, repo, user, permission_name):
1223 1223 permission = RepoModel().grant_user_permission(
1224 1224 repo, user, permission_name)
1225 1225 self.user_repo_permission_ids.append(
1226 1226 (repo.repo_id, user.user_id))
1227 1227 return permission
1228 1228
1229 1229 def grant_user_group_permission_to_repo(
1230 1230 self, repo, user_group, permission_name):
1231 1231 permission = RepoModel().grant_user_group_permission(
1232 1232 repo, user_group, permission_name)
1233 1233 self.user_group_repo_permission_ids.append(
1234 1234 (repo.repo_id, user_group.users_group_id))
1235 1235 return permission
1236 1236
1237 1237 def grant_user_permission_to_user_group(
1238 1238 self, target_user_group, user, permission_name):
1239 1239 permission = UserGroupModel().grant_user_permission(
1240 1240 target_user_group, user, permission_name)
1241 1241 self.user_user_group_permission_ids.append(
1242 1242 (target_user_group.users_group_id, user.user_id))
1243 1243 return permission
1244 1244
1245 1245 def grant_user_group_permission_to_user_group(
1246 1246 self, target_user_group, user_group, permission_name):
1247 1247 permission = UserGroupModel().grant_user_group_permission(
1248 1248 target_user_group, user_group, permission_name)
1249 1249 self.user_group_user_group_permission_ids.append(
1250 1250 (target_user_group.users_group_id, user_group.users_group_id))
1251 1251 return permission
1252 1252
1253 1253 def revoke_user_permission(self, user_name, permission_name):
1254 1254 self._inherit_default_user_permissions(user_name, True)
1255 1255 UserModel().revoke_perm(user_name, permission_name)
1256 1256
1257 1257 def _inherit_default_user_permissions(self, user_name, value):
1258 1258 user = UserModel().get_by_username(user_name)
1259 1259 user.inherit_default_permissions = value
1260 1260 Session().add(user)
1261 1261 Session().commit()
1262 1262
1263 1263 def cleanup(self):
1264 1264 self._cleanup_permissions()
1265 1265 self._cleanup_repos()
1266 1266 self._cleanup_repo_groups()
1267 1267 self._cleanup_user_groups()
1268 1268 self._cleanup_users()
1269 1269
1270 1270 def _cleanup_permissions(self):
1271 1271 if self.user_permissions:
1272 1272 for user_name, permission_name in self.user_permissions:
1273 1273 self.revoke_user_permission(user_name, permission_name)
1274 1274
1275 1275 for permission in self.user_repo_permission_ids:
1276 1276 RepoModel().revoke_user_permission(*permission)
1277 1277
1278 1278 for permission in self.user_group_repo_permission_ids:
1279 1279 RepoModel().revoke_user_group_permission(*permission)
1280 1280
1281 1281 for permission in self.user_repo_group_permission_ids:
1282 1282 RepoGroupModel().revoke_user_permission(*permission)
1283 1283
1284 1284 for permission in self.user_group_repo_group_permission_ids:
1285 1285 RepoGroupModel().revoke_user_group_permission(*permission)
1286 1286
1287 1287 for permission in self.user_user_group_permission_ids:
1288 1288 UserGroupModel().revoke_user_permission(*permission)
1289 1289
1290 1290 for permission in self.user_group_user_group_permission_ids:
1291 1291 UserGroupModel().revoke_user_group_permission(*permission)
1292 1292
1293 1293 def _cleanup_repo_groups(self):
1294 1294 def _repo_group_compare(first_group_id, second_group_id):
1295 1295 """
1296 1296 Gives higher priority to the groups with the most complex paths
1297 1297 """
1298 1298 first_group = RepoGroup.get(first_group_id)
1299 1299 second_group = RepoGroup.get(second_group_id)
1300 1300 first_group_parts = (
1301 1301 len(first_group.group_name.split('/')) if first_group else 0)
1302 1302 second_group_parts = (
1303 1303 len(second_group.group_name.split('/')) if second_group else 0)
1304 1304 return cmp(second_group_parts, first_group_parts)
1305 1305
1306 1306 sorted_repo_group_ids = sorted(
1307 1307 self.repo_group_ids, cmp=_repo_group_compare)
1308 1308 for repo_group_id in sorted_repo_group_ids:
1309 1309 self.fixture.destroy_repo_group(repo_group_id)
1310 1310
1311 1311 def _cleanup_repos(self):
1312 1312 sorted_repos_ids = sorted(self.repos_ids)
1313 1313 for repo_id in sorted_repos_ids:
1314 1314 self.fixture.destroy_repo(repo_id)
1315 1315
1316 1316 def _cleanup_user_groups(self):
1317 1317 def _user_group_compare(first_group_id, second_group_id):
1318 1318 """
1319 1319 Gives higher priority to the groups with the most complex paths
1320 1320 """
1321 1321 first_group = UserGroup.get(first_group_id)
1322 1322 second_group = UserGroup.get(second_group_id)
1323 1323 first_group_parts = (
1324 1324 len(first_group.users_group_name.split('/'))
1325 1325 if first_group else 0)
1326 1326 second_group_parts = (
1327 1327 len(second_group.users_group_name.split('/'))
1328 1328 if second_group else 0)
1329 1329 return cmp(second_group_parts, first_group_parts)
1330 1330
1331 1331 sorted_user_group_ids = sorted(
1332 1332 self.user_group_ids, cmp=_user_group_compare)
1333 1333 for user_group_id in sorted_user_group_ids:
1334 1334 self.fixture.destroy_user_group(user_group_id)
1335 1335
1336 1336 def _cleanup_users(self):
1337 1337 for user_id in self.user_ids:
1338 1338 self.fixture.destroy_user(user_id)
1339 1339
1340 1340
1341 1341 # TODO: Think about moving this into a pytest-pyro package and make it a
1342 1342 # pytest plugin
1343 1343 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1344 1344 def pytest_runtest_makereport(item, call):
1345 1345 """
1346 1346 Adding the remote traceback if the exception has this information.
1347 1347
1348 1348 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1349 1349 to the exception instance.
1350 1350 """
1351 1351 outcome = yield
1352 1352 report = outcome.get_result()
1353 1353 if call.excinfo:
1354 1354 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1355 1355
1356 1356
1357 1357 def _add_vcsserver_remote_traceback(report, exc):
1358 1358 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1359 1359
1360 1360 if vcsserver_traceback:
1361 1361 section = 'VCSServer remote traceback ' + report.when
1362 1362 report.sections.append((section, vcsserver_traceback))
1363 1363
1364 1364
1365 1365 @pytest.fixture(scope='session')
1366 1366 def testrun():
1367 1367 return {
1368 1368 'uuid': uuid.uuid4(),
1369 1369 'start': datetime.datetime.utcnow().isoformat(),
1370 1370 'timestamp': int(time.time()),
1371 1371 }
1372 1372
1373 1373
1374 1374 @pytest.fixture(autouse=True)
1375 1375 def collect_appenlight_stats(request, testrun):
1376 1376 """
1377 1377 This fixture reports memory consumtion of single tests.
1378 1378
1379 1379 It gathers data based on `psutil` and sends them to Appenlight. The option
1380 1380 ``--ae`` has te be used to enable this fixture and the API key for your
1381 1381 application has to be provided in ``--ae-key``.
1382 1382 """
1383 1383 try:
1384 1384 # cygwin cannot have yet psutil support.
1385 1385 import psutil
1386 1386 except ImportError:
1387 1387 return
1388 1388
1389 1389 if not request.config.getoption('--appenlight'):
1390 1390 return
1391 1391 else:
1392 1392 # Only request the pylonsapp fixture if appenlight tracking is
1393 1393 # enabled. This will speed up a test run of unit tests by 2 to 3
1394 1394 # seconds if appenlight is not enabled.
1395 1395 pylonsapp = request.getfuncargvalue("pylonsapp")
1396 1396 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1397 1397 client = AppenlightClient(
1398 1398 url=url,
1399 1399 api_key=request.config.getoption('--appenlight-api-key'),
1400 1400 namespace=request.node.nodeid,
1401 1401 request=str(testrun['uuid']),
1402 1402 testrun=testrun)
1403 1403
1404 1404 client.collect({
1405 1405 'message': "Starting",
1406 1406 })
1407 1407
1408 1408 server_and_port = pylonsapp.config['vcs.server']
1409 1409 protocol = pylonsapp.config['vcs.server.protocol']
1410 1410 server = create_vcsserver_proxy(server_and_port, protocol)
1411 1411 with server:
1412 1412 vcs_pid = server.get_pid()
1413 1413 server.run_gc()
1414 1414 vcs_process = psutil.Process(vcs_pid)
1415 1415 mem = vcs_process.memory_info()
1416 1416 client.tag_before('vcsserver.rss', mem.rss)
1417 1417 client.tag_before('vcsserver.vms', mem.vms)
1418 1418
1419 1419 test_process = psutil.Process()
1420 1420 mem = test_process.memory_info()
1421 1421 client.tag_before('test.rss', mem.rss)
1422 1422 client.tag_before('test.vms', mem.vms)
1423 1423
1424 1424 client.tag_before('time', time.time())
1425 1425
1426 1426 @request.addfinalizer
1427 1427 def send_stats():
1428 1428 client.tag_after('time', time.time())
1429 1429 with server:
1430 1430 gc_stats = server.run_gc()
1431 1431 for tag, value in gc_stats.items():
1432 1432 client.tag_after(tag, value)
1433 1433 mem = vcs_process.memory_info()
1434 1434 client.tag_after('vcsserver.rss', mem.rss)
1435 1435 client.tag_after('vcsserver.vms', mem.vms)
1436 1436
1437 1437 mem = test_process.memory_info()
1438 1438 client.tag_after('test.rss', mem.rss)
1439 1439 client.tag_after('test.vms', mem.vms)
1440 1440
1441 1441 client.collect({
1442 1442 'message': "Finished",
1443 1443 })
1444 1444 client.send_stats()
1445 1445
1446 1446 return client
1447 1447
1448 1448
1449 1449 class AppenlightClient():
1450 1450
1451 1451 url_template = '{url}?protocol_version=0.5'
1452 1452
1453 1453 def __init__(
1454 1454 self, url, api_key, add_server=True, add_timestamp=True,
1455 1455 namespace=None, request=None, testrun=None):
1456 1456 self.url = self.url_template.format(url=url)
1457 1457 self.api_key = api_key
1458 1458 self.add_server = add_server
1459 1459 self.add_timestamp = add_timestamp
1460 1460 self.namespace = namespace
1461 1461 self.request = request
1462 1462 self.server = socket.getfqdn(socket.gethostname())
1463 1463 self.tags_before = {}
1464 1464 self.tags_after = {}
1465 1465 self.stats = []
1466 1466 self.testrun = testrun or {}
1467 1467
1468 1468 def tag_before(self, tag, value):
1469 1469 self.tags_before[tag] = value
1470 1470
1471 1471 def tag_after(self, tag, value):
1472 1472 self.tags_after[tag] = value
1473 1473
1474 1474 def collect(self, data):
1475 1475 if self.add_server:
1476 1476 data.setdefault('server', self.server)
1477 1477 if self.add_timestamp:
1478 1478 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1479 1479 if self.namespace:
1480 1480 data.setdefault('namespace', self.namespace)
1481 1481 if self.request:
1482 1482 data.setdefault('request', self.request)
1483 1483 self.stats.append(data)
1484 1484
1485 1485 def send_stats(self):
1486 1486 tags = [
1487 1487 ('testrun', self.request),
1488 1488 ('testrun.start', self.testrun['start']),
1489 1489 ('testrun.timestamp', self.testrun['timestamp']),
1490 1490 ('test', self.namespace),
1491 1491 ]
1492 1492 for key, value in self.tags_before.items():
1493 1493 tags.append((key + '.before', value))
1494 1494 try:
1495 1495 delta = self.tags_after[key] - value
1496 1496 tags.append((key + '.delta', delta))
1497 1497 except Exception:
1498 1498 pass
1499 1499 for key, value in self.tags_after.items():
1500 1500 tags.append((key + '.after', value))
1501 1501 self.collect({
1502 1502 'message': "Collected tags",
1503 1503 'tags': tags,
1504 1504 })
1505 1505
1506 1506 response = requests.post(
1507 1507 self.url,
1508 1508 headers={
1509 1509 'X-appenlight-api-key': self.api_key},
1510 1510 json=self.stats,
1511 1511 )
1512 1512
1513 1513 if not response.status_code == 200:
1514 1514 pprint.pprint(self.stats)
1515 1515 print response.headers
1516 1516 print response.text
1517 1517 raise Exception('Sending to appenlight failed')
1518 1518
1519 1519
1520 1520 @pytest.fixture
1521 1521 def gist_util(request, pylonsapp):
1522 1522 """
1523 1523 Provides a wired instance of `GistUtility` with integrated cleanup.
1524 1524 """
1525 1525 utility = GistUtility()
1526 1526 request.addfinalizer(utility.cleanup)
1527 1527 return utility
1528 1528
1529 1529
1530 1530 class GistUtility(object):
1531 1531 def __init__(self):
1532 1532 self.fixture = Fixture()
1533 1533 self.gist_ids = []
1534 1534
1535 1535 def create_gist(self, **kwargs):
1536 1536 gist = self.fixture.create_gist(**kwargs)
1537 1537 self.gist_ids.append(gist.gist_id)
1538 1538 return gist
1539 1539
1540 1540 def cleanup(self):
1541 1541 for id_ in self.gist_ids:
1542 1542 self.fixture.destroy_gists(str(id_))
1543 1543
1544 1544
1545 1545 @pytest.fixture
1546 1546 def enabled_backends(request):
1547 1547 backends = request.config.option.backends
1548 1548 return backends[:]
1549 1549
1550 1550
1551 1551 @pytest.fixture
1552 1552 def settings_util(request):
1553 1553 """
1554 1554 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1555 1555 """
1556 1556 utility = SettingsUtility()
1557 1557 request.addfinalizer(utility.cleanup)
1558 1558 return utility
1559 1559
1560 1560
1561 1561 class SettingsUtility(object):
1562 1562 def __init__(self):
1563 1563 self.rhodecode_ui_ids = []
1564 1564 self.rhodecode_setting_ids = []
1565 1565 self.repo_rhodecode_ui_ids = []
1566 1566 self.repo_rhodecode_setting_ids = []
1567 1567
1568 1568 def create_repo_rhodecode_ui(
1569 1569 self, repo, section, value, key=None, active=True, cleanup=True):
1570 1570 key = key or hashlib.sha1(
1571 1571 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1572 1572
1573 1573 setting = RepoRhodeCodeUi()
1574 1574 setting.repository_id = repo.repo_id
1575 1575 setting.ui_section = section
1576 1576 setting.ui_value = value
1577 1577 setting.ui_key = key
1578 1578 setting.ui_active = active
1579 1579 Session().add(setting)
1580 1580 Session().commit()
1581 1581
1582 1582 if cleanup:
1583 1583 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1584 1584 return setting
1585 1585
1586 1586 def create_rhodecode_ui(
1587 1587 self, section, value, key=None, active=True, cleanup=True):
1588 1588 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1589 1589
1590 1590 setting = RhodeCodeUi()
1591 1591 setting.ui_section = section
1592 1592 setting.ui_value = value
1593 1593 setting.ui_key = key
1594 1594 setting.ui_active = active
1595 1595 Session().add(setting)
1596 1596 Session().commit()
1597 1597
1598 1598 if cleanup:
1599 1599 self.rhodecode_ui_ids.append(setting.ui_id)
1600 1600 return setting
1601 1601
1602 1602 def create_repo_rhodecode_setting(
1603 1603 self, repo, name, value, type_, cleanup=True):
1604 1604 setting = RepoRhodeCodeSetting(
1605 1605 repo.repo_id, key=name, val=value, type=type_)
1606 1606 Session().add(setting)
1607 1607 Session().commit()
1608 1608
1609 1609 if cleanup:
1610 1610 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1611 1611 return setting
1612 1612
1613 1613 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1614 1614 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1615 1615 Session().add(setting)
1616 1616 Session().commit()
1617 1617
1618 1618 if cleanup:
1619 1619 self.rhodecode_setting_ids.append(setting.app_settings_id)
1620 1620
1621 1621 return setting
1622 1622
1623 1623 def cleanup(self):
1624 1624 for id_ in self.rhodecode_ui_ids:
1625 1625 setting = RhodeCodeUi.get(id_)
1626 1626 Session().delete(setting)
1627 1627
1628 1628 for id_ in self.rhodecode_setting_ids:
1629 1629 setting = RhodeCodeSetting.get(id_)
1630 1630 Session().delete(setting)
1631 1631
1632 1632 for id_ in self.repo_rhodecode_ui_ids:
1633 1633 setting = RepoRhodeCodeUi.get(id_)
1634 1634 Session().delete(setting)
1635 1635
1636 1636 for id_ in self.repo_rhodecode_setting_ids:
1637 1637 setting = RepoRhodeCodeSetting.get(id_)
1638 1638 Session().delete(setting)
1639 1639
1640 1640 Session().commit()
1641 1641
1642 1642
1643 1643 @pytest.fixture
1644 1644 def no_notifications(request):
1645 1645 notification_patcher = mock.patch(
1646 1646 'rhodecode.model.notification.NotificationModel.create')
1647 1647 notification_patcher.start()
1648 1648 request.addfinalizer(notification_patcher.stop)
1649 1649
1650 1650
1651 1651 @pytest.fixture(scope='session')
1652 1652 def repeat(request):
1653 1653 """
1654 1654 The number of repetitions is based on this fixture.
1655 1655
1656 1656 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1657 1657 tests are not too slow in our default test suite.
1658 1658 """
1659 1659 return request.config.getoption('--repeat')
1660 1660
1661 1661
1662 1662 @pytest.fixture
1663 1663 def rhodecode_fixtures():
1664 1664 return Fixture()
1665 1665
1666 1666
1667 1667 @pytest.fixture
1668 1668 def request_stub():
1669 1669 """
1670 1670 Stub request object.
1671 1671 """
1672 1672 from rhodecode.lib.base import bootstrap_request
1673 1673 request = bootstrap_request(scheme='https')
1674 1674 return request
1675 1675
1676 1676
1677 1677 @pytest.fixture
1678 1678 def context_stub():
1679 1679 """
1680 1680 Stub context object.
1681 1681 """
1682 1682 context = pyramid.testing.DummyResource()
1683 1683 return context
1684 1684
1685 1685
1686 1686 @pytest.fixture
1687 1687 def config_stub(request, request_stub):
1688 1688 """
1689 1689 Set up pyramid.testing and return the Configurator.
1690 1690 """
1691 1691 config = pyramid.testing.setUp(request=request_stub)
1692 1692 add_test_routes(config)
1693 1693
1694 1694 @request.addfinalizer
1695 1695 def cleanup():
1696 1696 pyramid.testing.tearDown()
1697 1697
1698 1698 return config
1699 1699
1700 1700
1701 1701 @pytest.fixture
1702 1702 def StubIntegrationType():
1703 1703 class _StubIntegrationType(IntegrationTypeBase):
1704 1704 """ Test integration type class """
1705 1705
1706 1706 key = 'test'
1707 1707 display_name = 'Test integration type'
1708 1708 description = 'A test integration type for testing'
1709 1709 icon = 'test_icon_html_image'
1710 1710
1711 1711 def __init__(self, settings):
1712 1712 super(_StubIntegrationType, self).__init__(settings)
1713 1713 self.sent_events = [] # for testing
1714 1714
1715 1715 def send_event(self, event):
1716 1716 self.sent_events.append(event)
1717 1717
1718 1718 def settings_schema(self):
1719 1719 class SettingsSchema(colander.Schema):
1720 1720 test_string_field = colander.SchemaNode(
1721 1721 colander.String(),
1722 1722 missing=colander.required,
1723 1723 title='test string field',
1724 1724 )
1725 1725 test_int_field = colander.SchemaNode(
1726 1726 colander.Int(),
1727 1727 title='some integer setting',
1728 1728 )
1729 1729 return SettingsSchema()
1730 1730
1731 1731
1732 1732 integration_type_registry.register_integration_type(_StubIntegrationType)
1733 1733 return _StubIntegrationType
1734 1734
1735 1735 @pytest.fixture
1736 1736 def stub_integration_settings():
1737 1737 return {
1738 1738 'test_string_field': 'some data',
1739 1739 'test_int_field': 100,
1740 1740 }
1741 1741
1742 1742
1743 1743 @pytest.fixture
1744 1744 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1745 1745 stub_integration_settings):
1746 1746 integration = IntegrationModel().create(
1747 1747 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1748 1748 name='test repo integration',
1749 1749 repo=repo_stub, repo_group=None, child_repos_only=None)
1750 1750
1751 1751 @request.addfinalizer
1752 1752 def cleanup():
1753 1753 IntegrationModel().delete(integration)
1754 1754
1755 1755 return integration
1756 1756
1757 1757
1758 1758 @pytest.fixture
1759 1759 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1760 1760 stub_integration_settings):
1761 1761 integration = IntegrationModel().create(
1762 1762 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1763 1763 name='test repogroup integration',
1764 1764 repo=None, repo_group=test_repo_group, child_repos_only=True)
1765 1765
1766 1766 @request.addfinalizer
1767 1767 def cleanup():
1768 1768 IntegrationModel().delete(integration)
1769 1769
1770 1770 return integration
1771 1771
1772 1772
1773 1773 @pytest.fixture
1774 1774 def repogroup_recursive_integration_stub(request, test_repo_group,
1775 1775 StubIntegrationType, stub_integration_settings):
1776 1776 integration = IntegrationModel().create(
1777 1777 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1778 1778 name='test recursive repogroup integration',
1779 1779 repo=None, repo_group=test_repo_group, child_repos_only=False)
1780 1780
1781 1781 @request.addfinalizer
1782 1782 def cleanup():
1783 1783 IntegrationModel().delete(integration)
1784 1784
1785 1785 return integration
1786 1786
1787 1787
1788 1788 @pytest.fixture
1789 1789 def global_integration_stub(request, StubIntegrationType,
1790 1790 stub_integration_settings):
1791 1791 integration = IntegrationModel().create(
1792 1792 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1793 1793 name='test global integration',
1794 1794 repo=None, repo_group=None, child_repos_only=None)
1795 1795
1796 1796 @request.addfinalizer
1797 1797 def cleanup():
1798 1798 IntegrationModel().delete(integration)
1799 1799
1800 1800 return integration
1801 1801
1802 1802
1803 1803 @pytest.fixture
1804 1804 def root_repos_integration_stub(request, StubIntegrationType,
1805 1805 stub_integration_settings):
1806 1806 integration = IntegrationModel().create(
1807 1807 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1808 1808 name='test global integration',
1809 1809 repo=None, repo_group=None, child_repos_only=True)
1810 1810
1811 1811 @request.addfinalizer
1812 1812 def cleanup():
1813 1813 IntegrationModel().delete(integration)
1814 1814
1815 1815 return integration
1816 1816
1817 1817
1818 1818 @pytest.fixture
1819 1819 def local_dt_to_utc():
1820 1820 def _factory(dt):
1821 1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1822 1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1823 1823 return _factory
1824 1824
1825 1825
1826 1826 @pytest.fixture
1827 1827 def disable_anonymous_user(request, pylonsapp):
1828 1828 set_anonymous_access(False)
1829 1829
1830 1830 @request.addfinalizer
1831 1831 def cleanup():
1832 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
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now