##// END OF EJS Templates
grids: columns and sorting fixes
marcink -
r4150:554d4203 default
parent child Browse files
Show More
@@ -1,361 +1,360 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 import datetime
21 21 import logging
22 22 import time
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 28 from pyramid.view import view_config
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h, audit_logger
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 40 from rhodecode.model.forms import RepoGroupForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.db import (
45 45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 allow_empty_group = False
59 59
60 60 if self._can_create_repo_group():
61 61 # we're global admin, we're ok and we can create TOP level groups
62 62 allow_empty_group = True
63 63
64 64 # override the choices for this form, we need to filter choices
65 65 # and display only those we have ADMIN right
66 66 groups_with_admin_rights = RepoGroupList(
67 67 RepoGroup.query().all(),
68 68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 69 c.repo_groups = RepoGroup.groups_choices(
70 70 groups=groups_with_admin_rights,
71 71 show_empty_group=allow_empty_group)
72 72
73 73 def _can_create_repo_group(self, parent_group_id=None):
74 74 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 75 create_repo_group = HasPermissionAny(
76 76 'hg.repogroup.create.true')('group create controller')
77 77 if is_admin or (create_repo_group and not parent_group_id):
78 78 # we're global admin, or we have global repo group create
79 79 # permission
80 80 # we're ok and we can create TOP level groups
81 81 return True
82 82 elif parent_group_id:
83 83 # we check the permission if we can write to parent group
84 84 group = RepoGroup.get(parent_group_id)
85 85 group_name = group.group_name if group else None
86 86 if HasRepoGroupPermissionAny('group.admin')(
87 87 group_name, 'check if user is an admin of group'):
88 88 # we're an admin of passed in group, we're ok.
89 89 return True
90 90 else:
91 91 return False
92 92 return False
93 93
94 94 # permission check in data loading of
95 95 # `repo_group_list_data` via RepoGroupList
96 96 @LoginRequired()
97 97 @NotAnonymous()
98 98 @view_config(
99 99 route_name='repo_groups', request_method='GET',
100 100 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 101 def repo_group_list(self):
102 102 c = self.load_default_context()
103 103 return self._get_template_context(c)
104 104
105 105 # permission check inside
106 106 @LoginRequired()
107 107 @NotAnonymous()
108 108 @view_config(
109 109 route_name='repo_groups_data', request_method='GET',
110 110 renderer='json_ext', xhr=True)
111 111 def repo_group_list_data(self):
112 112 self.load_default_context()
113 113 column_map = {
114 'name_raw': 'group_name_hash',
114 'name': 'group_name_hash',
115 115 'desc': 'group_description',
116 'last_change_raw': 'updated_on',
116 'last_change': 'updated_on',
117 117 'top_level_repos': 'repos_total',
118 118 'owner': 'user_username',
119 119 }
120 120 draw, start, limit = self._extract_chunk(self.request)
121 121 search_q, order_by, order_dir = self._extract_ordering(
122 122 self.request, column_map=column_map)
123 123
124 124 _render = self.request.get_partial_renderer(
125 125 'rhodecode:templates/data_table/_dt_elements.mako')
126 126 c = _render.get_call_context()
127 127
128 128 def quick_menu(repo_group_name):
129 129 return _render('quick_repo_group_menu', repo_group_name)
130 130
131 131 def repo_group_lnk(repo_group_name):
132 132 return _render('repo_group_name', repo_group_name)
133 133
134 134 def last_change(last_change):
135 135 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 136 ts = time.time()
137 137 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 138 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 139 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 140 return _render("last_change", last_change)
141 141
142 142 def desc(desc, personal):
143 143 return _render(
144 144 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145 145
146 146 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 147 return _render(
148 148 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149 149
150 150 def user_profile(username):
151 151 return _render('user_profile', username)
152 152
153 153 _perms = ['group.admin']
154 154 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
155 155
156 156 repo_groups_data_total_count = RepoGroup.query()\
157 157 .filter(or_(
158 158 # generate multiple IN to fix limitation problems
159 159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 160 )) \
161 161 .count()
162 162
163 163 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 164 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 165 .count()
166 166
167 167 repo_count = count(Repository.repo_id)
168 168 base_q = Session.query(
169 169 RepoGroup.group_name,
170 170 RepoGroup.group_name_hash,
171 171 RepoGroup.group_description,
172 172 RepoGroup.group_id,
173 173 RepoGroup.personal,
174 174 RepoGroup.updated_on,
175 175 User,
176 176 repo_count.label('repos_count')
177 177 ) \
178 178 .filter(or_(
179 179 # generate multiple IN to fix limitation problems
180 180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 181 )) \
182 182 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
183 183 .join(User, User.user_id == RepoGroup.user_id) \
184 184 .group_by(RepoGroup, User)
185 185
186 186 if search_q:
187 187 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 188 base_q = base_q.filter(or_(
189 189 RepoGroup.group_name.ilike(like_expression),
190 190 ))
191 191
192 192 repo_groups_data_total_filtered_count = base_q.count()
193 193 # the inactive isn't really used, but we still make it same as other data grids
194 194 # which use inactive (users,user groups)
195 195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 196
197 197 sort_defined = False
198 198 if order_by == 'group_name':
199 199 sort_col = func.lower(RepoGroup.group_name)
200 200 sort_defined = True
201 201 elif order_by == 'repos_total':
202 202 sort_col = repo_count
203 203 sort_defined = True
204 204 elif order_by == 'user_username':
205 205 sort_col = User.username
206 206 else:
207 207 sort_col = getattr(RepoGroup, order_by, None)
208 208
209 209 if sort_defined or sort_col:
210 210 if order_dir == 'asc':
211 211 sort_col = sort_col.asc()
212 212 else:
213 213 sort_col = sort_col.desc()
214 214
215 215 base_q = base_q.order_by(sort_col)
216 216 base_q = base_q.offset(start).limit(limit)
217 217
218 218 # authenticated access to user groups
219 219 auth_repo_group_list = base_q.all()
220 220
221 221 repo_groups_data = []
222 222 for repo_gr in auth_repo_group_list:
223 223 row = {
224 224 "menu": quick_menu(repo_gr.group_name),
225 225 "name": repo_group_lnk(repo_gr.group_name),
226 "name_raw": repo_gr.group_name,
226
227 227 "last_change": last_change(repo_gr.updated_on),
228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229 228
230 229 "last_changeset": "",
231 230 "last_changeset_raw": "",
232 231
233 232 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 233 "owner": user_profile(repo_gr.User.username),
235 234 "top_level_repos": repo_gr.repos_count,
236 235 "action": repo_group_actions(
237 236 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238 237
239 238 }
240 239
241 240 repo_groups_data.append(row)
242 241
243 242 data = ({
244 243 'draw': draw,
245 244 'data': repo_groups_data,
246 245 'recordsTotal': repo_groups_data_total_count,
247 246 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 247 'recordsFiltered': repo_groups_data_total_filtered_count,
249 248 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 249 })
251 250
252 251 return data
253 252
254 253 @LoginRequired()
255 254 @NotAnonymous()
256 255 # perm checks inside
257 256 @view_config(
258 257 route_name='repo_group_new', request_method='GET',
259 258 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 259 def repo_group_new(self):
261 260 c = self.load_default_context()
262 261
263 262 # perm check for admin, create_group perm or admin of parent_group
264 263 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 264 if not self._can_create_repo_group(parent_group_id):
266 265 raise HTTPForbidden()
267 266
268 267 self._load_form_data(c)
269 268
270 269 defaults = {} # Future proof for default of repo group
271 270 data = render(
272 271 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
273 272 self._get_template_context(c), self.request)
274 273 html = formencode.htmlfill.render(
275 274 data,
276 275 defaults=defaults,
277 276 encoding="UTF-8",
278 277 force_defaults=False
279 278 )
280 279 return Response(html)
281 280
282 281 @LoginRequired()
283 282 @NotAnonymous()
284 283 @CSRFRequired()
285 284 # perm checks inside
286 285 @view_config(
287 286 route_name='repo_group_create', request_method='POST',
288 287 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
289 288 def repo_group_create(self):
290 289 c = self.load_default_context()
291 290 _ = self.request.translate
292 291
293 292 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 293 can_create = self._can_create_repo_group(parent_group_id)
295 294
296 295 self._load_form_data(c)
297 296 # permissions for can create group based on parent_id are checked
298 297 # here in the Form
299 298 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 299 repo_group_form = RepoGroupForm(
301 300 self.request.translate, available_groups=available_groups,
302 301 can_create_in_root=can_create)()
303 302
304 303 repo_group_name = self.request.POST.get('group_name')
305 304 try:
306 305 owner = self._rhodecode_user
307 306 form_result = repo_group_form.to_python(dict(self.request.POST))
308 307 copy_permissions = form_result.get('group_copy_permissions')
309 308 repo_group = RepoGroupModel().create(
310 309 group_name=form_result['group_name_full'],
311 310 group_description=form_result['group_description'],
312 311 owner=owner.user_id,
313 312 copy_permissions=form_result['group_copy_permissions']
314 313 )
315 314 Session().flush()
316 315
317 316 repo_group_data = repo_group.get_api_data()
318 317 audit_logger.store_web(
319 318 'repo_group.create', action_data={'data': repo_group_data},
320 319 user=self._rhodecode_user)
321 320
322 321 Session().commit()
323 322
324 323 _new_group_name = form_result['group_name_full']
325 324
326 325 repo_group_url = h.link_to(
327 326 _new_group_name,
328 327 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 328 h.flash(h.literal(_('Created repository group %s')
330 329 % repo_group_url), category='success')
331 330
332 331 except formencode.Invalid as errors:
333 332 data = render(
334 333 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 334 self._get_template_context(c), self.request)
336 335 html = formencode.htmlfill.render(
337 336 data,
338 337 defaults=errors.value,
339 338 errors=errors.error_dict or {},
340 339 prefix_error=False,
341 340 encoding="UTF-8",
342 341 force_defaults=False
343 342 )
344 343 return Response(html)
345 344 except Exception:
346 345 log.exception("Exception during creation of repository group")
347 346 h.flash(_('Error occurred during creation of repository group %s')
348 347 % repo_group_name, category='error')
349 348 raise HTTPFound(h.route_path('home'))
350 349
351 350 affected_user_ids = [self._rhodecode_user.user_id]
352 351 if copy_permissions:
353 352 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
354 353 copy_perms = [perm['user_id'] for perm in user_group_perms]
355 354 # also include those newly created by copy
356 355 affected_user_ids.extend(copy_perms)
357 356 PermissionModel().trigger_permission_flush(affected_user_ids)
358 357
359 358 raise HTTPFound(
360 359 h.route_path('repo_group_home',
361 360 repo_group_name=form_result['group_name_full']))
@@ -1,266 +1,266 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, CSRFRequired, NotAnonymous,
36 36 HasPermissionAny, HasRepoGroupPermissionAny)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 40 from rhodecode.model.forms import RepoForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.db import (
46 46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class AdminReposView(BaseAppView, DataGridAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55
56 56 return c
57 57
58 58 def _load_form_data(self, c):
59 59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 60 perm_set=['group.write', 'group.admin'])
61 61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64 64
65 65 @LoginRequired()
66 66 @NotAnonymous()
67 67 # perms check inside
68 68 @view_config(
69 69 route_name='repos', request_method='GET',
70 70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 71 def repository_list(self):
72 72 c = self.load_default_context()
73 73 return self._get_template_context(c)
74 74
75 75 @LoginRequired()
76 76 @NotAnonymous()
77 77 # perms check inside
78 78 @view_config(
79 79 route_name='repos_data', request_method='GET',
80 80 renderer='json_ext', xhr=True)
81 81 def repository_list_data(self):
82 82 self.load_default_context()
83 83 column_map = {
84 'name_raw': 'repo_name',
84 'name': 'repo_name',
85 85 'desc': 'description',
86 'last_change_raw': 'updated_on',
86 'last_change': 'updated_on',
87 87 'owner': 'user_username',
88 88 }
89 89 draw, start, limit = self._extract_chunk(self.request)
90 90 search_q, order_by, order_dir = self._extract_ordering(
91 91 self.request, column_map=column_map)
92 92
93 93 _perms = ['repository.admin']
94 94 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
95 95
96 96 repos_data_total_count = Repository.query() \
97 97 .filter(or_(
98 98 # generate multiple IN to fix limitation problems
99 99 *in_filter_generator(Repository.repo_id, allowed_ids))
100 100 ) \
101 101 .count()
102 102
103 103 base_q = Session.query(
104 104 Repository.repo_id,
105 105 Repository.repo_name,
106 106 Repository.description,
107 107 Repository.repo_type,
108 108 Repository.repo_state,
109 109 Repository.private,
110 110 Repository.archived,
111 111 Repository.fork,
112 112 Repository.updated_on,
113 113 Repository._changeset_cache,
114 114 User,
115 115 ) \
116 116 .filter(or_(
117 117 # generate multiple IN to fix limitation problems
118 118 *in_filter_generator(Repository.repo_id, allowed_ids))
119 119 ) \
120 120 .join(User, User.user_id == Repository.user_id) \
121 121 .group_by(Repository, User)
122 122
123 123 if search_q:
124 124 like_expression = u'%{}%'.format(safe_unicode(search_q))
125 125 base_q = base_q.filter(or_(
126 126 Repository.repo_name.ilike(like_expression),
127 127 ))
128 128
129 129 repos_data_total_filtered_count = base_q.count()
130 130
131 131 sort_defined = False
132 132 if order_by == 'repo_name':
133 133 sort_col = func.lower(Repository.repo_name)
134 134 sort_defined = True
135 135 elif order_by == 'user_username':
136 136 sort_col = User.username
137 137 else:
138 138 sort_col = getattr(Repository, order_by, None)
139 139
140 140 if sort_defined or sort_col:
141 141 if order_dir == 'asc':
142 142 sort_col = sort_col.asc()
143 143 else:
144 144 sort_col = sort_col.desc()
145 145
146 146 base_q = base_q.order_by(sort_col)
147 147 base_q = base_q.offset(start).limit(limit)
148 148
149 149 repos_list = base_q.all()
150 150
151 151 repos_data = RepoModel().get_repos_as_dict(
152 152 repo_list=repos_list, admin=True, super_user_actions=True)
153 153
154 154 data = ({
155 155 'draw': draw,
156 156 'data': repos_data,
157 157 'recordsTotal': repos_data_total_count,
158 158 'recordsFiltered': repos_data_total_filtered_count,
159 159 })
160 160 return data
161 161
162 162 @LoginRequired()
163 163 @NotAnonymous()
164 164 # perms check inside
165 165 @view_config(
166 166 route_name='repo_new', request_method='GET',
167 167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
168 168 def repository_new(self):
169 169 c = self.load_default_context()
170 170
171 171 new_repo = self.request.GET.get('repo', '')
172 172 parent_group = safe_int(self.request.GET.get('parent_group'))
173 173 _gr = RepoGroup.get(parent_group)
174 174
175 175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
176 176 # you're not super admin nor have global create permissions,
177 177 # but maybe you have at least write permission to a parent group ?
178 178
179 179 gr_name = _gr.group_name if _gr else None
180 180 # create repositories with write permission on group is set to true
181 181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
182 182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
183 183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
184 184 if not (group_admin or (group_write and create_on_write)):
185 185 raise HTTPForbidden()
186 186
187 187 self._load_form_data(c)
188 188 c.new_repo = repo_name_slug(new_repo)
189 189
190 190 # apply the defaults from defaults page
191 191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
192 192 # set checkbox to autochecked
193 193 defaults['repo_copy_permissions'] = True
194 194
195 195 parent_group_choice = '-1'
196 196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
197 197 parent_group_choice = self._rhodecode_user.personal_repo_group
198 198
199 199 if parent_group and _gr:
200 200 if parent_group in [x[0] for x in c.repo_groups]:
201 201 parent_group_choice = safe_unicode(parent_group)
202 202
203 203 defaults.update({'repo_group': parent_group_choice})
204 204
205 205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
206 206 self._get_template_context(c), self.request)
207 207 html = formencode.htmlfill.render(
208 208 data,
209 209 defaults=defaults,
210 210 encoding="UTF-8",
211 211 force_defaults=False
212 212 )
213 213 return Response(html)
214 214
215 215 @LoginRequired()
216 216 @NotAnonymous()
217 217 @CSRFRequired()
218 218 # perms check inside
219 219 @view_config(
220 220 route_name='repo_create', request_method='POST',
221 221 renderer='rhodecode:templates/admin/repos/repos.mako')
222 222 def repository_create(self):
223 223 c = self.load_default_context()
224 224
225 225 form_result = {}
226 226 self._load_form_data(c)
227 227
228 228 try:
229 229 # CanWriteToGroup validators checks permissions of this POST
230 230 form = RepoForm(
231 231 self.request.translate, repo_groups=c.repo_groups_choices)()
232 232 form_result = form.to_python(dict(self.request.POST))
233 233 copy_permissions = form_result.get('repo_copy_permissions')
234 234 # create is done sometimes async on celery, db transaction
235 235 # management is handled there.
236 236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
237 237 task_id = get_task_id(task)
238 238 except formencode.Invalid as errors:
239 239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
240 240 self._get_template_context(c), self.request)
241 241 html = formencode.htmlfill.render(
242 242 data,
243 243 defaults=errors.value,
244 244 errors=errors.error_dict or {},
245 245 prefix_error=False,
246 246 encoding="UTF-8",
247 247 force_defaults=False
248 248 )
249 249 return Response(html)
250 250
251 251 except Exception as e:
252 252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
253 253 h.flash(msg, category='error')
254 254 raise HTTPFound(h.route_path('home'))
255 255
256 256 repo_name = form_result.get('repo_name_full')
257 257
258 258 affected_user_ids = [self._rhodecode_user.user_id]
259 259 if copy_permissions:
260 260 # permission flush is done in repo creating
261 261 pass
262 262 PermissionModel().trigger_permission_flush(affected_user_ids)
263 263
264 264 raise HTTPFound(
265 265 h.route_path('repo_creating', repo_name=repo_name,
266 266 _query=dict(task_id=task_id)))
@@ -1,269 +1,268 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 35 from rhodecode.lib import helpers as h, audit_logger
36 36 from rhodecode.lib.utils2 import safe_unicode
37 37
38 38 from rhodecode.model.forms import UserGroupForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.scm import UserGroupList
41 41 from rhodecode.model.db import (
42 42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.user_group import UserGroupModel
45 45 from rhodecode.model.db import true
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 PermissionModel().set_global_permission_choices(
56 56 c, gettext_translator=self.request.translate)
57 57
58 58 return c
59 59
60 60 # permission check in data loading of
61 61 # `user_groups_list_data` via UserGroupList
62 62 @LoginRequired()
63 63 @NotAnonymous()
64 64 @view_config(
65 65 route_name='user_groups', request_method='GET',
66 66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 67 def user_groups_list(self):
68 68 c = self.load_default_context()
69 69 return self._get_template_context(c)
70 70
71 71 # permission check inside
72 72 @LoginRequired()
73 73 @NotAnonymous()
74 74 @view_config(
75 75 route_name='user_groups_data', request_method='GET',
76 76 renderer='json_ext', xhr=True)
77 77 def user_groups_list_data(self):
78 78 self.load_default_context()
79 79 column_map = {
80 80 'active': 'users_group_active',
81 81 'description': 'user_group_description',
82 82 'members': 'members_total',
83 83 'owner': 'user_username',
84 84 'sync': 'group_data'
85 85 }
86 86 draw, start, limit = self._extract_chunk(self.request)
87 87 search_q, order_by, order_dir = self._extract_ordering(
88 88 self.request, column_map=column_map)
89 89
90 90 _render = self.request.get_partial_renderer(
91 91 'rhodecode:templates/data_table/_dt_elements.mako')
92 92
93 93 def user_group_name(user_group_name):
94 94 return _render("user_group_name", user_group_name)
95 95
96 96 def user_group_actions(user_group_id, user_group_name):
97 97 return _render("user_group_actions", user_group_id, user_group_name)
98 98
99 99 def user_profile(username):
100 100 return _render('user_profile', username)
101 101
102 102 _perms = ['usergroup.admin']
103 103 allowed_ids = [-1] + self._rhodecode_user.user_group_acl_ids_from_stack(_perms)
104 104
105 105 user_groups_data_total_count = UserGroup.query()\
106 106 .filter(or_(
107 107 # generate multiple IN to fix limitation problems
108 108 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
109 109 ))\
110 110 .count()
111 111
112 112 user_groups_data_total_inactive_count = UserGroup.query()\
113 113 .filter(or_(
114 114 # generate multiple IN to fix limitation problems
115 115 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
116 116 ))\
117 117 .filter(UserGroup.users_group_active != true()).count()
118 118
119 119 member_count = count(UserGroupMember.user_id)
120 120 base_q = Session.query(
121 121 UserGroup.users_group_name,
122 122 UserGroup.user_group_description,
123 123 UserGroup.users_group_active,
124 124 UserGroup.users_group_id,
125 125 UserGroup.group_data,
126 126 User,
127 127 member_count.label('member_count')
128 128 ) \
129 129 .filter(or_(
130 130 # generate multiple IN to fix limitation problems
131 131 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
132 132 )) \
133 133 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
134 134 .join(User, User.user_id == UserGroup.user_id) \
135 135 .group_by(UserGroup, User)
136 136
137 137 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
138 138
139 139 if search_q:
140 140 like_expression = u'%{}%'.format(safe_unicode(search_q))
141 141 base_q = base_q.filter(or_(
142 142 UserGroup.users_group_name.ilike(like_expression),
143 143 ))
144 144 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
145 145
146 146 user_groups_data_total_filtered_count = base_q.count()
147 147 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
148 148
149 149 sort_defined = False
150 150 if order_by == 'members_total':
151 151 sort_col = member_count
152 152 sort_defined = True
153 153 elif order_by == 'user_username':
154 154 sort_col = User.username
155 155 else:
156 156 sort_col = getattr(UserGroup, order_by, None)
157 157
158 158 if sort_defined or sort_col:
159 159 if order_dir == 'asc':
160 160 sort_col = sort_col.asc()
161 161 else:
162 162 sort_col = sort_col.desc()
163 163
164 164 base_q = base_q.order_by(sort_col)
165 165 base_q = base_q.offset(start).limit(limit)
166 166
167 167 # authenticated access to user groups
168 168 auth_user_group_list = base_q.all()
169 169
170 170 user_groups_data = []
171 171 for user_gr in auth_user_group_list:
172 172 row = {
173 173 "users_group_name": user_group_name(user_gr.users_group_name),
174 "name_raw": h.escape(user_gr.users_group_name),
175 174 "description": h.escape(user_gr.user_group_description),
176 175 "members": user_gr.member_count,
177 176 # NOTE(marcink): because of advanced query we
178 177 # need to load it like that
179 178 "sync": UserGroup._load_sync(
180 179 UserGroup._load_group_data(user_gr.group_data)),
181 180 "active": h.bool2icon(user_gr.users_group_active),
182 181 "owner": user_profile(user_gr.User.username),
183 182 "action": user_group_actions(
184 183 user_gr.users_group_id, user_gr.users_group_name)
185 184 }
186 185 user_groups_data.append(row)
187 186
188 187 data = ({
189 188 'draw': draw,
190 189 'data': user_groups_data,
191 190 'recordsTotal': user_groups_data_total_count,
192 191 'recordsTotalInactive': user_groups_data_total_inactive_count,
193 192 'recordsFiltered': user_groups_data_total_filtered_count,
194 193 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
195 194 })
196 195
197 196 return data
198 197
199 198 @LoginRequired()
200 199 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
201 200 @view_config(
202 201 route_name='user_groups_new', request_method='GET',
203 202 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
204 203 def user_groups_new(self):
205 204 c = self.load_default_context()
206 205 return self._get_template_context(c)
207 206
208 207 @LoginRequired()
209 208 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
210 209 @CSRFRequired()
211 210 @view_config(
212 211 route_name='user_groups_create', request_method='POST',
213 212 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
214 213 def user_groups_create(self):
215 214 _ = self.request.translate
216 215 c = self.load_default_context()
217 216 users_group_form = UserGroupForm(self.request.translate)()
218 217
219 218 user_group_name = self.request.POST.get('users_group_name')
220 219 try:
221 220 form_result = users_group_form.to_python(dict(self.request.POST))
222 221 user_group = UserGroupModel().create(
223 222 name=form_result['users_group_name'],
224 223 description=form_result['user_group_description'],
225 224 owner=self._rhodecode_user.user_id,
226 225 active=form_result['users_group_active'])
227 226 Session().flush()
228 227 creation_data = user_group.get_api_data()
229 228 user_group_name = form_result['users_group_name']
230 229
231 230 audit_logger.store_web(
232 231 'user_group.create', action_data={'data': creation_data},
233 232 user=self._rhodecode_user)
234 233
235 234 user_group_link = h.link_to(
236 235 h.escape(user_group_name),
237 236 h.route_path(
238 237 'edit_user_group', user_group_id=user_group.users_group_id))
239 238 h.flash(h.literal(_('Created user group %(user_group_link)s')
240 239 % {'user_group_link': user_group_link}),
241 240 category='success')
242 241 Session().commit()
243 242 user_group_id = user_group.users_group_id
244 243 except formencode.Invalid as errors:
245 244
246 245 data = render(
247 246 'rhodecode:templates/admin/user_groups/user_group_add.mako',
248 247 self._get_template_context(c), self.request)
249 248 html = formencode.htmlfill.render(
250 249 data,
251 250 defaults=errors.value,
252 251 errors=errors.error_dict or {},
253 252 prefix_error=False,
254 253 encoding="UTF-8",
255 254 force_defaults=False
256 255 )
257 256 return Response(html)
258 257
259 258 except Exception:
260 259 log.exception("Exception creating user group")
261 260 h.flash(_('Error occurred during creation of user group %s') \
262 261 % user_group_name, category='error')
263 262 raise HTTPFound(h.route_path('user_groups_new'))
264 263
265 264 affected_user_ids = [self._rhodecode_user.user_id]
266 265 PermissionModel().trigger_permission_flush(affected_user_ids)
267 266
268 267 raise HTTPFound(
269 268 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,823 +1,823 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 re
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound
26 26 from pyramid.view import view_config
27 27
28 28 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
32 32 HasRepoGroupPermissionAny)
33 33 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
34 34 from rhodecode.lib.index import searcher_from_config
35 35 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
36 36 from rhodecode.lib.vcs.nodes import FileNode
37 37 from rhodecode.model.db import (
38 38 func, true, or_, case, in_filter_generator, Session,
39 39 Repository, RepoGroup, User, UserGroup)
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.repo_group import RepoGroupModel
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.user_group import UserGroupModel
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class HomeView(BaseAppView, DataGridAppView):
49 49
50 50 def load_default_context(self):
51 51 c = self._get_local_tmpl_context()
52 52 c.user = c.auth_user.get_instance()
53 53
54 54 return c
55 55
56 56 @LoginRequired()
57 57 @view_config(
58 58 route_name='user_autocomplete_data', request_method='GET',
59 59 renderer='json_ext', xhr=True)
60 60 def user_autocomplete_data(self):
61 61 self.load_default_context()
62 62 query = self.request.GET.get('query')
63 63 active = str2bool(self.request.GET.get('active') or True)
64 64 include_groups = str2bool(self.request.GET.get('user_groups'))
65 65 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
66 66 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
67 67
68 68 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
69 69 query, active, include_groups)
70 70
71 71 _users = UserModel().get_users(
72 72 name_contains=query, only_active=active)
73 73
74 74 def maybe_skip_default_user(usr):
75 75 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
76 76 return False
77 77 return True
78 78 _users = filter(maybe_skip_default_user, _users)
79 79
80 80 if include_groups:
81 81 # extend with user groups
82 82 _user_groups = UserGroupModel().get_user_groups(
83 83 name_contains=query, only_active=active,
84 84 expand_groups=expand_groups)
85 85 _users = _users + _user_groups
86 86
87 87 return {'suggestions': _users}
88 88
89 89 @LoginRequired()
90 90 @NotAnonymous()
91 91 @view_config(
92 92 route_name='user_group_autocomplete_data', request_method='GET',
93 93 renderer='json_ext', xhr=True)
94 94 def user_group_autocomplete_data(self):
95 95 self.load_default_context()
96 96 query = self.request.GET.get('query')
97 97 active = str2bool(self.request.GET.get('active') or True)
98 98 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
99 99
100 100 log.debug('generating user group list, query:%s, active:%s',
101 101 query, active)
102 102
103 103 _user_groups = UserGroupModel().get_user_groups(
104 104 name_contains=query, only_active=active,
105 105 expand_groups=expand_groups)
106 106 _user_groups = _user_groups
107 107
108 108 return {'suggestions': _user_groups}
109 109
110 110 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
111 111 org_query = name_contains
112 112 allowed_ids = self._rhodecode_user.repo_acl_ids(
113 113 ['repository.read', 'repository.write', 'repository.admin'],
114 114 cache=False, name_filter=name_contains) or [-1]
115 115
116 116 query = Repository.query()\
117 117 .filter(Repository.archived.isnot(true()))\
118 118 .filter(or_(
119 119 # generate multiple IN to fix limitation problems
120 120 *in_filter_generator(Repository.repo_id, allowed_ids)
121 121 ))
122 122
123 123 query = query.order_by(case(
124 124 [
125 125 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
126 126 ],
127 127 ))
128 128 query = query.order_by(func.length(Repository.repo_name))
129 129 query = query.order_by(Repository.repo_name)
130 130
131 131 if repo_type:
132 132 query = query.filter(Repository.repo_type == repo_type)
133 133
134 134 if name_contains:
135 135 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
136 136 query = query.filter(
137 137 Repository.repo_name.ilike(ilike_expression))
138 138 query = query.limit(limit)
139 139
140 140 acl_iter = query
141 141
142 142 return [
143 143 {
144 144 'id': obj.repo_name,
145 145 'value': org_query,
146 146 'value_display': obj.repo_name,
147 147 'text': obj.repo_name,
148 148 'type': 'repo',
149 149 'repo_id': obj.repo_id,
150 150 'repo_type': obj.repo_type,
151 151 'private': obj.private,
152 152 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
153 153 }
154 154 for obj in acl_iter]
155 155
156 156 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
157 157 org_query = name_contains
158 158 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
159 159 ['group.read', 'group.write', 'group.admin'],
160 160 cache=False, name_filter=name_contains) or [-1]
161 161
162 162 query = RepoGroup.query()\
163 163 .filter(or_(
164 164 # generate multiple IN to fix limitation problems
165 165 *in_filter_generator(RepoGroup.group_id, allowed_ids)
166 166 ))
167 167
168 168 query = query.order_by(case(
169 169 [
170 170 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
171 171 ],
172 172 ))
173 173 query = query.order_by(func.length(RepoGroup.group_name))
174 174 query = query.order_by(RepoGroup.group_name)
175 175
176 176 if name_contains:
177 177 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
178 178 query = query.filter(
179 179 RepoGroup.group_name.ilike(ilike_expression))
180 180 query = query.limit(limit)
181 181
182 182 acl_iter = query
183 183
184 184 return [
185 185 {
186 186 'id': obj.group_name,
187 187 'value': org_query,
188 188 'value_display': obj.group_name,
189 189 'text': obj.group_name,
190 190 'type': 'repo_group',
191 191 'repo_group_id': obj.group_id,
192 192 'url': h.route_path(
193 193 'repo_group_home', repo_group_name=obj.group_name)
194 194 }
195 195 for obj in acl_iter]
196 196
197 197 def _get_user_list(self, name_contains=None, limit=20):
198 198 org_query = name_contains
199 199 if not name_contains:
200 200 return [], False
201 201
202 202 # TODO(marcink): should all logged in users be allowed to search others?
203 203 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
204 204 if not allowed_user_search:
205 205 return [], False
206 206
207 207 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
208 208 if len(name_contains) != 1:
209 209 return [], False
210 210
211 211 name_contains = name_contains[0]
212 212
213 213 query = User.query()\
214 214 .order_by(func.length(User.username))\
215 215 .order_by(User.username) \
216 216 .filter(User.username != User.DEFAULT_USER)
217 217
218 218 if name_contains:
219 219 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
220 220 query = query.filter(
221 221 User.username.ilike(ilike_expression))
222 222 query = query.limit(limit)
223 223
224 224 acl_iter = query
225 225
226 226 return [
227 227 {
228 228 'id': obj.user_id,
229 229 'value': org_query,
230 230 'value_display': 'user: `{}`'.format(obj.username),
231 231 'type': 'user',
232 232 'icon_link': h.gravatar_url(obj.email, 30),
233 233 'url': h.route_path(
234 234 'user_profile', username=obj.username)
235 235 }
236 236 for obj in acl_iter], True
237 237
238 238 def _get_user_groups_list(self, name_contains=None, limit=20):
239 239 org_query = name_contains
240 240 if not name_contains:
241 241 return [], False
242 242
243 243 # TODO(marcink): should all logged in users be allowed to search others?
244 244 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
245 245 if not allowed_user_search:
246 246 return [], False
247 247
248 248 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
249 249 if len(name_contains) != 1:
250 250 return [], False
251 251
252 252 name_contains = name_contains[0]
253 253
254 254 query = UserGroup.query()\
255 255 .order_by(func.length(UserGroup.users_group_name))\
256 256 .order_by(UserGroup.users_group_name)
257 257
258 258 if name_contains:
259 259 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
260 260 query = query.filter(
261 261 UserGroup.users_group_name.ilike(ilike_expression))
262 262 query = query.limit(limit)
263 263
264 264 acl_iter = query
265 265
266 266 return [
267 267 {
268 268 'id': obj.users_group_id,
269 269 'value': org_query,
270 270 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
271 271 'type': 'user_group',
272 272 'url': h.route_path(
273 273 'user_group_profile', user_group_name=obj.users_group_name)
274 274 }
275 275 for obj in acl_iter], True
276 276
277 277 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
278 278 repo_name = repo_group_name = None
279 279 if repo:
280 280 repo_name = repo.repo_name
281 281 if repo_group:
282 282 repo_group_name = repo_group.group_name
283 283
284 284 org_query = query
285 285 if not query or len(query) < 3 or not searcher:
286 286 return [], False
287 287
288 288 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
289 289
290 290 if len(commit_hashes) != 1:
291 291 return [], False
292 292
293 293 commit_hash = commit_hashes[0]
294 294
295 295 result = searcher.search(
296 296 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
297 297 repo_name, repo_group_name, raise_on_exc=False)
298 298
299 299 commits = []
300 300 for entry in result['results']:
301 301 repo_data = {
302 302 'repository_id': entry.get('repository_id'),
303 303 'repository_type': entry.get('repo_type'),
304 304 'repository_name': entry.get('repository'),
305 305 }
306 306
307 307 commit_entry = {
308 308 'id': entry['commit_id'],
309 309 'value': org_query,
310 310 'value_display': '`{}` commit: {}'.format(
311 311 entry['repository'], entry['commit_id']),
312 312 'type': 'commit',
313 313 'repo': entry['repository'],
314 314 'repo_data': repo_data,
315 315
316 316 'url': h.route_path(
317 317 'repo_commit',
318 318 repo_name=entry['repository'], commit_id=entry['commit_id'])
319 319 }
320 320
321 321 commits.append(commit_entry)
322 322 return commits, True
323 323
324 324 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
325 325 repo_name = repo_group_name = None
326 326 if repo:
327 327 repo_name = repo.repo_name
328 328 if repo_group:
329 329 repo_group_name = repo_group.group_name
330 330
331 331 org_query = query
332 332 if not query or len(query) < 3 or not searcher:
333 333 return [], False
334 334
335 335 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
336 336 if len(paths_re) != 1:
337 337 return [], False
338 338
339 339 file_path = paths_re[0]
340 340
341 341 search_path = searcher.escape_specials(file_path)
342 342 result = searcher.search(
343 343 'file.raw:*{}*'.format(search_path), 'path', auth_user,
344 344 repo_name, repo_group_name, raise_on_exc=False)
345 345
346 346 files = []
347 347 for entry in result['results']:
348 348 repo_data = {
349 349 'repository_id': entry.get('repository_id'),
350 350 'repository_type': entry.get('repo_type'),
351 351 'repository_name': entry.get('repository'),
352 352 }
353 353
354 354 file_entry = {
355 355 'id': entry['commit_id'],
356 356 'value': org_query,
357 357 'value_display': '`{}` file: {}'.format(
358 358 entry['repository'], entry['file']),
359 359 'type': 'file',
360 360 'repo': entry['repository'],
361 361 'repo_data': repo_data,
362 362
363 363 'url': h.route_path(
364 364 'repo_files',
365 365 repo_name=entry['repository'], commit_id=entry['commit_id'],
366 366 f_path=entry['file'])
367 367 }
368 368
369 369 files.append(file_entry)
370 370 return files, True
371 371
372 372 @LoginRequired()
373 373 @view_config(
374 374 route_name='repo_list_data', request_method='GET',
375 375 renderer='json_ext', xhr=True)
376 376 def repo_list_data(self):
377 377 _ = self.request.translate
378 378 self.load_default_context()
379 379
380 380 query = self.request.GET.get('query')
381 381 repo_type = self.request.GET.get('repo_type')
382 382 log.debug('generating repo list, query:%s, repo_type:%s',
383 383 query, repo_type)
384 384
385 385 res = []
386 386 repos = self._get_repo_list(query, repo_type=repo_type)
387 387 if repos:
388 388 res.append({
389 389 'text': _('Repositories'),
390 390 'children': repos
391 391 })
392 392
393 393 data = {
394 394 'more': False,
395 395 'results': res
396 396 }
397 397 return data
398 398
399 399 @LoginRequired()
400 400 @view_config(
401 401 route_name='repo_group_list_data', request_method='GET',
402 402 renderer='json_ext', xhr=True)
403 403 def repo_group_list_data(self):
404 404 _ = self.request.translate
405 405 self.load_default_context()
406 406
407 407 query = self.request.GET.get('query')
408 408
409 409 log.debug('generating repo group list, query:%s',
410 410 query)
411 411
412 412 res = []
413 413 repo_groups = self._get_repo_group_list(query)
414 414 if repo_groups:
415 415 res.append({
416 416 'text': _('Repository Groups'),
417 417 'children': repo_groups
418 418 })
419 419
420 420 data = {
421 421 'more': False,
422 422 'results': res
423 423 }
424 424 return data
425 425
426 426 def _get_default_search_queries(self, search_context, searcher, query):
427 427 if not searcher:
428 428 return []
429 429
430 430 is_es_6 = searcher.is_es_6
431 431
432 432 queries = []
433 433 repo_group_name, repo_name, repo_context = None, None, None
434 434
435 435 # repo group context
436 436 if search_context.get('search_context[repo_group_name]'):
437 437 repo_group_name = search_context.get('search_context[repo_group_name]')
438 438 if search_context.get('search_context[repo_name]'):
439 439 repo_name = search_context.get('search_context[repo_name]')
440 440 repo_context = search_context.get('search_context[repo_view_type]')
441 441
442 442 if is_es_6 and repo_name:
443 443 # files
444 444 def query_modifier():
445 445 qry = query
446 446 return {'q': qry, 'type': 'content'}
447 447
448 448 label = u'File search for `{}`'.format(h.escape(query))
449 449 file_qry = {
450 450 'id': -10,
451 451 'value': query,
452 452 'value_display': label,
453 453 'value_icon': '<i class="icon-code"></i>',
454 454 'type': 'search',
455 455 'subtype': 'repo',
456 456 'url': h.route_path('search_repo',
457 457 repo_name=repo_name,
458 458 _query=query_modifier())
459 459 }
460 460
461 461 # commits
462 462 def query_modifier():
463 463 qry = query
464 464 return {'q': qry, 'type': 'commit'}
465 465
466 466 label = u'Commit search for `{}`'.format(h.escape(query))
467 467 commit_qry = {
468 468 'id': -20,
469 469 'value': query,
470 470 'value_display': label,
471 471 'value_icon': '<i class="icon-history"></i>',
472 472 'type': 'search',
473 473 'subtype': 'repo',
474 474 'url': h.route_path('search_repo',
475 475 repo_name=repo_name,
476 476 _query=query_modifier())
477 477 }
478 478
479 479 if repo_context in ['commit', 'commits']:
480 480 queries.extend([commit_qry, file_qry])
481 481 elif repo_context in ['files', 'summary']:
482 482 queries.extend([file_qry, commit_qry])
483 483 else:
484 484 queries.extend([commit_qry, file_qry])
485 485
486 486 elif is_es_6 and repo_group_name:
487 487 # files
488 488 def query_modifier():
489 489 qry = query
490 490 return {'q': qry, 'type': 'content'}
491 491
492 492 label = u'File search for `{}`'.format(query)
493 493 file_qry = {
494 494 'id': -30,
495 495 'value': query,
496 496 'value_display': label,
497 497 'value_icon': '<i class="icon-code"></i>',
498 498 'type': 'search',
499 499 'subtype': 'repo_group',
500 500 'url': h.route_path('search_repo_group',
501 501 repo_group_name=repo_group_name,
502 502 _query=query_modifier())
503 503 }
504 504
505 505 # commits
506 506 def query_modifier():
507 507 qry = query
508 508 return {'q': qry, 'type': 'commit'}
509 509
510 510 label = u'Commit search for `{}`'.format(query)
511 511 commit_qry = {
512 512 'id': -40,
513 513 'value': query,
514 514 'value_display': label,
515 515 'value_icon': '<i class="icon-history"></i>',
516 516 'type': 'search',
517 517 'subtype': 'repo_group',
518 518 'url': h.route_path('search_repo_group',
519 519 repo_group_name=repo_group_name,
520 520 _query=query_modifier())
521 521 }
522 522
523 523 if repo_context in ['commit', 'commits']:
524 524 queries.extend([commit_qry, file_qry])
525 525 elif repo_context in ['files', 'summary']:
526 526 queries.extend([file_qry, commit_qry])
527 527 else:
528 528 queries.extend([commit_qry, file_qry])
529 529
530 530 # Global, not scoped
531 531 if not queries:
532 532 queries.append(
533 533 {
534 534 'id': -1,
535 535 'value': query,
536 536 'value_display': u'File search for: `{}`'.format(query),
537 537 'value_icon': '<i class="icon-code"></i>',
538 538 'type': 'search',
539 539 'subtype': 'global',
540 540 'url': h.route_path('search',
541 541 _query={'q': query, 'type': 'content'})
542 542 })
543 543 queries.append(
544 544 {
545 545 'id': -2,
546 546 'value': query,
547 547 'value_display': u'Commit search for: `{}`'.format(query),
548 548 'value_icon': '<i class="icon-history"></i>',
549 549 'type': 'search',
550 550 'subtype': 'global',
551 551 'url': h.route_path('search',
552 552 _query={'q': query, 'type': 'commit'})
553 553 })
554 554
555 555 return queries
556 556
557 557 @LoginRequired()
558 558 @view_config(
559 559 route_name='goto_switcher_data', request_method='GET',
560 560 renderer='json_ext', xhr=True)
561 561 def goto_switcher_data(self):
562 562 c = self.load_default_context()
563 563
564 564 _ = self.request.translate
565 565
566 566 query = self.request.GET.get('query')
567 567 log.debug('generating main filter data, query %s', query)
568 568
569 569 res = []
570 570 if not query:
571 571 return {'suggestions': res}
572 572
573 573 def no_match(name):
574 574 return {
575 575 'id': -1,
576 576 'value': "",
577 577 'value_display': name,
578 578 'type': 'text',
579 579 'url': ""
580 580 }
581 581 searcher = searcher_from_config(self.request.registry.settings)
582 582 has_specialized_search = False
583 583
584 584 # set repo context
585 585 repo = None
586 586 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
587 587 if repo_id:
588 588 repo = Repository.get(repo_id)
589 589
590 590 # set group context
591 591 repo_group = None
592 592 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
593 593 if repo_group_id:
594 594 repo_group = RepoGroup.get(repo_group_id)
595 595 prefix_match = False
596 596
597 597 # user: type search
598 598 if not prefix_match:
599 599 users, prefix_match = self._get_user_list(query)
600 600 if users:
601 601 has_specialized_search = True
602 602 for serialized_user in users:
603 603 res.append(serialized_user)
604 604 elif prefix_match:
605 605 has_specialized_search = True
606 606 res.append(no_match('No matching users found'))
607 607
608 608 # user_group: type search
609 609 if not prefix_match:
610 610 user_groups, prefix_match = self._get_user_groups_list(query)
611 611 if user_groups:
612 612 has_specialized_search = True
613 613 for serialized_user_group in user_groups:
614 614 res.append(serialized_user_group)
615 615 elif prefix_match:
616 616 has_specialized_search = True
617 617 res.append(no_match('No matching user groups found'))
618 618
619 619 # FTS commit: type search
620 620 if not prefix_match:
621 621 commits, prefix_match = self._get_hash_commit_list(
622 622 c.auth_user, searcher, query, repo, repo_group)
623 623 if commits:
624 624 has_specialized_search = True
625 625 unique_repos = collections.OrderedDict()
626 626 for commit in commits:
627 627 repo_name = commit['repo']
628 628 unique_repos.setdefault(repo_name, []).append(commit)
629 629
630 630 for _repo, commits in unique_repos.items():
631 631 for commit in commits:
632 632 res.append(commit)
633 633 elif prefix_match:
634 634 has_specialized_search = True
635 635 res.append(no_match('No matching commits found'))
636 636
637 637 # FTS file: type search
638 638 if not prefix_match:
639 639 paths, prefix_match = self._get_path_list(
640 640 c.auth_user, searcher, query, repo, repo_group)
641 641 if paths:
642 642 has_specialized_search = True
643 643 unique_repos = collections.OrderedDict()
644 644 for path in paths:
645 645 repo_name = path['repo']
646 646 unique_repos.setdefault(repo_name, []).append(path)
647 647
648 648 for repo, paths in unique_repos.items():
649 649 for path in paths:
650 650 res.append(path)
651 651 elif prefix_match:
652 652 has_specialized_search = True
653 653 res.append(no_match('No matching files found'))
654 654
655 655 # main suggestions
656 656 if not has_specialized_search:
657 657 repo_group_name = ''
658 658 if repo_group:
659 659 repo_group_name = repo_group.group_name
660 660
661 661 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
662 662 res.append(_q)
663 663
664 664 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
665 665 for serialized_repo_group in repo_groups:
666 666 res.append(serialized_repo_group)
667 667
668 668 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
669 669 for serialized_repo in repos:
670 670 res.append(serialized_repo)
671 671
672 672 if not repos and not repo_groups:
673 673 res.append(no_match('No matches found'))
674 674
675 675 return {'suggestions': res}
676 676
677 677 @LoginRequired()
678 678 @view_config(
679 679 route_name='home', request_method='GET',
680 680 renderer='rhodecode:templates/index.mako')
681 681 def main_page(self):
682 682 c = self.load_default_context()
683 683 c.repo_group = None
684 684 return self._get_template_context(c)
685 685
686 686 def _main_page_repo_groups_data(self, repo_group_id):
687 687 column_map = {
688 'name_raw': 'group_name_hash',
688 'name': 'group_name_hash',
689 689 'desc': 'group_description',
690 'last_change_raw': 'updated_on',
690 'last_change': 'updated_on',
691 691 'owner': 'user_username',
692 692 }
693 693 draw, start, limit = self._extract_chunk(self.request)
694 694 search_q, order_by, order_dir = self._extract_ordering(
695 695 self.request, column_map=column_map)
696 696 return RepoGroupModel().get_repo_groups_data_table(
697 697 draw, start, limit,
698 698 search_q, order_by, order_dir,
699 699 self._rhodecode_user, repo_group_id)
700 700
701 701 def _main_page_repos_data(self, repo_group_id):
702 702 column_map = {
703 'name_raw': 'repo_name',
703 'name': 'repo_name',
704 704 'desc': 'description',
705 'last_change_raw': 'updated_on',
705 'last_change': 'updated_on',
706 706 'owner': 'user_username',
707 707 }
708 708 draw, start, limit = self._extract_chunk(self.request)
709 709 search_q, order_by, order_dir = self._extract_ordering(
710 710 self.request, column_map=column_map)
711 711 return RepoModel().get_repos_data_table(
712 712 draw, start, limit,
713 713 search_q, order_by, order_dir,
714 714 self._rhodecode_user, repo_group_id)
715 715
716 716 @LoginRequired()
717 717 @view_config(
718 718 route_name='main_page_repo_groups_data',
719 719 request_method='GET', renderer='json_ext', xhr=True)
720 720 def main_page_repo_groups_data(self):
721 721 self.load_default_context()
722 722 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
723 723
724 724 if repo_group_id:
725 725 group = RepoGroup.get_or_404(repo_group_id)
726 726 _perms = ['group.read', 'group.write', 'group.admin']
727 727 if not HasRepoGroupPermissionAny(*_perms)(
728 728 group.group_name, 'user is allowed to list repo group children'):
729 729 raise HTTPNotFound()
730 730
731 731 return self._main_page_repo_groups_data(repo_group_id)
732 732
733 733 @LoginRequired()
734 734 @view_config(
735 735 route_name='main_page_repos_data',
736 736 request_method='GET', renderer='json_ext', xhr=True)
737 737 def main_page_repos_data(self):
738 738 self.load_default_context()
739 739 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
740 740
741 741 if repo_group_id:
742 742 group = RepoGroup.get_or_404(repo_group_id)
743 743 _perms = ['group.read', 'group.write', 'group.admin']
744 744 if not HasRepoGroupPermissionAny(*_perms)(
745 745 group.group_name, 'user is allowed to list repo group children'):
746 746 raise HTTPNotFound()
747 747
748 748 return self._main_page_repos_data(repo_group_id)
749 749
750 750 @LoginRequired()
751 751 @HasRepoGroupPermissionAnyDecorator(
752 752 'group.read', 'group.write', 'group.admin')
753 753 @view_config(
754 754 route_name='repo_group_home', request_method='GET',
755 755 renderer='rhodecode:templates/index_repo_group.mako')
756 756 @view_config(
757 757 route_name='repo_group_home_slash', request_method='GET',
758 758 renderer='rhodecode:templates/index_repo_group.mako')
759 759 def repo_group_main_page(self):
760 760 c = self.load_default_context()
761 761 c.repo_group = self.request.db_repo_group
762 762 return self._get_template_context(c)
763 763
764 764 @LoginRequired()
765 765 @CSRFRequired()
766 766 @view_config(
767 767 route_name='markup_preview', request_method='POST',
768 768 renderer='string', xhr=True)
769 769 def markup_preview(self):
770 770 # Technically a CSRF token is not needed as no state changes with this
771 771 # call. However, as this is a POST is better to have it, so automated
772 772 # tools don't flag it as potential CSRF.
773 773 # Post is required because the payload could be bigger than the maximum
774 774 # allowed by GET.
775 775
776 776 text = self.request.POST.get('text')
777 777 renderer = self.request.POST.get('renderer') or 'rst'
778 778 if text:
779 779 return h.render(text, renderer=renderer, mentions=True)
780 780 return ''
781 781
782 782 @LoginRequired()
783 783 @CSRFRequired()
784 784 @view_config(
785 785 route_name='file_preview', request_method='POST',
786 786 renderer='string', xhr=True)
787 787 def file_preview(self):
788 788 # Technically a CSRF token is not needed as no state changes with this
789 789 # call. However, as this is a POST is better to have it, so automated
790 790 # tools don't flag it as potential CSRF.
791 791 # Post is required because the payload could be bigger than the maximum
792 792 # allowed by GET.
793 793
794 794 text = self.request.POST.get('text')
795 795 file_path = self.request.POST.get('file_path')
796 796
797 797 renderer = h.renderer_from_filename(file_path)
798 798
799 799 if renderer:
800 800 return h.render(text, renderer=renderer, mentions=True)
801 801 else:
802 802 self.load_default_context()
803 803 _render = self.request.get_partial_renderer(
804 804 'rhodecode:templates/files/file_content.mako')
805 805
806 806 lines = filenode_as_lines_tokens(FileNode(file_path, text))
807 807
808 808 return _render('render_lines', lines)
809 809
810 810 @LoginRequired()
811 811 @CSRFRequired()
812 812 @view_config(
813 813 route_name='store_user_session_value', request_method='POST',
814 814 renderer='string', xhr=True)
815 815 def store_user_session_attr(self):
816 816 key = self.request.POST.get('key')
817 817 val = self.request.POST.get('val')
818 818
819 819 existing_value = self.request.session.get(key)
820 820 if existing_value != val:
821 821 self.request.session[key] = val
822 822
823 823 return 'stored:{}:{}'.format(key, val)
@@ -1,1160 +1,1158 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 import re
23 23 import shutil
24 24 import time
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from zope.cachedescriptors.property import Lazy as LazyProperty
31 31
32 32 from rhodecode import events
33 33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 34 from rhodecode.lib.caching_query import FromCache
35 35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 36 from rhodecode.lib.hooks_base import log_delete_repository
37 37 from rhodecode.lib.user_log_filter import user_log_filter
38 38 from rhodecode.lib.utils import make_db_config
39 39 from rhodecode.lib.utils2 import (
40 40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 42 action_logger_generic)
43 43 from rhodecode.lib.vcs.backends import get_backend
44 44 from rhodecode.model import BaseModel
45 45 from rhodecode.model.db import (
46 46 _hash_key, func, case, joinedload, or_, in_filter_generator,
47 47 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
48 48 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
49 49 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
50 50 from rhodecode.model.settings import VcsSettingsModel
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class RepoModel(BaseModel):
56 56
57 57 cls = Repository
58 58
59 59 def _get_user_group(self, users_group):
60 60 return self._get_instance(UserGroup, users_group,
61 61 callback=UserGroup.get_by_group_name)
62 62
63 63 def _get_repo_group(self, repo_group):
64 64 return self._get_instance(RepoGroup, repo_group,
65 65 callback=RepoGroup.get_by_group_name)
66 66
67 67 def _create_default_perms(self, repository, private):
68 68 # create default permission
69 69 default = 'repository.read'
70 70 def_user = User.get_default_user()
71 71 for p in def_user.user_perms:
72 72 if p.permission.permission_name.startswith('repository.'):
73 73 default = p.permission.permission_name
74 74 break
75 75
76 76 default_perm = 'repository.none' if private else default
77 77
78 78 repo_to_perm = UserRepoToPerm()
79 79 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 80
81 81 repo_to_perm.repository = repository
82 82 repo_to_perm.user_id = def_user.user_id
83 83
84 84 return repo_to_perm
85 85
86 86 @LazyProperty
87 87 def repos_path(self):
88 88 """
89 89 Gets the repositories root path from database
90 90 """
91 91 settings_model = VcsSettingsModel(sa=self.sa)
92 92 return settings_model.get_repos_location()
93 93
94 94 def get(self, repo_id):
95 95 repo = self.sa.query(Repository) \
96 96 .filter(Repository.repo_id == repo_id)
97 97
98 98 return repo.scalar()
99 99
100 100 def get_repo(self, repository):
101 101 return self._get_repo(repository)
102 102
103 103 def get_by_repo_name(self, repo_name, cache=False):
104 104 repo = self.sa.query(Repository) \
105 105 .filter(Repository.repo_name == repo_name)
106 106
107 107 if cache:
108 108 name_key = _hash_key(repo_name)
109 109 repo = repo.options(
110 110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
111 111 return repo.scalar()
112 112
113 113 def _extract_id_from_repo_name(self, repo_name):
114 114 if repo_name.startswith('/'):
115 115 repo_name = repo_name.lstrip('/')
116 116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 117 if by_id_match:
118 118 return by_id_match.groups()[0]
119 119
120 120 def get_repo_by_id(self, repo_name):
121 121 """
122 122 Extracts repo_name by id from special urls.
123 123 Example url is _11/repo_name
124 124
125 125 :param repo_name:
126 126 :return: repo object if matched else None
127 127 """
128 128
129 129 try:
130 130 _repo_id = self._extract_id_from_repo_name(repo_name)
131 131 if _repo_id:
132 132 return self.get(_repo_id)
133 133 except Exception:
134 134 log.exception('Failed to extract repo_name from URL')
135 135
136 136 return None
137 137
138 138 def get_repos_for_root(self, root, traverse=False):
139 139 if traverse:
140 140 like_expression = u'{}%'.format(safe_unicode(root))
141 141 repos = Repository.query().filter(
142 142 Repository.repo_name.like(like_expression)).all()
143 143 else:
144 144 if root and not isinstance(root, RepoGroup):
145 145 raise ValueError(
146 146 'Root must be an instance '
147 147 'of RepoGroup, got:{} instead'.format(type(root)))
148 148 repos = Repository.query().filter(Repository.group == root).all()
149 149 return repos
150 150
151 151 def get_url(self, repo, request=None, permalink=False):
152 152 if not request:
153 153 request = get_current_request()
154 154
155 155 if not request:
156 156 return
157 157
158 158 if permalink:
159 159 return request.route_url(
160 160 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
161 161 else:
162 162 return request.route_url(
163 163 'repo_summary', repo_name=safe_str(repo.repo_name))
164 164
165 165 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
166 166 if not request:
167 167 request = get_current_request()
168 168
169 169 if not request:
170 170 return
171 171
172 172 if permalink:
173 173 return request.route_url(
174 174 'repo_commit', repo_name=safe_str(repo.repo_id),
175 175 commit_id=commit_id)
176 176
177 177 else:
178 178 return request.route_url(
179 179 'repo_commit', repo_name=safe_str(repo.repo_name),
180 180 commit_id=commit_id)
181 181
182 182 def get_repo_log(self, repo, filter_term):
183 183 repo_log = UserLog.query()\
184 184 .filter(or_(UserLog.repository_id == repo.repo_id,
185 185 UserLog.repository_name == repo.repo_name))\
186 186 .options(joinedload(UserLog.user))\
187 187 .options(joinedload(UserLog.repository))\
188 188 .order_by(UserLog.action_date.desc())
189 189
190 190 repo_log = user_log_filter(repo_log, filter_term)
191 191 return repo_log
192 192
193 193 @classmethod
194 194 def update_commit_cache(cls, repositories=None):
195 195 if not repositories:
196 196 repositories = Repository.getAll()
197 197 for repo in repositories:
198 198 repo.update_commit_cache()
199 199
200 200 def get_repos_as_dict(self, repo_list=None, admin=False,
201 201 super_user_actions=False, short_name=None):
202 202 _render = get_current_request().get_partial_renderer(
203 203 'rhodecode:templates/data_table/_dt_elements.mako')
204 204 c = _render.get_call_context()
205 205
206 206 def quick_menu(repo_name):
207 207 return _render('quick_menu', repo_name)
208 208
209 209 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
210 210 if short_name is not None:
211 211 short_name_var = short_name
212 212 else:
213 213 short_name_var = not admin
214 214 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
215 215 short_name=short_name_var, admin=False)
216 216
217 217 def last_change(last_change):
218 218 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
219 219 ts = time.time()
220 220 utc_offset = (datetime.datetime.fromtimestamp(ts)
221 221 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
222 222 last_change = last_change + datetime.timedelta(seconds=utc_offset)
223 223
224 224 return _render("last_change", last_change)
225 225
226 226 def rss_lnk(repo_name):
227 227 return _render("rss", repo_name)
228 228
229 229 def atom_lnk(repo_name):
230 230 return _render("atom", repo_name)
231 231
232 232 def last_rev(repo_name, cs_cache):
233 233 return _render('revision', repo_name, cs_cache.get('revision'),
234 234 cs_cache.get('raw_id'), cs_cache.get('author'),
235 235 cs_cache.get('message'), cs_cache.get('date'))
236 236
237 237 def desc(desc):
238 238 return _render('repo_desc', desc, c.visual.stylify_metatags)
239 239
240 240 def state(repo_state):
241 241 return _render("repo_state", repo_state)
242 242
243 243 def repo_actions(repo_name):
244 244 return _render('repo_actions', repo_name, super_user_actions)
245 245
246 246 def user_profile(username):
247 247 return _render('user_profile', username)
248 248
249 249 repos_data = []
250 250 for repo in repo_list:
251 251 # NOTE(marcink): because we use only raw column we need to load it like that
252 252 changeset_cache = Repository._load_changeset_cache(
253 253 repo.repo_id, repo._changeset_cache)
254 last_commit_change = Repository._load_commit_change(changeset_cache)
255 254
256 255 row = {
257 256 "menu": quick_menu(repo.repo_name),
258 257
259 258 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
260 259 repo.private, repo.archived, repo.fork),
261 "name_raw": repo.repo_name.lower(),
260
262 261 "desc": desc(repo.description),
263 262
264 "last_change": last_change(last_commit_change),
265 "last_change_raw": datetime_to_time(last_commit_change),
263 "last_change": last_change(repo.updated_on),
266 264
267 265 "last_changeset": last_rev(repo.repo_name, changeset_cache),
268 266 "last_changeset_raw": changeset_cache.get('revision'),
269 267
270 268 "owner": user_profile(repo.User.username),
271 269
272 270 "state": state(repo.repo_state),
273 271 "rss": rss_lnk(repo.repo_name),
274 272 "atom": atom_lnk(repo.repo_name),
275 273 }
276 274 if admin:
277 275 row.update({
278 276 "action": repo_actions(repo.repo_name),
279 277 })
280 278 repos_data.append(row)
281 279
282 280 return repos_data
283 281
284 282 def get_repos_data_table(
285 283 self, draw, start, limit,
286 284 search_q, order_by, order_dir,
287 285 auth_user, repo_group_id):
288 286 from rhodecode.model.scm import RepoList
289 287
290 288 _perms = ['repository.read', 'repository.write', 'repository.admin']
291 289
292 290 repos = Repository.query() \
293 291 .filter(Repository.group_id == repo_group_id) \
294 292 .all()
295 293 auth_repo_list = RepoList(
296 294 repos, perm_set=_perms,
297 295 extra_kwargs=dict(user=auth_user))
298 296
299 297 allowed_ids = [-1]
300 298 for repo in auth_repo_list:
301 299 allowed_ids.append(repo.repo_id)
302 300
303 301 repos_data_total_count = Repository.query() \
304 302 .filter(Repository.group_id == repo_group_id) \
305 303 .filter(or_(
306 304 # generate multiple IN to fix limitation problems
307 305 *in_filter_generator(Repository.repo_id, allowed_ids))
308 306 ) \
309 307 .count()
310 308
311 309 base_q = Session.query(
312 310 Repository.repo_id,
313 311 Repository.repo_name,
314 312 Repository.description,
315 313 Repository.repo_type,
316 314 Repository.repo_state,
317 315 Repository.private,
318 316 Repository.archived,
319 317 Repository.fork,
320 318 Repository.updated_on,
321 319 Repository._changeset_cache,
322 320 User,
323 321 ) \
324 322 .filter(Repository.group_id == repo_group_id) \
325 323 .filter(or_(
326 324 # generate multiple IN to fix limitation problems
327 325 *in_filter_generator(Repository.repo_id, allowed_ids))
328 326 ) \
329 327 .join(User, User.user_id == Repository.user_id) \
330 328 .group_by(Repository, User)
331 329
332 330 repos_data_total_filtered_count = base_q.count()
333 331
334 332 sort_defined = False
335 333 if order_by == 'repo_name':
336 334 sort_col = func.lower(Repository.repo_name)
337 335 sort_defined = True
338 336 elif order_by == 'user_username':
339 337 sort_col = User.username
340 338 else:
341 339 sort_col = getattr(Repository, order_by, None)
342 340
343 341 if sort_defined or sort_col:
344 342 if order_dir == 'asc':
345 343 sort_col = sort_col.asc()
346 344 else:
347 345 sort_col = sort_col.desc()
348 346
349 347 base_q = base_q.order_by(sort_col)
350 348 base_q = base_q.offset(start).limit(limit)
351 349
352 350 repos_list = base_q.all()
353 351
354 352 repos_data = RepoModel().get_repos_as_dict(
355 353 repo_list=repos_list, admin=False)
356 354
357 355 data = ({
358 356 'draw': draw,
359 357 'data': repos_data,
360 358 'recordsTotal': repos_data_total_count,
361 359 'recordsFiltered': repos_data_total_filtered_count,
362 360 })
363 361 return data
364 362
365 363 def _get_defaults(self, repo_name):
366 364 """
367 365 Gets information about repository, and returns a dict for
368 366 usage in forms
369 367
370 368 :param repo_name:
371 369 """
372 370
373 371 repo_info = Repository.get_by_repo_name(repo_name)
374 372
375 373 if repo_info is None:
376 374 return None
377 375
378 376 defaults = repo_info.get_dict()
379 377 defaults['repo_name'] = repo_info.just_name
380 378
381 379 groups = repo_info.groups_with_parents
382 380 parent_group = groups[-1] if groups else None
383 381
384 382 # we use -1 as this is how in HTML, we mark an empty group
385 383 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
386 384
387 385 keys_to_process = (
388 386 {'k': 'repo_type', 'strip': False},
389 387 {'k': 'repo_enable_downloads', 'strip': True},
390 388 {'k': 'repo_description', 'strip': True},
391 389 {'k': 'repo_enable_locking', 'strip': True},
392 390 {'k': 'repo_landing_rev', 'strip': True},
393 391 {'k': 'clone_uri', 'strip': False},
394 392 {'k': 'push_uri', 'strip': False},
395 393 {'k': 'repo_private', 'strip': True},
396 394 {'k': 'repo_enable_statistics', 'strip': True}
397 395 )
398 396
399 397 for item in keys_to_process:
400 398 attr = item['k']
401 399 if item['strip']:
402 400 attr = remove_prefix(item['k'], 'repo_')
403 401
404 402 val = defaults[attr]
405 403 if item['k'] == 'repo_landing_rev':
406 404 val = ':'.join(defaults[attr])
407 405 defaults[item['k']] = val
408 406 if item['k'] == 'clone_uri':
409 407 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
410 408 if item['k'] == 'push_uri':
411 409 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
412 410
413 411 # fill owner
414 412 if repo_info.user:
415 413 defaults.update({'user': repo_info.user.username})
416 414 else:
417 415 replacement_user = User.get_first_super_admin().username
418 416 defaults.update({'user': replacement_user})
419 417
420 418 return defaults
421 419
422 420 def update(self, repo, **kwargs):
423 421 try:
424 422 cur_repo = self._get_repo(repo)
425 423 source_repo_name = cur_repo.repo_name
426 424 if 'user' in kwargs:
427 425 cur_repo.user = User.get_by_username(kwargs['user'])
428 426
429 427 if 'repo_group' in kwargs:
430 428 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
431 429 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
432 430
433 431 update_keys = [
434 432 (1, 'repo_description'),
435 433 (1, 'repo_landing_rev'),
436 434 (1, 'repo_private'),
437 435 (1, 'repo_enable_downloads'),
438 436 (1, 'repo_enable_locking'),
439 437 (1, 'repo_enable_statistics'),
440 438 (0, 'clone_uri'),
441 439 (0, 'push_uri'),
442 440 (0, 'fork_id')
443 441 ]
444 442 for strip, k in update_keys:
445 443 if k in kwargs:
446 444 val = kwargs[k]
447 445 if strip:
448 446 k = remove_prefix(k, 'repo_')
449 447
450 448 setattr(cur_repo, k, val)
451 449
452 450 new_name = cur_repo.get_new_name(kwargs['repo_name'])
453 451 cur_repo.repo_name = new_name
454 452
455 453 # if private flag is set, reset default permission to NONE
456 454 if kwargs.get('repo_private'):
457 455 EMPTY_PERM = 'repository.none'
458 456 RepoModel().grant_user_permission(
459 457 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
460 458 )
461 459
462 460 # handle extra fields
463 461 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
464 462 k = RepositoryField.un_prefix_key(field)
465 463 ex_field = RepositoryField.get_by_key_name(
466 464 key=k, repo=cur_repo)
467 465 if ex_field:
468 466 ex_field.field_value = kwargs[field]
469 467 self.sa.add(ex_field)
470 468
471 469 self.sa.add(cur_repo)
472 470
473 471 if source_repo_name != new_name:
474 472 # rename repository
475 473 self._rename_filesystem_repo(
476 474 old=source_repo_name, new=new_name)
477 475
478 476 return cur_repo
479 477 except Exception:
480 478 log.error(traceback.format_exc())
481 479 raise
482 480
483 481 def _create_repo(self, repo_name, repo_type, description, owner,
484 482 private=False, clone_uri=None, repo_group=None,
485 483 landing_rev='rev:tip', fork_of=None,
486 484 copy_fork_permissions=False, enable_statistics=False,
487 485 enable_locking=False, enable_downloads=False,
488 486 copy_group_permissions=False,
489 487 state=Repository.STATE_PENDING):
490 488 """
491 489 Create repository inside database with PENDING state, this should be
492 490 only executed by create() repo. With exception of importing existing
493 491 repos
494 492 """
495 493 from rhodecode.model.scm import ScmModel
496 494
497 495 owner = self._get_user(owner)
498 496 fork_of = self._get_repo(fork_of)
499 497 repo_group = self._get_repo_group(safe_int(repo_group))
500 498
501 499 try:
502 500 repo_name = safe_unicode(repo_name)
503 501 description = safe_unicode(description)
504 502 # repo name is just a name of repository
505 503 # while repo_name_full is a full qualified name that is combined
506 504 # with name and path of group
507 505 repo_name_full = repo_name
508 506 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
509 507
510 508 new_repo = Repository()
511 509 new_repo.repo_state = state
512 510 new_repo.enable_statistics = False
513 511 new_repo.repo_name = repo_name_full
514 512 new_repo.repo_type = repo_type
515 513 new_repo.user = owner
516 514 new_repo.group = repo_group
517 515 new_repo.description = description or repo_name
518 516 new_repo.private = private
519 517 new_repo.archived = False
520 518 new_repo.clone_uri = clone_uri
521 519 new_repo.landing_rev = landing_rev
522 520
523 521 new_repo.enable_statistics = enable_statistics
524 522 new_repo.enable_locking = enable_locking
525 523 new_repo.enable_downloads = enable_downloads
526 524
527 525 if repo_group:
528 526 new_repo.enable_locking = repo_group.enable_locking
529 527
530 528 if fork_of:
531 529 parent_repo = fork_of
532 530 new_repo.fork = parent_repo
533 531
534 532 events.trigger(events.RepoPreCreateEvent(new_repo))
535 533
536 534 self.sa.add(new_repo)
537 535
538 536 EMPTY_PERM = 'repository.none'
539 537 if fork_of and copy_fork_permissions:
540 538 repo = fork_of
541 539 user_perms = UserRepoToPerm.query() \
542 540 .filter(UserRepoToPerm.repository == repo).all()
543 541 group_perms = UserGroupRepoToPerm.query() \
544 542 .filter(UserGroupRepoToPerm.repository == repo).all()
545 543
546 544 for perm in user_perms:
547 545 UserRepoToPerm.create(
548 546 perm.user, new_repo, perm.permission)
549 547
550 548 for perm in group_perms:
551 549 UserGroupRepoToPerm.create(
552 550 perm.users_group, new_repo, perm.permission)
553 551 # in case we copy permissions and also set this repo to private
554 552 # override the default user permission to make it a private repo
555 553 if private:
556 554 RepoModel(self.sa).grant_user_permission(
557 555 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
558 556
559 557 elif repo_group and copy_group_permissions:
560 558 user_perms = UserRepoGroupToPerm.query() \
561 559 .filter(UserRepoGroupToPerm.group == repo_group).all()
562 560
563 561 group_perms = UserGroupRepoGroupToPerm.query() \
564 562 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
565 563
566 564 for perm in user_perms:
567 565 perm_name = perm.permission.permission_name.replace(
568 566 'group.', 'repository.')
569 567 perm_obj = Permission.get_by_key(perm_name)
570 568 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
571 569
572 570 for perm in group_perms:
573 571 perm_name = perm.permission.permission_name.replace(
574 572 'group.', 'repository.')
575 573 perm_obj = Permission.get_by_key(perm_name)
576 574 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
577 575
578 576 if private:
579 577 RepoModel(self.sa).grant_user_permission(
580 578 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
581 579
582 580 else:
583 581 perm_obj = self._create_default_perms(new_repo, private)
584 582 self.sa.add(perm_obj)
585 583
586 584 # now automatically start following this repository as owner
587 585 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
588 586
589 587 # we need to flush here, in order to check if database won't
590 588 # throw any exceptions, create filesystem dirs at the very end
591 589 self.sa.flush()
592 590 events.trigger(events.RepoCreateEvent(new_repo))
593 591 return new_repo
594 592
595 593 except Exception:
596 594 log.error(traceback.format_exc())
597 595 raise
598 596
599 597 def create(self, form_data, cur_user):
600 598 """
601 599 Create repository using celery tasks
602 600
603 601 :param form_data:
604 602 :param cur_user:
605 603 """
606 604 from rhodecode.lib.celerylib import tasks, run_task
607 605 return run_task(tasks.create_repo, form_data, cur_user)
608 606
609 607 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
610 608 perm_deletions=None, check_perms=True,
611 609 cur_user=None):
612 610 if not perm_additions:
613 611 perm_additions = []
614 612 if not perm_updates:
615 613 perm_updates = []
616 614 if not perm_deletions:
617 615 perm_deletions = []
618 616
619 617 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
620 618
621 619 changes = {
622 620 'added': [],
623 621 'updated': [],
624 622 'deleted': []
625 623 }
626 624 # update permissions
627 625 for member_id, perm, member_type in perm_updates:
628 626 member_id = int(member_id)
629 627 if member_type == 'user':
630 628 member_name = User.get(member_id).username
631 629 # this updates also current one if found
632 630 self.grant_user_permission(
633 631 repo=repo, user=member_id, perm=perm)
634 632 elif member_type == 'user_group':
635 633 # check if we have permissions to alter this usergroup
636 634 member_name = UserGroup.get(member_id).users_group_name
637 635 if not check_perms or HasUserGroupPermissionAny(
638 636 *req_perms)(member_name, user=cur_user):
639 637 self.grant_user_group_permission(
640 638 repo=repo, group_name=member_id, perm=perm)
641 639 else:
642 640 raise ValueError("member_type must be 'user' or 'user_group' "
643 641 "got {} instead".format(member_type))
644 642 changes['updated'].append({'type': member_type, 'id': member_id,
645 643 'name': member_name, 'new_perm': perm})
646 644
647 645 # set new permissions
648 646 for member_id, perm, member_type in perm_additions:
649 647 member_id = int(member_id)
650 648 if member_type == 'user':
651 649 member_name = User.get(member_id).username
652 650 self.grant_user_permission(
653 651 repo=repo, user=member_id, perm=perm)
654 652 elif member_type == 'user_group':
655 653 # check if we have permissions to alter this usergroup
656 654 member_name = UserGroup.get(member_id).users_group_name
657 655 if not check_perms or HasUserGroupPermissionAny(
658 656 *req_perms)(member_name, user=cur_user):
659 657 self.grant_user_group_permission(
660 658 repo=repo, group_name=member_id, perm=perm)
661 659 else:
662 660 raise ValueError("member_type must be 'user' or 'user_group' "
663 661 "got {} instead".format(member_type))
664 662
665 663 changes['added'].append({'type': member_type, 'id': member_id,
666 664 'name': member_name, 'new_perm': perm})
667 665 # delete permissions
668 666 for member_id, perm, member_type in perm_deletions:
669 667 member_id = int(member_id)
670 668 if member_type == 'user':
671 669 member_name = User.get(member_id).username
672 670 self.revoke_user_permission(repo=repo, user=member_id)
673 671 elif member_type == 'user_group':
674 672 # check if we have permissions to alter this usergroup
675 673 member_name = UserGroup.get(member_id).users_group_name
676 674 if not check_perms or HasUserGroupPermissionAny(
677 675 *req_perms)(member_name, user=cur_user):
678 676 self.revoke_user_group_permission(
679 677 repo=repo, group_name=member_id)
680 678 else:
681 679 raise ValueError("member_type must be 'user' or 'user_group' "
682 680 "got {} instead".format(member_type))
683 681
684 682 changes['deleted'].append({'type': member_type, 'id': member_id,
685 683 'name': member_name, 'new_perm': perm})
686 684 return changes
687 685
688 686 def create_fork(self, form_data, cur_user):
689 687 """
690 688 Simple wrapper into executing celery task for fork creation
691 689
692 690 :param form_data:
693 691 :param cur_user:
694 692 """
695 693 from rhodecode.lib.celerylib import tasks, run_task
696 694 return run_task(tasks.create_repo_fork, form_data, cur_user)
697 695
698 696 def archive(self, repo):
699 697 """
700 698 Archive given repository. Set archive flag.
701 699
702 700 :param repo:
703 701 """
704 702 repo = self._get_repo(repo)
705 703 if repo:
706 704
707 705 try:
708 706 repo.archived = True
709 707 self.sa.add(repo)
710 708 self.sa.commit()
711 709 except Exception:
712 710 log.error(traceback.format_exc())
713 711 raise
714 712
715 713 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
716 714 """
717 715 Delete given repository, forks parameter defines what do do with
718 716 attached forks. Throws AttachedForksError if deleted repo has attached
719 717 forks
720 718
721 719 :param repo:
722 720 :param forks: str 'delete' or 'detach'
723 721 :param pull_requests: str 'delete' or None
724 722 :param fs_remove: remove(archive) repo from filesystem
725 723 """
726 724 if not cur_user:
727 725 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
728 726 repo = self._get_repo(repo)
729 727 if repo:
730 728 if forks == 'detach':
731 729 for r in repo.forks:
732 730 r.fork = None
733 731 self.sa.add(r)
734 732 elif forks == 'delete':
735 733 for r in repo.forks:
736 734 self.delete(r, forks='delete')
737 735 elif [f for f in repo.forks]:
738 736 raise AttachedForksError()
739 737
740 738 # check for pull requests
741 739 pr_sources = repo.pull_requests_source
742 740 pr_targets = repo.pull_requests_target
743 741 if pull_requests != 'delete' and (pr_sources or pr_targets):
744 742 raise AttachedPullRequestsError()
745 743
746 744 old_repo_dict = repo.get_dict()
747 745 events.trigger(events.RepoPreDeleteEvent(repo))
748 746 try:
749 747 self.sa.delete(repo)
750 748 if fs_remove:
751 749 self._delete_filesystem_repo(repo)
752 750 else:
753 751 log.debug('skipping removal from filesystem')
754 752 old_repo_dict.update({
755 753 'deleted_by': cur_user,
756 754 'deleted_on': time.time(),
757 755 })
758 756 log_delete_repository(**old_repo_dict)
759 757 events.trigger(events.RepoDeleteEvent(repo))
760 758 except Exception:
761 759 log.error(traceback.format_exc())
762 760 raise
763 761
764 762 def grant_user_permission(self, repo, user, perm):
765 763 """
766 764 Grant permission for user on given repository, or update existing one
767 765 if found
768 766
769 767 :param repo: Instance of Repository, repository_id, or repository name
770 768 :param user: Instance of User, user_id or username
771 769 :param perm: Instance of Permission, or permission_name
772 770 """
773 771 user = self._get_user(user)
774 772 repo = self._get_repo(repo)
775 773 permission = self._get_perm(perm)
776 774
777 775 # check if we have that permission already
778 776 obj = self.sa.query(UserRepoToPerm) \
779 777 .filter(UserRepoToPerm.user == user) \
780 778 .filter(UserRepoToPerm.repository == repo) \
781 779 .scalar()
782 780 if obj is None:
783 781 # create new !
784 782 obj = UserRepoToPerm()
785 783 obj.repository = repo
786 784 obj.user = user
787 785 obj.permission = permission
788 786 self.sa.add(obj)
789 787 log.debug('Granted perm %s to %s on %s', perm, user, repo)
790 788 action_logger_generic(
791 789 'granted permission: {} to user: {} on repo: {}'.format(
792 790 perm, user, repo), namespace='security.repo')
793 791 return obj
794 792
795 793 def revoke_user_permission(self, repo, user):
796 794 """
797 795 Revoke permission for user on given repository
798 796
799 797 :param repo: Instance of Repository, repository_id, or repository name
800 798 :param user: Instance of User, user_id or username
801 799 """
802 800
803 801 user = self._get_user(user)
804 802 repo = self._get_repo(repo)
805 803
806 804 obj = self.sa.query(UserRepoToPerm) \
807 805 .filter(UserRepoToPerm.repository == repo) \
808 806 .filter(UserRepoToPerm.user == user) \
809 807 .scalar()
810 808 if obj:
811 809 self.sa.delete(obj)
812 810 log.debug('Revoked perm on %s on %s', repo, user)
813 811 action_logger_generic(
814 812 'revoked permission from user: {} on repo: {}'.format(
815 813 user, repo), namespace='security.repo')
816 814
817 815 def grant_user_group_permission(self, repo, group_name, perm):
818 816 """
819 817 Grant permission for user group on given repository, or update
820 818 existing one if found
821 819
822 820 :param repo: Instance of Repository, repository_id, or repository name
823 821 :param group_name: Instance of UserGroup, users_group_id,
824 822 or user group name
825 823 :param perm: Instance of Permission, or permission_name
826 824 """
827 825 repo = self._get_repo(repo)
828 826 group_name = self._get_user_group(group_name)
829 827 permission = self._get_perm(perm)
830 828
831 829 # check if we have that permission already
832 830 obj = self.sa.query(UserGroupRepoToPerm) \
833 831 .filter(UserGroupRepoToPerm.users_group == group_name) \
834 832 .filter(UserGroupRepoToPerm.repository == repo) \
835 833 .scalar()
836 834
837 835 if obj is None:
838 836 # create new
839 837 obj = UserGroupRepoToPerm()
840 838
841 839 obj.repository = repo
842 840 obj.users_group = group_name
843 841 obj.permission = permission
844 842 self.sa.add(obj)
845 843 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
846 844 action_logger_generic(
847 845 'granted permission: {} to usergroup: {} on repo: {}'.format(
848 846 perm, group_name, repo), namespace='security.repo')
849 847
850 848 return obj
851 849
852 850 def revoke_user_group_permission(self, repo, group_name):
853 851 """
854 852 Revoke permission for user group on given repository
855 853
856 854 :param repo: Instance of Repository, repository_id, or repository name
857 855 :param group_name: Instance of UserGroup, users_group_id,
858 856 or user group name
859 857 """
860 858 repo = self._get_repo(repo)
861 859 group_name = self._get_user_group(group_name)
862 860
863 861 obj = self.sa.query(UserGroupRepoToPerm) \
864 862 .filter(UserGroupRepoToPerm.repository == repo) \
865 863 .filter(UserGroupRepoToPerm.users_group == group_name) \
866 864 .scalar()
867 865 if obj:
868 866 self.sa.delete(obj)
869 867 log.debug('Revoked perm to %s on %s', repo, group_name)
870 868 action_logger_generic(
871 869 'revoked permission from usergroup: {} on repo: {}'.format(
872 870 group_name, repo), namespace='security.repo')
873 871
874 872 def delete_stats(self, repo_name):
875 873 """
876 874 removes stats for given repo
877 875
878 876 :param repo_name:
879 877 """
880 878 repo = self._get_repo(repo_name)
881 879 try:
882 880 obj = self.sa.query(Statistics) \
883 881 .filter(Statistics.repository == repo).scalar()
884 882 if obj:
885 883 self.sa.delete(obj)
886 884 except Exception:
887 885 log.error(traceback.format_exc())
888 886 raise
889 887
890 888 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
891 889 field_type='str', field_desc=''):
892 890
893 891 repo = self._get_repo(repo_name)
894 892
895 893 new_field = RepositoryField()
896 894 new_field.repository = repo
897 895 new_field.field_key = field_key
898 896 new_field.field_type = field_type # python type
899 897 new_field.field_value = field_value
900 898 new_field.field_desc = field_desc
901 899 new_field.field_label = field_label
902 900 self.sa.add(new_field)
903 901 return new_field
904 902
905 903 def delete_repo_field(self, repo_name, field_key):
906 904 repo = self._get_repo(repo_name)
907 905 field = RepositoryField.get_by_key_name(field_key, repo)
908 906 if field:
909 907 self.sa.delete(field)
910 908
911 909 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
912 910 clone_uri=None, repo_store_location=None,
913 911 use_global_config=False, install_hooks=True):
914 912 """
915 913 makes repository on filesystem. It's group aware means it'll create
916 914 a repository within a group, and alter the paths accordingly of
917 915 group location
918 916
919 917 :param repo_name:
920 918 :param alias:
921 919 :param parent:
922 920 :param clone_uri:
923 921 :param repo_store_location:
924 922 """
925 923 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
926 924 from rhodecode.model.scm import ScmModel
927 925
928 926 if Repository.NAME_SEP in repo_name:
929 927 raise ValueError(
930 928 'repo_name must not contain groups got `%s`' % repo_name)
931 929
932 930 if isinstance(repo_group, RepoGroup):
933 931 new_parent_path = os.sep.join(repo_group.full_path_splitted)
934 932 else:
935 933 new_parent_path = repo_group or ''
936 934
937 935 if repo_store_location:
938 936 _paths = [repo_store_location]
939 937 else:
940 938 _paths = [self.repos_path, new_parent_path, repo_name]
941 939 # we need to make it str for mercurial
942 940 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
943 941
944 942 # check if this path is not a repository
945 943 if is_valid_repo(repo_path, self.repos_path):
946 944 raise Exception('This path %s is a valid repository' % repo_path)
947 945
948 946 # check if this path is a group
949 947 if is_valid_repo_group(repo_path, self.repos_path):
950 948 raise Exception('This path %s is a valid group' % repo_path)
951 949
952 950 log.info('creating repo %s in %s from url: `%s`',
953 951 repo_name, safe_unicode(repo_path),
954 952 obfuscate_url_pw(clone_uri))
955 953
956 954 backend = get_backend(repo_type)
957 955
958 956 config_repo = None if use_global_config else repo_name
959 957 if config_repo and new_parent_path:
960 958 config_repo = Repository.NAME_SEP.join(
961 959 (new_parent_path, config_repo))
962 960 config = make_db_config(clear_session=False, repo=config_repo)
963 961 config.set('extensions', 'largefiles', '')
964 962
965 963 # patch and reset hooks section of UI config to not run any
966 964 # hooks on creating remote repo
967 965 config.clear_section('hooks')
968 966
969 967 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
970 968 if repo_type == 'git':
971 969 repo = backend(
972 970 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
973 971 with_wire={"cache": False})
974 972 else:
975 973 repo = backend(
976 974 repo_path, config=config, create=True, src_url=clone_uri,
977 975 with_wire={"cache": False})
978 976
979 977 if install_hooks:
980 978 repo.install_hooks()
981 979
982 980 log.debug('Created repo %s with %s backend',
983 981 safe_unicode(repo_name), safe_unicode(repo_type))
984 982 return repo
985 983
986 984 def _rename_filesystem_repo(self, old, new):
987 985 """
988 986 renames repository on filesystem
989 987
990 988 :param old: old name
991 989 :param new: new name
992 990 """
993 991 log.info('renaming repo from %s to %s', old, new)
994 992
995 993 old_path = os.path.join(self.repos_path, old)
996 994 new_path = os.path.join(self.repos_path, new)
997 995 if os.path.isdir(new_path):
998 996 raise Exception(
999 997 'Was trying to rename to already existing dir %s' % new_path
1000 998 )
1001 999 shutil.move(old_path, new_path)
1002 1000
1003 1001 def _delete_filesystem_repo(self, repo):
1004 1002 """
1005 1003 removes repo from filesystem, the removal is acctually made by
1006 1004 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1007 1005 repository is no longer valid for rhodecode, can be undeleted later on
1008 1006 by reverting the renames on this repository
1009 1007
1010 1008 :param repo: repo object
1011 1009 """
1012 1010 rm_path = os.path.join(self.repos_path, repo.repo_name)
1013 1011 repo_group = repo.group
1014 1012 log.info("Removing repository %s", rm_path)
1015 1013 # disable hg/git internal that it doesn't get detected as repo
1016 1014 alias = repo.repo_type
1017 1015
1018 1016 config = make_db_config(clear_session=False)
1019 1017 config.set('extensions', 'largefiles', '')
1020 1018 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1021 1019
1022 1020 # skip this for bare git repos
1023 1021 if not bare:
1024 1022 # disable VCS repo
1025 1023 vcs_path = os.path.join(rm_path, '.%s' % alias)
1026 1024 if os.path.exists(vcs_path):
1027 1025 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1028 1026
1029 1027 _now = datetime.datetime.now()
1030 1028 _ms = str(_now.microsecond).rjust(6, '0')
1031 1029 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1032 1030 repo.just_name)
1033 1031 if repo_group:
1034 1032 # if repository is in group, prefix the removal path with the group
1035 1033 args = repo_group.full_path_splitted + [_d]
1036 1034 _d = os.path.join(*args)
1037 1035
1038 1036 if os.path.isdir(rm_path):
1039 1037 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1040 1038
1041 1039 # finally cleanup diff-cache if it exists
1042 1040 cached_diffs_dir = repo.cached_diffs_dir
1043 1041 if os.path.isdir(cached_diffs_dir):
1044 1042 shutil.rmtree(cached_diffs_dir)
1045 1043
1046 1044
1047 1045 class ReadmeFinder:
1048 1046 """
1049 1047 Utility which knows how to find a readme for a specific commit.
1050 1048
1051 1049 The main idea is that this is a configurable algorithm. When creating an
1052 1050 instance you can define parameters, currently only the `default_renderer`.
1053 1051 Based on this configuration the method :meth:`search` behaves slightly
1054 1052 different.
1055 1053 """
1056 1054
1057 1055 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1058 1056 path_re = re.compile(r'^docs?', re.IGNORECASE)
1059 1057
1060 1058 default_priorities = {
1061 1059 None: 0,
1062 1060 '.text': 2,
1063 1061 '.txt': 3,
1064 1062 '.rst': 1,
1065 1063 '.rest': 2,
1066 1064 '.md': 1,
1067 1065 '.mkdn': 2,
1068 1066 '.mdown': 3,
1069 1067 '.markdown': 4,
1070 1068 }
1071 1069
1072 1070 path_priority = {
1073 1071 'doc': 0,
1074 1072 'docs': 1,
1075 1073 }
1076 1074
1077 1075 FALLBACK_PRIORITY = 99
1078 1076
1079 1077 RENDERER_TO_EXTENSION = {
1080 1078 'rst': ['.rst', '.rest'],
1081 1079 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1082 1080 }
1083 1081
1084 1082 def __init__(self, default_renderer=None):
1085 1083 self._default_renderer = default_renderer
1086 1084 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1087 1085 default_renderer, [])
1088 1086
1089 1087 def search(self, commit, path='/'):
1090 1088 """
1091 1089 Find a readme in the given `commit`.
1092 1090 """
1093 1091 nodes = commit.get_nodes(path)
1094 1092 matches = self._match_readmes(nodes)
1095 1093 matches = self._sort_according_to_priority(matches)
1096 1094 if matches:
1097 1095 return matches[0].node
1098 1096
1099 1097 paths = self._match_paths(nodes)
1100 1098 paths = self._sort_paths_according_to_priority(paths)
1101 1099 for path in paths:
1102 1100 match = self.search(commit, path=path)
1103 1101 if match:
1104 1102 return match
1105 1103
1106 1104 return None
1107 1105
1108 1106 def _match_readmes(self, nodes):
1109 1107 for node in nodes:
1110 1108 if not node.is_file():
1111 1109 continue
1112 1110 path = node.path.rsplit('/', 1)[-1]
1113 1111 match = self.readme_re.match(path)
1114 1112 if match:
1115 1113 extension = match.group(1)
1116 1114 yield ReadmeMatch(node, match, self._priority(extension))
1117 1115
1118 1116 def _match_paths(self, nodes):
1119 1117 for node in nodes:
1120 1118 if not node.is_dir():
1121 1119 continue
1122 1120 match = self.path_re.match(node.path)
1123 1121 if match:
1124 1122 yield node.path
1125 1123
1126 1124 def _priority(self, extension):
1127 1125 renderer_priority = (
1128 1126 0 if extension in self._renderer_extensions else 1)
1129 1127 extension_priority = self.default_priorities.get(
1130 1128 extension, self.FALLBACK_PRIORITY)
1131 1129 return (renderer_priority, extension_priority)
1132 1130
1133 1131 def _sort_according_to_priority(self, matches):
1134 1132
1135 1133 def priority_and_path(match):
1136 1134 return (match.priority, match.path)
1137 1135
1138 1136 return sorted(matches, key=priority_and_path)
1139 1137
1140 1138 def _sort_paths_according_to_priority(self, paths):
1141 1139
1142 1140 def priority_and_path(path):
1143 1141 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1144 1142
1145 1143 return sorted(paths, key=priority_and_path)
1146 1144
1147 1145
1148 1146 class ReadmeMatch:
1149 1147
1150 1148 def __init__(self, node, match, priority):
1151 1149 self.node = node
1152 1150 self._match = match
1153 1151 self.priority = priority
1154 1152
1155 1153 @property
1156 1154 def path(self):
1157 1155 return self.node.path
1158 1156
1159 1157 def __repr__(self):
1160 1158 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,877 +1,876 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 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 time
32 32 import traceback
33 33 import string
34 34
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode import events
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 41 UserGroup, Repository)
42 42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 43 from rhodecode.lib.caching_query import FromCache
44 44 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class RepoGroupModel(BaseModel):
50 50
51 51 cls = RepoGroup
52 52 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
53 53 PERSONAL_GROUP_PATTERN = '${username}' # default
54 54
55 55 def _get_user_group(self, users_group):
56 56 return self._get_instance(UserGroup, users_group,
57 57 callback=UserGroup.get_by_group_name)
58 58
59 59 def _get_repo_group(self, repo_group):
60 60 return self._get_instance(RepoGroup, repo_group,
61 61 callback=RepoGroup.get_by_group_name)
62 62
63 63 @LazyProperty
64 64 def repos_path(self):
65 65 """
66 66 Gets the repositories root path from database
67 67 """
68 68
69 69 settings_model = VcsSettingsModel(sa=self.sa)
70 70 return settings_model.get_repos_location()
71 71
72 72 def get_by_group_name(self, repo_group_name, cache=None):
73 73 repo = self.sa.query(RepoGroup) \
74 74 .filter(RepoGroup.group_name == repo_group_name)
75 75
76 76 if cache:
77 77 name_key = _hash_key(repo_group_name)
78 78 repo = repo.options(
79 79 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
80 80 return repo.scalar()
81 81
82 82 def get_default_create_personal_repo_group(self):
83 83 value = SettingsModel().get_setting_by_name(
84 84 'create_personal_repo_group')
85 85 return value.app_settings_value if value else None or False
86 86
87 87 def get_personal_group_name_pattern(self):
88 88 value = SettingsModel().get_setting_by_name(
89 89 'personal_repo_group_pattern')
90 90 val = value.app_settings_value if value else None
91 91 group_template = val or self.PERSONAL_GROUP_PATTERN
92 92
93 93 group_template = group_template.lstrip('/')
94 94 return group_template
95 95
96 96 def get_personal_group_name(self, user):
97 97 template = self.get_personal_group_name_pattern()
98 98 return string.Template(template).safe_substitute(
99 99 username=user.username,
100 100 user_id=user.user_id,
101 101 first_name=user.first_name,
102 102 last_name=user.last_name,
103 103 )
104 104
105 105 def create_personal_repo_group(self, user, commit_early=True):
106 106 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
107 107 personal_repo_group_name = self.get_personal_group_name(user)
108 108
109 109 # create a new one
110 110 RepoGroupModel().create(
111 111 group_name=personal_repo_group_name,
112 112 group_description=desc,
113 113 owner=user.username,
114 114 personal=True,
115 115 commit_early=commit_early)
116 116
117 117 def _create_default_perms(self, new_group):
118 118 # create default permission
119 119 default_perm = 'group.read'
120 120 def_user = User.get_default_user()
121 121 for p in def_user.user_perms:
122 122 if p.permission.permission_name.startswith('group.'):
123 123 default_perm = p.permission.permission_name
124 124 break
125 125
126 126 repo_group_to_perm = UserRepoGroupToPerm()
127 127 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
128 128
129 129 repo_group_to_perm.group = new_group
130 130 repo_group_to_perm.user_id = def_user.user_id
131 131 return repo_group_to_perm
132 132
133 133 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
134 134 get_object=False):
135 135 """
136 136 Get's the group name and a parent group name from given group name.
137 137 If repo_in_path is set to truth, we asume the full path also includes
138 138 repo name, in such case we clean the last element.
139 139
140 140 :param group_name_full:
141 141 """
142 142 split_paths = 1
143 143 if repo_in_path:
144 144 split_paths = 2
145 145 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
146 146
147 147 if repo_in_path and len(_parts) > 1:
148 148 # such case last element is the repo_name
149 149 _parts.pop(-1)
150 150 group_name_cleaned = _parts[-1] # just the group name
151 151 parent_repo_group_name = None
152 152
153 153 if len(_parts) > 1:
154 154 parent_repo_group_name = _parts[0]
155 155
156 156 parent_group = None
157 157 if parent_repo_group_name:
158 158 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
159 159
160 160 if get_object:
161 161 return group_name_cleaned, parent_repo_group_name, parent_group
162 162
163 163 return group_name_cleaned, parent_repo_group_name
164 164
165 165 def check_exist_filesystem(self, group_name, exc_on_failure=True):
166 166 create_path = os.path.join(self.repos_path, group_name)
167 167 log.debug('creating new group in %s', create_path)
168 168
169 169 if os.path.isdir(create_path):
170 170 if exc_on_failure:
171 171 abs_create_path = os.path.abspath(create_path)
172 172 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
173 173 return False
174 174 return True
175 175
176 176 def _create_group(self, group_name):
177 177 """
178 178 makes repository group on filesystem
179 179
180 180 :param repo_name:
181 181 :param parent_id:
182 182 """
183 183
184 184 self.check_exist_filesystem(group_name)
185 185 create_path = os.path.join(self.repos_path, group_name)
186 186 log.debug('creating new group in %s', create_path)
187 187 os.makedirs(create_path, mode=0o755)
188 188 log.debug('created group in %s', create_path)
189 189
190 190 def _rename_group(self, old, new):
191 191 """
192 192 Renames a group on filesystem
193 193
194 194 :param group_name:
195 195 """
196 196
197 197 if old == new:
198 198 log.debug('skipping group rename')
199 199 return
200 200
201 201 log.debug('renaming repository group from %s to %s', old, new)
202 202
203 203 old_path = os.path.join(self.repos_path, old)
204 204 new_path = os.path.join(self.repos_path, new)
205 205
206 206 log.debug('renaming repos paths from %s to %s', old_path, new_path)
207 207
208 208 if os.path.isdir(new_path):
209 209 raise Exception('Was trying to rename to already '
210 210 'existing dir %s' % new_path)
211 211 shutil.move(old_path, new_path)
212 212
213 213 def _delete_filesystem_group(self, group, force_delete=False):
214 214 """
215 215 Deletes a group from a filesystem
216 216
217 217 :param group: instance of group from database
218 218 :param force_delete: use shutil rmtree to remove all objects
219 219 """
220 220 paths = group.full_path.split(RepoGroup.url_sep())
221 221 paths = os.sep.join(paths)
222 222
223 223 rm_path = os.path.join(self.repos_path, paths)
224 224 log.info("Removing group %s", rm_path)
225 225 # delete only if that path really exists
226 226 if os.path.isdir(rm_path):
227 227 if force_delete:
228 228 shutil.rmtree(rm_path)
229 229 else:
230 230 # archive that group`
231 231 _now = datetime.datetime.now()
232 232 _ms = str(_now.microsecond).rjust(6, '0')
233 233 _d = 'rm__%s_GROUP_%s' % (
234 234 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
235 235 shutil.move(rm_path, os.path.join(self.repos_path, _d))
236 236
237 237 def create(self, group_name, group_description, owner, just_db=False,
238 238 copy_permissions=False, personal=None, commit_early=True):
239 239
240 240 (group_name_cleaned,
241 241 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
242 242
243 243 parent_group = None
244 244 if parent_group_name:
245 245 parent_group = self._get_repo_group(parent_group_name)
246 246 if not parent_group:
247 247 # we tried to create a nested group, but the parent is not
248 248 # existing
249 249 raise ValueError(
250 250 'Parent group `%s` given in `%s` group name '
251 251 'is not yet existing.' % (parent_group_name, group_name))
252 252
253 253 # because we are doing a cleanup, we need to check if such directory
254 254 # already exists. If we don't do that we can accidentally delete
255 255 # existing directory via cleanup that can cause data issues, since
256 256 # delete does a folder rename to special syntax later cleanup
257 257 # functions can delete this
258 258 cleanup_group = self.check_exist_filesystem(group_name,
259 259 exc_on_failure=False)
260 260 user = self._get_user(owner)
261 261 if not user:
262 262 raise ValueError('Owner %s not found as rhodecode user', owner)
263 263
264 264 try:
265 265 new_repo_group = RepoGroup()
266 266 new_repo_group.user = user
267 267 new_repo_group.group_description = group_description or group_name
268 268 new_repo_group.parent_group = parent_group
269 269 new_repo_group.group_name = group_name
270 270 new_repo_group.personal = personal
271 271
272 272 self.sa.add(new_repo_group)
273 273
274 274 # create an ADMIN permission for owner except if we're super admin,
275 275 # later owner should go into the owner field of groups
276 276 if not user.is_admin:
277 277 self.grant_user_permission(repo_group=new_repo_group,
278 278 user=owner, perm='group.admin')
279 279
280 280 if parent_group and copy_permissions:
281 281 # copy permissions from parent
282 282 user_perms = UserRepoGroupToPerm.query() \
283 283 .filter(UserRepoGroupToPerm.group == parent_group).all()
284 284
285 285 group_perms = UserGroupRepoGroupToPerm.query() \
286 286 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
287 287
288 288 for perm in user_perms:
289 289 # don't copy over the permission for user who is creating
290 290 # this group, if he is not super admin he get's admin
291 291 # permission set above
292 292 if perm.user != user or user.is_admin:
293 293 UserRepoGroupToPerm.create(
294 294 perm.user, new_repo_group, perm.permission)
295 295
296 296 for perm in group_perms:
297 297 UserGroupRepoGroupToPerm.create(
298 298 perm.users_group, new_repo_group, perm.permission)
299 299 else:
300 300 perm_obj = self._create_default_perms(new_repo_group)
301 301 self.sa.add(perm_obj)
302 302
303 303 # now commit the changes, earlier so we are sure everything is in
304 304 # the database.
305 305 if commit_early:
306 306 self.sa.commit()
307 307 if not just_db:
308 308 self._create_group(new_repo_group.group_name)
309 309
310 310 # trigger the post hook
311 311 from rhodecode.lib.hooks_base import log_create_repository_group
312 312 repo_group = RepoGroup.get_by_group_name(group_name)
313 313
314 314 # update repo group commit caches initially
315 315 repo_group.update_commit_cache()
316 316
317 317 log_create_repository_group(
318 318 created_by=user.username, **repo_group.get_dict())
319 319
320 320 # Trigger create event.
321 321 events.trigger(events.RepoGroupCreateEvent(repo_group))
322 322
323 323 return new_repo_group
324 324 except Exception:
325 325 self.sa.rollback()
326 326 log.exception('Exception occurred when creating repository group, '
327 327 'doing cleanup...')
328 328 # rollback things manually !
329 329 repo_group = RepoGroup.get_by_group_name(group_name)
330 330 if repo_group:
331 331 RepoGroup.delete(repo_group.group_id)
332 332 self.sa.commit()
333 333 if cleanup_group:
334 334 RepoGroupModel()._delete_filesystem_group(repo_group)
335 335 raise
336 336
337 337 def update_permissions(
338 338 self, repo_group, perm_additions=None, perm_updates=None,
339 339 perm_deletions=None, recursive=None, check_perms=True,
340 340 cur_user=None):
341 341 from rhodecode.model.repo import RepoModel
342 342 from rhodecode.lib.auth import HasUserGroupPermissionAny
343 343
344 344 if not perm_additions:
345 345 perm_additions = []
346 346 if not perm_updates:
347 347 perm_updates = []
348 348 if not perm_deletions:
349 349 perm_deletions = []
350 350
351 351 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
352 352
353 353 changes = {
354 354 'added': [],
355 355 'updated': [],
356 356 'deleted': []
357 357 }
358 358
359 359 def _set_perm_user(obj, user, perm):
360 360 if isinstance(obj, RepoGroup):
361 361 self.grant_user_permission(
362 362 repo_group=obj, user=user, perm=perm)
363 363 elif isinstance(obj, Repository):
364 364 # private repos will not allow to change the default
365 365 # permissions using recursive mode
366 366 if obj.private and user == User.DEFAULT_USER:
367 367 return
368 368
369 369 # we set group permission but we have to switch to repo
370 370 # permission
371 371 perm = perm.replace('group.', 'repository.')
372 372 RepoModel().grant_user_permission(
373 373 repo=obj, user=user, perm=perm)
374 374
375 375 def _set_perm_group(obj, users_group, perm):
376 376 if isinstance(obj, RepoGroup):
377 377 self.grant_user_group_permission(
378 378 repo_group=obj, group_name=users_group, perm=perm)
379 379 elif isinstance(obj, Repository):
380 380 # we set group permission but we have to switch to repo
381 381 # permission
382 382 perm = perm.replace('group.', 'repository.')
383 383 RepoModel().grant_user_group_permission(
384 384 repo=obj, group_name=users_group, perm=perm)
385 385
386 386 def _revoke_perm_user(obj, user):
387 387 if isinstance(obj, RepoGroup):
388 388 self.revoke_user_permission(repo_group=obj, user=user)
389 389 elif isinstance(obj, Repository):
390 390 RepoModel().revoke_user_permission(repo=obj, user=user)
391 391
392 392 def _revoke_perm_group(obj, user_group):
393 393 if isinstance(obj, RepoGroup):
394 394 self.revoke_user_group_permission(
395 395 repo_group=obj, group_name=user_group)
396 396 elif isinstance(obj, Repository):
397 397 RepoModel().revoke_user_group_permission(
398 398 repo=obj, group_name=user_group)
399 399
400 400 # start updates
401 401 log.debug('Now updating permissions for %s in recursive mode:%s',
402 402 repo_group, recursive)
403 403
404 404 # initialize check function, we'll call that multiple times
405 405 has_group_perm = HasUserGroupPermissionAny(*req_perms)
406 406
407 407 for obj in repo_group.recursive_groups_and_repos():
408 408 # iterated obj is an instance of a repos group or repository in
409 409 # that group, recursive option can be: none, repos, groups, all
410 410 if recursive == 'all':
411 411 obj = obj
412 412 elif recursive == 'repos':
413 413 # skip groups, other than this one
414 414 if isinstance(obj, RepoGroup) and not obj == repo_group:
415 415 continue
416 416 elif recursive == 'groups':
417 417 # skip repos
418 418 if isinstance(obj, Repository):
419 419 continue
420 420 else: # recursive == 'none':
421 421 # DEFAULT option - don't apply to iterated objects
422 422 # also we do a break at the end of this loop. if we are not
423 423 # in recursive mode
424 424 obj = repo_group
425 425
426 426 change_obj = obj.get_api_data()
427 427
428 428 # update permissions
429 429 for member_id, perm, member_type in perm_updates:
430 430 member_id = int(member_id)
431 431 if member_type == 'user':
432 432 member_name = User.get(member_id).username
433 433 # this updates also current one if found
434 434 _set_perm_user(obj, user=member_id, perm=perm)
435 435 elif member_type == 'user_group':
436 436 member_name = UserGroup.get(member_id).users_group_name
437 437 if not check_perms or has_group_perm(member_name,
438 438 user=cur_user):
439 439 _set_perm_group(obj, users_group=member_id, perm=perm)
440 440 else:
441 441 raise ValueError("member_type must be 'user' or 'user_group' "
442 442 "got {} instead".format(member_type))
443 443
444 444 changes['updated'].append(
445 445 {'change_obj': change_obj, 'type': member_type,
446 446 'id': member_id, 'name': member_name, 'new_perm': perm})
447 447
448 448 # set new permissions
449 449 for member_id, perm, member_type in perm_additions:
450 450 member_id = int(member_id)
451 451 if member_type == 'user':
452 452 member_name = User.get(member_id).username
453 453 _set_perm_user(obj, user=member_id, perm=perm)
454 454 elif member_type == 'user_group':
455 455 # check if we have permissions to alter this usergroup
456 456 member_name = UserGroup.get(member_id).users_group_name
457 457 if not check_perms or has_group_perm(member_name,
458 458 user=cur_user):
459 459 _set_perm_group(obj, users_group=member_id, perm=perm)
460 460 else:
461 461 raise ValueError("member_type must be 'user' or 'user_group' "
462 462 "got {} instead".format(member_type))
463 463
464 464 changes['added'].append(
465 465 {'change_obj': change_obj, 'type': member_type,
466 466 'id': member_id, 'name': member_name, 'new_perm': perm})
467 467
468 468 # delete permissions
469 469 for member_id, perm, member_type in perm_deletions:
470 470 member_id = int(member_id)
471 471 if member_type == 'user':
472 472 member_name = User.get(member_id).username
473 473 _revoke_perm_user(obj, user=member_id)
474 474 elif member_type == 'user_group':
475 475 # check if we have permissions to alter this usergroup
476 476 member_name = UserGroup.get(member_id).users_group_name
477 477 if not check_perms or has_group_perm(member_name,
478 478 user=cur_user):
479 479 _revoke_perm_group(obj, user_group=member_id)
480 480 else:
481 481 raise ValueError("member_type must be 'user' or 'user_group' "
482 482 "got {} instead".format(member_type))
483 483
484 484 changes['deleted'].append(
485 485 {'change_obj': change_obj, 'type': member_type,
486 486 'id': member_id, 'name': member_name, 'new_perm': perm})
487 487
488 488 # if it's not recursive call for all,repos,groups
489 489 # break the loop and don't proceed with other changes
490 490 if recursive not in ['all', 'repos', 'groups']:
491 491 break
492 492
493 493 return changes
494 494
495 495 def update(self, repo_group, form_data):
496 496 try:
497 497 repo_group = self._get_repo_group(repo_group)
498 498 old_path = repo_group.full_path
499 499
500 500 # change properties
501 501 if 'group_description' in form_data:
502 502 repo_group.group_description = form_data['group_description']
503 503
504 504 if 'enable_locking' in form_data:
505 505 repo_group.enable_locking = form_data['enable_locking']
506 506
507 507 if 'group_parent_id' in form_data:
508 508 parent_group = (
509 509 self._get_repo_group(form_data['group_parent_id']))
510 510 repo_group.group_parent_id = (
511 511 parent_group.group_id if parent_group else None)
512 512 repo_group.parent_group = parent_group
513 513
514 514 # mikhail: to update the full_path, we have to explicitly
515 515 # update group_name
516 516 group_name = form_data.get('group_name', repo_group.name)
517 517 repo_group.group_name = repo_group.get_new_name(group_name)
518 518
519 519 new_path = repo_group.full_path
520 520
521 521 if 'user' in form_data:
522 522 repo_group.user = User.get_by_username(form_data['user'])
523 523
524 524 self.sa.add(repo_group)
525 525
526 526 # iterate over all members of this groups and do fixes
527 527 # set locking if given
528 528 # if obj is a repoGroup also fix the name of the group according
529 529 # to the parent
530 530 # if obj is a Repo fix it's name
531 531 # this can be potentially heavy operation
532 532 for obj in repo_group.recursive_groups_and_repos():
533 533 # set the value from it's parent
534 534 obj.enable_locking = repo_group.enable_locking
535 535 if isinstance(obj, RepoGroup):
536 536 new_name = obj.get_new_name(obj.name)
537 537 log.debug('Fixing group %s to new name %s',
538 538 obj.group_name, new_name)
539 539 obj.group_name = new_name
540 540
541 541 elif isinstance(obj, Repository):
542 542 # we need to get all repositories from this new group and
543 543 # rename them accordingly to new group path
544 544 new_name = obj.get_new_name(obj.just_name)
545 545 log.debug('Fixing repo %s to new name %s',
546 546 obj.repo_name, new_name)
547 547 obj.repo_name = new_name
548 548
549 549 self.sa.add(obj)
550 550
551 551 self._rename_group(old_path, new_path)
552 552
553 553 # Trigger update event.
554 554 events.trigger(events.RepoGroupUpdateEvent(repo_group))
555 555
556 556 return repo_group
557 557 except Exception:
558 558 log.error(traceback.format_exc())
559 559 raise
560 560
561 561 def delete(self, repo_group, force_delete=False, fs_remove=True):
562 562 repo_group = self._get_repo_group(repo_group)
563 563 if not repo_group:
564 564 return False
565 565 try:
566 566 self.sa.delete(repo_group)
567 567 if fs_remove:
568 568 self._delete_filesystem_group(repo_group, force_delete)
569 569 else:
570 570 log.debug('skipping removal from filesystem')
571 571
572 572 # Trigger delete event.
573 573 events.trigger(events.RepoGroupDeleteEvent(repo_group))
574 574 return True
575 575
576 576 except Exception:
577 577 log.error('Error removing repo_group %s', repo_group)
578 578 raise
579 579
580 580 def grant_user_permission(self, repo_group, user, perm):
581 581 """
582 582 Grant permission for user on given repository group, or update
583 583 existing one if found
584 584
585 585 :param repo_group: Instance of RepoGroup, repositories_group_id,
586 586 or repositories_group name
587 587 :param user: Instance of User, user_id or username
588 588 :param perm: Instance of Permission, or permission_name
589 589 """
590 590
591 591 repo_group = self._get_repo_group(repo_group)
592 592 user = self._get_user(user)
593 593 permission = self._get_perm(perm)
594 594
595 595 # check if we have that permission already
596 596 obj = self.sa.query(UserRepoGroupToPerm)\
597 597 .filter(UserRepoGroupToPerm.user == user)\
598 598 .filter(UserRepoGroupToPerm.group == repo_group)\
599 599 .scalar()
600 600 if obj is None:
601 601 # create new !
602 602 obj = UserRepoGroupToPerm()
603 603 obj.group = repo_group
604 604 obj.user = user
605 605 obj.permission = permission
606 606 self.sa.add(obj)
607 607 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
608 608 action_logger_generic(
609 609 'granted permission: {} to user: {} on repogroup: {}'.format(
610 610 perm, user, repo_group), namespace='security.repogroup')
611 611 return obj
612 612
613 613 def revoke_user_permission(self, repo_group, user):
614 614 """
615 615 Revoke permission for user on given repository group
616 616
617 617 :param repo_group: Instance of RepoGroup, repositories_group_id,
618 618 or repositories_group name
619 619 :param user: Instance of User, user_id or username
620 620 """
621 621
622 622 repo_group = self._get_repo_group(repo_group)
623 623 user = self._get_user(user)
624 624
625 625 obj = self.sa.query(UserRepoGroupToPerm)\
626 626 .filter(UserRepoGroupToPerm.user == user)\
627 627 .filter(UserRepoGroupToPerm.group == repo_group)\
628 628 .scalar()
629 629 if obj:
630 630 self.sa.delete(obj)
631 631 log.debug('Revoked perm on %s on %s', repo_group, user)
632 632 action_logger_generic(
633 633 'revoked permission from user: {} on repogroup: {}'.format(
634 634 user, repo_group), namespace='security.repogroup')
635 635
636 636 def grant_user_group_permission(self, repo_group, group_name, perm):
637 637 """
638 638 Grant permission for user group on given repository group, or update
639 639 existing one if found
640 640
641 641 :param repo_group: Instance of RepoGroup, repositories_group_id,
642 642 or repositories_group name
643 643 :param group_name: Instance of UserGroup, users_group_id,
644 644 or user group name
645 645 :param perm: Instance of Permission, or permission_name
646 646 """
647 647 repo_group = self._get_repo_group(repo_group)
648 648 group_name = self._get_user_group(group_name)
649 649 permission = self._get_perm(perm)
650 650
651 651 # check if we have that permission already
652 652 obj = self.sa.query(UserGroupRepoGroupToPerm)\
653 653 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
654 654 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
655 655 .scalar()
656 656
657 657 if obj is None:
658 658 # create new
659 659 obj = UserGroupRepoGroupToPerm()
660 660
661 661 obj.group = repo_group
662 662 obj.users_group = group_name
663 663 obj.permission = permission
664 664 self.sa.add(obj)
665 665 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
666 666 action_logger_generic(
667 667 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
668 668 perm, group_name, repo_group), namespace='security.repogroup')
669 669 return obj
670 670
671 671 def revoke_user_group_permission(self, repo_group, group_name):
672 672 """
673 673 Revoke permission for user group on given repository group
674 674
675 675 :param repo_group: Instance of RepoGroup, repositories_group_id,
676 676 or repositories_group name
677 677 :param group_name: Instance of UserGroup, users_group_id,
678 678 or user group name
679 679 """
680 680 repo_group = self._get_repo_group(repo_group)
681 681 group_name = self._get_user_group(group_name)
682 682
683 683 obj = self.sa.query(UserGroupRepoGroupToPerm)\
684 684 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
685 685 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
686 686 .scalar()
687 687 if obj:
688 688 self.sa.delete(obj)
689 689 log.debug('Revoked perm to %s on %s', repo_group, group_name)
690 690 action_logger_generic(
691 691 'revoked permission from usergroup: {} on repogroup: {}'.format(
692 692 group_name, repo_group), namespace='security.repogroup')
693 693
694 694 @classmethod
695 695 def update_commit_cache(cls, repo_groups=None):
696 696 if not repo_groups:
697 697 repo_groups = RepoGroup.getAll()
698 698 for repo_group in repo_groups:
699 699 repo_group.update_commit_cache()
700 700
701 701
702 702
703 703 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
704 704 super_user_actions=False):
705 705
706 706 from pyramid.threadlocal import get_current_request
707 707 _render = get_current_request().get_partial_renderer(
708 708 'rhodecode:templates/data_table/_dt_elements.mako')
709 709 c = _render.get_call_context()
710 710 h = _render.get_helpers()
711 711
712 712 def quick_menu(repo_group_name):
713 713 return _render('quick_repo_group_menu', repo_group_name)
714 714
715 715 def repo_group_lnk(repo_group_name):
716 716 return _render('repo_group_name', repo_group_name)
717 717
718 718 def last_change(last_change):
719 719 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
720 720 ts = time.time()
721 721 utc_offset = (datetime.datetime.fromtimestamp(ts)
722 722 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
723 723 last_change = last_change + datetime.timedelta(seconds=utc_offset)
724 724 return _render("last_change", last_change)
725 725
726 726 def desc(desc, personal):
727 727 return _render(
728 728 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
729 729
730 730 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
731 731 return _render(
732 732 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
733 733
734 734 def repo_group_name(repo_group_name, children_groups):
735 735 return _render("repo_group_name", repo_group_name, children_groups)
736 736
737 737 def user_profile(username):
738 738 return _render('user_profile', username)
739 739
740 740 repo_group_data = []
741 741 for group in repo_group_list:
742 742 # NOTE(marcink): because we use only raw column we need to load it like that
743 743 changeset_cache = RepoGroup._load_changeset_cache(
744 744 '', group._changeset_cache)
745 745 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
746 746 row = {
747 747 "menu": quick_menu(group.group_name),
748 748 "name": repo_group_lnk(group.group_name),
749 749 "name_raw": group.group_name,
750 750
751 751 "last_change": last_change(last_commit_change),
752 "last_change_raw": datetime_to_time(last_commit_change),
753 752
754 753 "last_changeset": "",
755 754 "last_changeset_raw": "",
756 755
757 756 "desc": desc(group.group_description, group.personal),
758 757 "top_level_repos": 0,
759 758 "owner": user_profile(group.User.username)
760 759 }
761 760 if admin:
762 761 repo_count = group.repositories.count()
763 762 children_groups = map(
764 763 h.safe_unicode,
765 764 itertools.chain((g.name for g in group.parents),
766 765 (x.name for x in [group])))
767 766 row.update({
768 767 "action": repo_group_actions(
769 768 group.group_id, group.group_name, repo_count),
770 769 "top_level_repos": repo_count,
771 770 "name": repo_group_name(group.group_name, children_groups),
772 771
773 772 })
774 773 repo_group_data.append(row)
775 774
776 775 return repo_group_data
777 776
778 777 def get_repo_groups_data_table(
779 778 self, draw, start, limit,
780 779 search_q, order_by, order_dir,
781 780 auth_user, repo_group_id):
782 781 from rhodecode.model.scm import RepoGroupList
783 782
784 783 _perms = ['group.read', 'group.write', 'group.admin']
785 784 repo_groups = RepoGroup.query() \
786 785 .filter(RepoGroup.group_parent_id == repo_group_id) \
787 786 .all()
788 787 auth_repo_group_list = RepoGroupList(
789 788 repo_groups, perm_set=_perms,
790 789 extra_kwargs=dict(user=auth_user))
791 790
792 791 allowed_ids = [-1]
793 792 for repo_group in auth_repo_group_list:
794 793 allowed_ids.append(repo_group.group_id)
795 794
796 795 repo_groups_data_total_count = RepoGroup.query() \
797 796 .filter(RepoGroup.group_parent_id == repo_group_id) \
798 797 .filter(or_(
799 798 # generate multiple IN to fix limitation problems
800 799 *in_filter_generator(RepoGroup.group_id, allowed_ids))
801 800 ) \
802 801 .count()
803 802
804 803 base_q = Session.query(
805 804 RepoGroup.group_name,
806 805 RepoGroup.group_name_hash,
807 806 RepoGroup.group_description,
808 807 RepoGroup.group_id,
809 808 RepoGroup.personal,
810 809 RepoGroup.updated_on,
811 810 RepoGroup._changeset_cache,
812 811 User,
813 812 ) \
814 813 .filter(RepoGroup.group_parent_id == repo_group_id) \
815 814 .filter(or_(
816 815 # generate multiple IN to fix limitation problems
817 816 *in_filter_generator(RepoGroup.group_id, allowed_ids))
818 817 ) \
819 818 .join(User, User.user_id == RepoGroup.user_id) \
820 819 .group_by(RepoGroup, User)
821 820
822 821 repo_groups_data_total_filtered_count = base_q.count()
823 822
824 823 sort_defined = False
825 824
826 825 if order_by == 'group_name':
827 826 sort_col = func.lower(RepoGroup.group_name)
828 827 sort_defined = True
829 828 elif order_by == 'user_username':
830 829 sort_col = User.username
831 830 else:
832 831 sort_col = getattr(RepoGroup, order_by, None)
833 832
834 833 if sort_defined or sort_col:
835 834 if order_dir == 'asc':
836 835 sort_col = sort_col.asc()
837 836 else:
838 837 sort_col = sort_col.desc()
839 838
840 839 base_q = base_q.order_by(sort_col)
841 840 base_q = base_q.offset(start).limit(limit)
842 841
843 842 repo_group_list = base_q.all()
844 843
845 844 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
846 845 repo_group_list=repo_group_list, admin=False)
847 846
848 847 data = ({
849 848 'draw': draw,
850 849 'data': repo_groups_data,
851 850 'recordsTotal': repo_groups_data_total_count,
852 851 'recordsFiltered': repo_groups_data_total_filtered_count,
853 852 })
854 853 return data
855 854
856 855 def _get_defaults(self, repo_group_name):
857 856 repo_group = RepoGroup.get_by_group_name(repo_group_name)
858 857
859 858 if repo_group is None:
860 859 return None
861 860
862 861 defaults = repo_group.get_dict()
863 862 defaults['repo_group_name'] = repo_group.name
864 863 defaults['repo_group_description'] = repo_group.group_description
865 864 defaults['repo_group_enable_locking'] = repo_group.enable_locking
866 865
867 866 # we use -1 as this is how in HTML, we mark an empty group
868 867 defaults['repo_group'] = defaults['group_parent_id'] or -1
869 868
870 869 # fill owner
871 870 if repo_group.user:
872 871 defaults.update({'user': repo_group.user.username})
873 872 else:
874 873 replacement_user = User.get_first_super_admin().username
875 874 defaults.update({'user': replacement_user})
876 875
877 876 return defaults
@@ -1,117 +1,117 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()"></%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='admin')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.admin_menu(active='repository_groups')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23
24 24 <div class="title">
25 25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
26 26 <span id="repo_group_count"></span>
27 27
28 28 <ul class="links">
29 29 %if c.can_create_repo_group:
30 30 <li>
31 31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36 <div id="repos_list_wrap">
37 37 <table id="group_list_table" class="display"></table>
38 38 </div>
39 39 </div>
40 40
41 41 <script>
42 42 $(document).ready(function() {
43 43 var $repoGroupsListTable = $('#group_list_table');
44 44
45 45 // repo group list
46 46 $repoGroupsListTable.DataTable({
47 47 processing: true,
48 48 serverSide: true,
49 49 ajax: {
50 50 "url": "${h.route_path('repo_groups_data')}",
51 51 "dataSrc": function (json) {
52 52 var filteredCount = json.recordsFiltered;
53 53 var filteredInactiveCount = json.recordsFilteredInactive;
54 54 var totalInactive = json.recordsTotalInactive;
55 55 var total = json.recordsTotal;
56 56
57 57 var _text = _gettext(
58 58 "{0} of {1} repository groups").format(
59 59 filteredCount, total);
60 60
61 61 if (total === filteredCount) {
62 62 _text = _gettext("{0} repository groups").format(total);
63 63 }
64 64 $('#repo_group_count').text(_text);
65 65 return json.data;
66 66 },
67 67 },
68 68
69 69 dom: 'rtp',
70 70 pageLength: ${c.visual.admin_grid_items},
71 71 order: [[ 0, "asc" ]],
72 72 columns: [
73 73 { data: {"_": "name",
74 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
74 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
75 75 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
76 76 { data: {"_": "desc",
77 77 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
78 78 { data: {"_": "last_change",
79 "sort": "last_change_raw",
79 "sort": "last_change",
80 80 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
81 81 { data: {"_": "top_level_repos",
82 82 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
83 83 { data: {"_": "owner",
84 84 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
85 85 { data: {"_": "action",
86 86 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
87 87 ],
88 88 language: {
89 89 paginate: DEFAULT_GRID_PAGINATION,
90 90 sProcessing: _gettext('loading...'),
91 91 emptyTable: _gettext("No repository groups available yet.")
92 92 },
93 93 });
94 94
95 95 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
96 96 $repoGroupsListTable.css('opacity', 1);
97 97 });
98 98
99 99 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
100 100 $repoGroupsListTable.css('opacity', 0.3);
101 101 });
102 102
103 103 // filter
104 104 $('#q_filter').on('keyup',
105 105 $.debounce(250, function() {
106 106 $repoGroupsListTable.DataTable().search(
107 107 $('#q_filter').val()
108 108 ).draw();
109 109 })
110 110 );
111 111
112 112 });
113 113
114 114 </script>
115 115
116 116 </%def>
117 117
@@ -1,149 +1,149 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories 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()"></%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='admin')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.admin_menu(active='repositories')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23
24 24 <div class="title">
25 25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
26 26 <span id="repo_count"></span>
27 27
28 28 <ul class="links">
29 29 %if c.can_create_repo:
30 30 <li>
31 31 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36 <div id="repos_list_wrap">
37 37 <table id="repo_list_table" class="display"></table>
38 38 </div>
39 39
40 40 </div>
41 41
42 42 <script>
43 43 $(document).ready(function() {
44 44 var $repoListTable = $('#repo_list_table');
45 45
46 46 // repo list
47 47 $repoListTable.DataTable({
48 48 processing: true,
49 49 serverSide: true,
50 50 ajax: {
51 51 "url": "${h.route_path('repos_data')}",
52 52 "dataSrc": function (json) {
53 53 var filteredCount = json.recordsFiltered;
54 54 var total = json.recordsTotal;
55 55
56 56 var _text = _gettext(
57 57 "{0} of {1} repositories").format(
58 58 filteredCount, total);
59 59
60 60 if (total === filteredCount) {
61 61 _text = _gettext("{0} repositories").format(total);
62 62 }
63 63 $('#repo_count').text(_text);
64 64
65 65 return json.data;
66 66 },
67 67 },
68 68 dom: 'rtp',
69 69 pageLength: ${c.visual.admin_grid_items},
70 70 order: [[ 0, "asc" ]],
71 71 columns: [
72 72 {
73 73 data: {
74 74 "_": "name",
75 "sort": "name_raw"
75 "sort": "name"
76 76 }, title: "${_('Name')}", className: "td-componentname"
77 77 },
78 78 {
79 79 data: 'menu', "bSortable": false, className: "quick_repo_menu"},
80 80 {
81 81 data: {
82 82 "_": "desc",
83 83 "sort": "desc"
84 84 }, title: "${_('Description')}", className: "td-description"
85 85 },
86 86 {
87 87 data: {
88 88 "_": "last_change",
89 "sort": "last_change_raw",
89 "sort": "last_change",
90 90 "type": Number
91 91 }, title: "${_('Last Change')}", className: "td-time"
92 92 },
93 93 {
94 94 data: {
95 95 "_": "last_changeset",
96 96 "sort": "last_changeset_raw",
97 97 "type": Number
98 98 }, title: "${_('Commit')}", className: "td-commit", orderable: false
99 99 },
100 100 {
101 101 data: {
102 102 "_": "owner",
103 103 "sort": "owner"
104 104 }, title: "${_('Owner')}", className: "td-user"
105 105 },
106 106 {
107 107 data: {
108 108 "_": "state",
109 109 "sort": "state"
110 110 }, title: "${_('State')}", className: "td-tags td-state"
111 111 },
112 112 {
113 113 data: {
114 114 "_": "action",
115 115 "sort": "action"
116 116 }, title: "${_('Action')}", className: "td-action", orderable: false
117 117 }
118 118 ],
119 119 language: {
120 120 paginate: DEFAULT_GRID_PAGINATION,
121 121 sProcessing: _gettext('loading...'),
122 122 emptyTable:_gettext("No repositories present.")
123 123 },
124 124 "initComplete": function( settings, json ) {
125 125 quick_repo_menu();
126 126 }
127 127 });
128 128
129 129 $repoListTable.on('xhr.dt', function(e, settings, json, xhr){
130 130 $repoListTable.css('opacity', 1);
131 131 });
132 132
133 133 $repoListTable.on('preXhr.dt', function(e, settings, data){
134 134 $repoListTable.css('opacity', 0.3);
135 135 });
136 136
137 137 $('#q_filter').on('keyup',
138 138 $.debounce(250, function() {
139 139 $repoListTable.DataTable().search(
140 140 $('#q_filter').val()
141 141 ).draw();
142 142 })
143 143 );
144 144
145 145 });
146 146
147 147 </script>
148 148
149 149 </%def>
@@ -1,221 +1,223 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3
4 4 <%def name="menu_bar_subnav()">
5 5 % if c.repo_group:
6 6 ${self.repo_group_menu(active='home')}
7 7 % endif
8 8 </%def>
9 9
10 10
11 11 <%def name="main()">
12 12 <div class="box">
13 13 <!-- box / title -->
14 14 <div class="title">
15 15
16 16 </div>
17 17 <!-- end box / title -->
18 18 <div id="no_grid_data" class="table" style="display: none">
19 19 <h2 class="no-object-border">
20 20 ${_('No repositories or repositories groups exists here.')}
21 21 </h2>
22 22 </div>
23 23
24 24 <div class="table">
25 25 <div id="groups_list_wrap" style="min-height: 200px;">
26 26 <table id="group_list_table" class="display" style="width: 100%;"></table>
27 27 </div>
28 28 </div>
29 29
30 30 <div class="table">
31 31 <div id="repos_list_wrap" style="min-height: 200px;">
32 32 <table id="repo_list_table" class="display" style="width: 100%;"></table>
33 33 </div>
34 34 </div>
35 35
36 36 </div>
37 37 <script>
38 38 $(document).ready(function () {
39 39
40 40 // repo group list
41 41 var $groupListTable = $('#group_list_table');
42 42
43 43 $groupListTable.DataTable({
44 44 processing: true,
45 45 serverSide: true,
46 46 ajax: {
47 47 "url": "${h.route_path('main_page_repo_groups_data')}",
48 48 "data": function (d) {
49 49 % if c.repo_group:
50 50 d.repo_group_id = ${c.repo_group.group_id}
51 51 % endif
52 52 }
53 53 },
54 54 dom: 'rtp',
55 55 pageLength: ${c.visual.dashboard_items},
56 56 order: [[0, "asc"]],
57 57 columns: [
58 58 {
59 59 data: {
60 60 "_": "name",
61 "sort": "name_raw"
61 "sort": "name"
62 62 }, title: "${_('Name')}", className: "truncate-wrap td-grid-name"
63 63 },
64 {data: 'menu', "bSortable": false, className: "quick_repo_menu"},
64 {
65 data: 'menu', "bSortable": false, className: "quick_repo_menu"
66 },
65 67 {
66 68 data: {
67 69 "_": "desc",
68 70 "sort": "desc"
69 71 }, title: "${_('Description')}", className: "td-description"
70 72 },
71 73 {
72 74 data: {
73 75 "_": "last_change",
74 "sort": "last_change_raw",
76 "sort": "last_change",
75 77 "type": Number
76 78 }, title: "${_('Last Change')}", className: "td-time"
77 79 },
78 80 {
79 81 data: {
80 82 "_": "last_changeset",
81 83 "sort": "last_changeset_raw",
82 84 "type": Number
83 }, title: "", className: "td-hash"
85 }, title: "", className: "td-hash", orderable: false
84 86 },
85 87 {
86 88 data: {
87 89 "_": "owner",
88 90 "sort": "owner"
89 91 }, title: "${_('Owner')}", className: "td-user"
90 92 }
91 93 ],
92 94 language: {
93 95 paginate: DEFAULT_GRID_PAGINATION,
94 96 sProcessing: _gettext('loading...'),
95 97 emptyTable: _gettext("No repository groups present.")
96 98 },
97 99 "drawCallback": function (settings, json) {
98 100 // hide grid if it's empty
99 101 if (settings.fnRecordsDisplay() === 0) {
100 102 $('#groups_list_wrap').hide();
101 103 // both hidden, show no-data
102 104 if ($('#repos_list_wrap').is(':hidden')) {
103 105 $('#no_grid_data').show();
104 106 }
105 107 } else {
106 108 $('#groups_list_wrap').show();
107 109 }
108 110
109 111 timeagoActivate();
110 112 tooltipActivate();
111 113 quick_repo_menu();
112 114 // hide pagination for single page
113 115 if (settings._iDisplayLength >= settings.fnRecordsDisplay()) {
114 116 $(settings.nTableWrapper).find('.dataTables_paginate').hide();
115 117 }
116 118
117 119 },
118 120 });
119 121
120 122 $groupListTable.on('xhr.dt', function (e, settings, json, xhr) {
121 123 $groupListTable.css('opacity', 1);
122 124 });
123 125
124 126 $groupListTable.on('preXhr.dt', function (e, settings, data) {
125 127 $groupListTable.css('opacity', 0.3);
126 128 });
127 129
128 130
129 131 ## // repo list
130 132 var $repoListTable = $('#repo_list_table');
131 133
132 134 $repoListTable.DataTable({
133 135 processing: true,
134 136 serverSide: true,
135 137 ajax: {
136 138 "url": "${h.route_path('main_page_repos_data')}",
137 139 "data": function (d) {
138 140 % if c.repo_group:
139 141 d.repo_group_id = ${c.repo_group.group_id}
140 142 % endif
141 143 }
142 144 },
143 145 order: [[0, "asc"]],
144 146 dom: 'rtp',
145 147 pageLength: ${c.visual.dashboard_items},
146 148 columns: [
147 149 {
148 150 data: {
149 151 "_": "name",
150 "sort": "name_raw"
152 "sort": "name"
151 153 }, title: "${_('Name')}", className: "truncate-wrap td-grid-name"
152 154 },
153 155 {
154 156 data: 'menu', "bSortable": false, className: "quick_repo_menu"
155 157 },
156 158 {
157 159 data: {
158 160 "_": "desc",
159 161 "sort": "desc"
160 162 }, title: "${_('Description')}", className: "td-description"
161 163 },
162 164 {
163 165 data: {
164 166 "_": "last_change",
165 "sort": "last_change_raw",
167 "sort": "last_change",
166 168 "type": Number
167 }, title: "${_('Last Change')}", className: "td-time", orderable: false
169 }, title: "${_('Last Change')}", className: "td-time"
168 170 },
169 171 {
170 172 data: {
171 173 "_": "last_changeset",
172 174 "sort": "last_changeset_raw",
173 175 "type": Number
174 }, title: "${_('Commit')}", className: "td-hash"
176 }, title: "${_('Commit')}", className: "td-hash", orderable: false
175 177 },
176 178 {
177 179 data: {
178 180 "_": "owner",
179 181 "sort": "owner"
180 182 }, title: "${_('Owner')}", className: "td-user"
181 183 }
182 184 ],
183 185 language: {
184 186 paginate: DEFAULT_GRID_PAGINATION,
185 187 sProcessing: _gettext('loading...'),
186 188 emptyTable: _gettext("No repositories present.")
187 189 },
188 190 "drawCallback": function (settings, json) {
189 191 // hide grid if it's empty
190 192 if (settings.fnRecordsDisplay() == 0) {
191 193 $('#repos_list_wrap').hide()
192 194 // both hidden, show no-data
193 195 if ($('#groups_list_wrap').is(':hidden')) {
194 196 $('#no_grid_data').show()
195 197 }
196 198 } else {
197 199 $('#repos_list_wrap').show()
198 200 }
199 201
200 202 timeagoActivate();
201 203 tooltipActivate();
202 204 quick_repo_menu();
203 205 // hide pagination for single page
204 206 if (settings._iDisplayLength >= settings.fnRecordsDisplay()) {
205 207 $(settings.nTableWrapper).find('.dataTables_paginate').hide();
206 208 }
207 209
208 210 },
209 211 });
210 212
211 213 $repoListTable.on('xhr.dt', function (e, settings, json, xhr) {
212 214 $repoListTable.css('opacity', 1);
213 215 });
214 216
215 217 $repoListTable.on('preXhr.dt', function (e, settings, data) {
216 218 $repoListTable.css('opacity', 0.3);
217 219 });
218 220
219 221 });
220 222 </script>
221 223 </%def>
General Comments 0
You need to be logged in to leave comments. Login now