##// END OF EJS Templates
user/user-groups: show if users or user groups are a part of review rules....
marcink -
r2054:0026cc40 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,413 +1,417 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 User Groups crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 import peppercorn
29 29 from formencode import htmlfill
30 30 from pylons import request, tmpl_context as c, url, config
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from rhodecode.lib import auth
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib import audit_logger
37 37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 38 RepoGroupAssignmentError
39 39 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
40 40 from rhodecode.lib.auth import (
41 41 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
42 42 HasPermissionAnyDecorator)
43 43 from rhodecode.lib.base import BaseController, render
44 44 from rhodecode.model.permission import PermissionModel
45 45 from rhodecode.model.user_group import UserGroupModel
46 46 from rhodecode.model.db import User, UserGroup
47 47 from rhodecode.model.forms import (
48 48 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
49 49 UserPermissionsForm)
50 50 from rhodecode.model.meta import Session
51 51
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class UserGroupsController(BaseController):
57 57 """REST Controller styled on the Atom Publishing Protocol"""
58 58
59 59 @LoginRequired()
60 60 def __before__(self):
61 61 super(UserGroupsController, self).__before__()
62 62 c.available_permissions = config['available_permissions']
63 63 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
64 64
65 65 def __load_data(self, user_group_id):
66 66 c.group_members_obj = [x.user for x in c.user_group.members]
67 67 c.group_members_obj.sort(key=lambda u: u.username.lower())
68 68 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
69 69
70 70 def __load_defaults(self, user_group_id):
71 71 """
72 72 Load defaults settings for edit, and update
73 73
74 74 :param user_group_id:
75 75 """
76 76 user_group = UserGroup.get_or_404(user_group_id)
77 77 data = user_group.get_dict()
78 78 # fill owner
79 79 if user_group.user:
80 80 data.update({'user': user_group.user.username})
81 81 else:
82 82 replacement_user = User.get_first_super_admin().username
83 83 data.update({'user': replacement_user})
84 84 return data
85 85
86 86 def _revoke_perms_on_yourself(self, form_result):
87 87 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
88 88 form_result['perm_updates'])
89 89 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
90 90 form_result['perm_additions'])
91 91 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
92 92 form_result['perm_deletions'])
93 93 admin_perm = 'usergroup.admin'
94 94 if _updates and _updates[0][1] != admin_perm or \
95 95 _additions and _additions[0][1] != admin_perm or \
96 96 _deletions and _deletions[0][1] != admin_perm:
97 97 return True
98 98 return False
99 99
100 100 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
101 101 @auth.CSRFRequired()
102 102 def create(self):
103 103
104 104 users_group_form = UserGroupForm()()
105 105 try:
106 106 form_result = users_group_form.to_python(dict(request.POST))
107 107 user_group = UserGroupModel().create(
108 108 name=form_result['users_group_name'],
109 109 description=form_result['user_group_description'],
110 110 owner=c.rhodecode_user.user_id,
111 111 active=form_result['users_group_active'])
112 112 Session().flush()
113 113 creation_data = user_group.get_api_data()
114 114 user_group_name = form_result['users_group_name']
115 115
116 116 audit_logger.store_web(
117 117 'user_group.create', action_data={'data': creation_data},
118 118 user=c.rhodecode_user)
119 119
120 120 user_group_link = h.link_to(
121 121 h.escape(user_group_name),
122 122 url('edit_users_group', user_group_id=user_group.users_group_id))
123 123 h.flash(h.literal(_('Created user group %(user_group_link)s')
124 124 % {'user_group_link': user_group_link}),
125 125 category='success')
126 126 Session().commit()
127 127 except formencode.Invalid as errors:
128 128 return htmlfill.render(
129 129 render('admin/user_groups/user_group_add.mako'),
130 130 defaults=errors.value,
131 131 errors=errors.error_dict or {},
132 132 prefix_error=False,
133 133 encoding="UTF-8",
134 134 force_defaults=False)
135 135 except Exception:
136 136 log.exception("Exception creating user group")
137 137 h.flash(_('Error occurred during creation of user group %s') \
138 138 % request.POST.get('users_group_name'), category='error')
139 139
140 140 return redirect(
141 141 url('edit_users_group', user_group_id=user_group.users_group_id))
142 142
143 143 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
144 144 def new(self):
145 145 """GET /user_groups/new: Form to create a new item"""
146 146 # url('new_users_group')
147 147 return render('admin/user_groups/user_group_add.mako')
148 148
149 149 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
150 150 @auth.CSRFRequired()
151 151 def update(self, user_group_id):
152 152
153 153 user_group_id = safe_int(user_group_id)
154 154 c.user_group = UserGroup.get_or_404(user_group_id)
155 155 c.active = 'settings'
156 156 self.__load_data(user_group_id)
157 157
158 158 users_group_form = UserGroupForm(
159 159 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
160 160
161 161 old_values = c.user_group.get_api_data()
162 162 try:
163 163 form_result = users_group_form.to_python(request.POST)
164 164 pstruct = peppercorn.parse(request.POST.items())
165 165 form_result['users_group_members'] = pstruct['user_group_members']
166 166
167 167 user_group, added_members, removed_members = \
168 168 UserGroupModel().update(c.user_group, form_result)
169 169 updated_user_group = form_result['users_group_name']
170 170
171 171 audit_logger.store_web(
172 172 'user_group.edit', action_data={'old_data': old_values},
173 173 user=c.rhodecode_user)
174 174
175 175 # TODO(marcink): use added/removed to set user_group.edit.member.add
176 176
177 177 h.flash(_('Updated user group %s') % updated_user_group,
178 178 category='success')
179 179 Session().commit()
180 180 except formencode.Invalid as errors:
181 181 defaults = errors.value
182 182 e = errors.error_dict or {}
183 183
184 184 return htmlfill.render(
185 185 render('admin/user_groups/user_group_edit.mako'),
186 186 defaults=defaults,
187 187 errors=e,
188 188 prefix_error=False,
189 189 encoding="UTF-8",
190 190 force_defaults=False)
191 191 except Exception:
192 192 log.exception("Exception during update of user group")
193 193 h.flash(_('Error occurred during update of user group %s')
194 194 % request.POST.get('users_group_name'), category='error')
195 195
196 196 return redirect(url('edit_users_group', user_group_id=user_group_id))
197 197
198 198 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
199 199 @auth.CSRFRequired()
200 200 def delete(self, user_group_id):
201 201 user_group_id = safe_int(user_group_id)
202 202 c.user_group = UserGroup.get_or_404(user_group_id)
203 203 force = str2bool(request.POST.get('force'))
204 204
205 205 old_values = c.user_group.get_api_data()
206 206 try:
207 207 UserGroupModel().delete(c.user_group, force=force)
208 208 audit_logger.store_web(
209 209 'user.delete', action_data={'old_data': old_values},
210 210 user=c.rhodecode_user)
211 211 Session().commit()
212 212 h.flash(_('Successfully deleted user group'), category='success')
213 213 except UserGroupAssignedException as e:
214 214 h.flash(str(e), category='error')
215 215 except Exception:
216 216 log.exception("Exception during deletion of user group")
217 217 h.flash(_('An error occurred during deletion of user group'),
218 218 category='error')
219 219 return redirect(url('users_groups'))
220 220
221 221 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
222 222 def edit(self, user_group_id):
223 223 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
224 224 # url('edit_users_group', user_group_id=ID)
225 225
226 226 user_group_id = safe_int(user_group_id)
227 227 c.user_group = UserGroup.get_or_404(user_group_id)
228 228 c.active = 'settings'
229 229 self.__load_data(user_group_id)
230 230
231 231 defaults = self.__load_defaults(user_group_id)
232 232
233 233 return htmlfill.render(
234 234 render('admin/user_groups/user_group_edit.mako'),
235 235 defaults=defaults,
236 236 encoding="UTF-8",
237 237 force_defaults=False
238 238 )
239 239
240 240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
241 241 def edit_perms(self, user_group_id):
242 242 user_group_id = safe_int(user_group_id)
243 243 c.user_group = UserGroup.get_or_404(user_group_id)
244 244 c.active = 'perms'
245 245
246 246 defaults = {}
247 247 # fill user group users
248 248 for p in c.user_group.user_user_group_to_perm:
249 249 defaults.update({'u_perm_%s' % p.user.user_id:
250 250 p.permission.permission_name})
251 251
252 252 for p in c.user_group.user_group_user_group_to_perm:
253 253 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
254 254 p.permission.permission_name})
255 255
256 256 return htmlfill.render(
257 257 render('admin/user_groups/user_group_edit.mako'),
258 258 defaults=defaults,
259 259 encoding="UTF-8",
260 260 force_defaults=False
261 261 )
262 262
263 263 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
264 264 @auth.CSRFRequired()
265 265 def update_perms(self, user_group_id):
266 266 """
267 267 grant permission for given usergroup
268 268
269 269 :param user_group_id:
270 270 """
271 271 user_group_id = safe_int(user_group_id)
272 272 c.user_group = UserGroup.get_or_404(user_group_id)
273 273 form = UserGroupPermsForm()().to_python(request.POST)
274 274
275 275 if not c.rhodecode_user.is_admin:
276 276 if self._revoke_perms_on_yourself(form):
277 277 msg = _('Cannot change permission for yourself as admin')
278 278 h.flash(msg, category='warning')
279 279 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
280 280
281 281 try:
282 282 UserGroupModel().update_permissions(user_group_id,
283 283 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
284 284 except RepoGroupAssignmentError:
285 285 h.flash(_('Target group cannot be the same'), category='error')
286 286 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
287 287
288 288 # TODO(marcink): implement global permissions
289 289 # audit_log.store_web('user_group.edit.permissions')
290 290 Session().commit()
291 291 h.flash(_('User Group permissions updated'), category='success')
292 292 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
293 293
294 294
295 295
296 296 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
297 297 def edit_global_perms(self, user_group_id):
298 298 user_group_id = safe_int(user_group_id)
299 299 c.user_group = UserGroup.get_or_404(user_group_id)
300 300 c.active = 'global_perms'
301 301
302 302 c.default_user = User.get_default_user()
303 303 defaults = c.user_group.get_dict()
304 304 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
305 305 defaults.update(c.user_group.get_default_perms())
306 306
307 307 return htmlfill.render(
308 308 render('admin/user_groups/user_group_edit.mako'),
309 309 defaults=defaults,
310 310 encoding="UTF-8",
311 311 force_defaults=False
312 312 )
313 313
314 314 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
315 315 @auth.CSRFRequired()
316 316 def update_global_perms(self, user_group_id):
317 317 user_group_id = safe_int(user_group_id)
318 318 user_group = UserGroup.get_or_404(user_group_id)
319 319 c.active = 'global_perms'
320 320
321 321 try:
322 322 # first stage that verifies the checkbox
323 323 _form = UserIndividualPermissionsForm()
324 324 form_result = _form.to_python(dict(request.POST))
325 325 inherit_perms = form_result['inherit_default_permissions']
326 326 user_group.inherit_default_permissions = inherit_perms
327 327 Session().add(user_group)
328 328
329 329 if not inherit_perms:
330 330 # only update the individual ones if we un check the flag
331 331 _form = UserPermissionsForm(
332 332 [x[0] for x in c.repo_create_choices],
333 333 [x[0] for x in c.repo_create_on_write_choices],
334 334 [x[0] for x in c.repo_group_create_choices],
335 335 [x[0] for x in c.user_group_create_choices],
336 336 [x[0] for x in c.fork_choices],
337 337 [x[0] for x in c.inherit_default_permission_choices])()
338 338
339 339 form_result = _form.to_python(dict(request.POST))
340 340 form_result.update({'perm_user_group_id': user_group.users_group_id})
341 341
342 342 PermissionModel().update_user_group_permissions(form_result)
343 343
344 344 Session().commit()
345 345 h.flash(_('User Group global permissions updated successfully'),
346 346 category='success')
347 347
348 348 except formencode.Invalid as errors:
349 349 defaults = errors.value
350 350 c.user_group = user_group
351 351 return htmlfill.render(
352 352 render('admin/user_groups/user_group_edit.mako'),
353 353 defaults=defaults,
354 354 errors=errors.error_dict or {},
355 355 prefix_error=False,
356 356 encoding="UTF-8",
357 357 force_defaults=False)
358 358 except Exception:
359 359 log.exception("Exception during permissions saving")
360 360 h.flash(_('An error occurred during permissions saving'),
361 361 category='error')
362 362
363 363 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
364 364
365 365 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
366 366 def edit_advanced(self, user_group_id):
367 367 user_group_id = safe_int(user_group_id)
368 368 c.user_group = UserGroup.get_or_404(user_group_id)
369 369 c.active = 'advanced'
370 370 c.group_members_obj = sorted(
371 371 (x.user for x in c.user_group.members),
372 372 key=lambda u: u.username.lower())
373 373
374 374 c.group_to_repos = sorted(
375 375 (x.repository for x in c.user_group.users_group_repo_to_perm),
376 376 key=lambda u: u.repo_name.lower())
377 377
378 378 c.group_to_repo_groups = sorted(
379 379 (x.group for x in c.user_group.users_group_repo_group_to_perm),
380 380 key=lambda u: u.group_name.lower())
381 381
382 c.group_to_review_rules = sorted(
383 (x.users_group for x in c.user_group.user_group_review_rules),
384 key=lambda u: u.users_group_name.lower())
385
382 386 return render('admin/user_groups/user_group_edit.mako')
383 387
384 388 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
385 389 def edit_advanced_set_synchronization(self, user_group_id):
386 390 user_group_id = safe_int(user_group_id)
387 391 user_group = UserGroup.get_or_404(user_group_id)
388 392
389 393 existing = user_group.group_data.get('extern_type')
390 394
391 395 if existing:
392 396 new_state = user_group.group_data
393 397 new_state['extern_type'] = None
394 398 else:
395 399 new_state = user_group.group_data
396 400 new_state['extern_type'] = 'manual'
397 401 new_state['extern_type_set_by'] = c.rhodecode_user.username
398 402
399 403 try:
400 404 user_group.group_data = new_state
401 405 Session().add(user_group)
402 406 Session().commit()
403 407
404 408 h.flash(_('User Group synchronization updated successfully'),
405 409 category='success')
406 410 except Exception:
407 411 log.exception("Exception during sync settings saving")
408 412 h.flash(_('An error occurred during synchronization update'),
409 413 category='error')
410 414
411 415 return redirect(
412 416 url('edit_user_group_advanced', user_group_id=user_group_id))
413 417
@@ -1,491 +1,496 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Users crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator, AuthUser)
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.exceptions import (
42 42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
43 43 UserOwnsUserGroupsException, UserCreationError)
44 44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 45
46 46 from rhodecode.model.db import (
47 47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
48 48 from rhodecode.model.forms import (
49 49 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.permission import PermissionModel
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UsersController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(UsersController, self).__before__()
64 64 c.available_permissions = config['available_permissions']
65 65 c.allowed_languages = [
66 66 ('en', 'English (en)'),
67 67 ('de', 'German (de)'),
68 68 ('fr', 'French (fr)'),
69 69 ('it', 'Italian (it)'),
70 70 ('ja', 'Japanese (ja)'),
71 71 ('pl', 'Polish (pl)'),
72 72 ('pt', 'Portuguese (pt)'),
73 73 ('ru', 'Russian (ru)'),
74 74 ('zh', 'Chinese (zh)'),
75 75 ]
76 76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
77 77
78 78 def _get_personal_repo_group_template_vars(self):
79 79 DummyUser = AttributeDict({
80 80 'username': '${username}',
81 81 'user_id': '${user_id}',
82 82 })
83 83 c.default_create_repo_group = RepoGroupModel() \
84 84 .get_default_create_personal_repo_group()
85 85 c.personal_repo_group_name = RepoGroupModel() \
86 86 .get_personal_group_name(DummyUser)
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 @auth.CSRFRequired()
90 90 def create(self):
91 91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
92 92 user_model = UserModel()
93 93 user_form = UserForm()()
94 94 try:
95 95 form_result = user_form.to_python(dict(request.POST))
96 96 user = user_model.create(form_result)
97 97 Session().flush()
98 98 creation_data = user.get_api_data()
99 99 username = form_result['username']
100 100
101 101 audit_logger.store_web(
102 102 'user.create', action_data={'data': creation_data},
103 103 user=c.rhodecode_user)
104 104
105 105 user_link = h.link_to(h.escape(username),
106 106 url('edit_user',
107 107 user_id=user.user_id))
108 108 h.flash(h.literal(_('Created user %(user_link)s')
109 109 % {'user_link': user_link}), category='success')
110 110 Session().commit()
111 111 except formencode.Invalid as errors:
112 112 self._get_personal_repo_group_template_vars()
113 113 return htmlfill.render(
114 114 render('admin/users/user_add.mako'),
115 115 defaults=errors.value,
116 116 errors=errors.error_dict or {},
117 117 prefix_error=False,
118 118 encoding="UTF-8",
119 119 force_defaults=False)
120 120 except UserCreationError as e:
121 121 h.flash(e, 'error')
122 122 except Exception:
123 123 log.exception("Exception creation of user")
124 124 h.flash(_('Error occurred during creation of user %s')
125 125 % request.POST.get('username'), category='error')
126 126 return redirect(h.route_path('users'))
127 127
128 128 @HasPermissionAllDecorator('hg.admin')
129 129 def new(self):
130 130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 131 self._get_personal_repo_group_template_vars()
132 132 return render('admin/users/user_add.mako')
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 @auth.CSRFRequired()
136 136 def update(self, user_id):
137 137
138 138 user_id = safe_int(user_id)
139 139 c.user = User.get_or_404(user_id)
140 140 c.active = 'profile'
141 141 c.extern_type = c.user.extern_type
142 142 c.extern_name = c.user.extern_name
143 143 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
144 144 available_languages = [x[0] for x in c.allowed_languages]
145 145 _form = UserForm(edit=True, available_languages=available_languages,
146 146 old_data={'user_id': user_id,
147 147 'email': c.user.email})()
148 148 form_result = {}
149 149 old_values = c.user.get_api_data()
150 150 try:
151 151 form_result = _form.to_python(dict(request.POST))
152 152 skip_attrs = ['extern_type', 'extern_name']
153 153 # TODO: plugin should define if username can be updated
154 154 if c.extern_type != "rhodecode":
155 155 # forbid updating username for external accounts
156 156 skip_attrs.append('username')
157 157
158 158 UserModel().update_user(
159 159 user_id, skip_attrs=skip_attrs, **form_result)
160 160
161 161 audit_logger.store_web(
162 162 'user.edit', action_data={'old_data': old_values},
163 163 user=c.rhodecode_user)
164 164
165 165 Session().commit()
166 166 h.flash(_('User updated successfully'), category='success')
167 167 except formencode.Invalid as errors:
168 168 defaults = errors.value
169 169 e = errors.error_dict or {}
170 170
171 171 return htmlfill.render(
172 172 render('admin/users/user_edit.mako'),
173 173 defaults=defaults,
174 174 errors=e,
175 175 prefix_error=False,
176 176 encoding="UTF-8",
177 177 force_defaults=False)
178 178 except UserCreationError as e:
179 179 h.flash(e, 'error')
180 180 except Exception:
181 181 log.exception("Exception updating user")
182 182 h.flash(_('Error occurred during update of user %s')
183 183 % form_result.get('username'), category='error')
184 184 return redirect(url('edit_user', user_id=user_id))
185 185
186 186 @HasPermissionAllDecorator('hg.admin')
187 187 @auth.CSRFRequired()
188 188 def delete(self, user_id):
189 189 user_id = safe_int(user_id)
190 190 c.user = User.get_or_404(user_id)
191 191
192 192 _repos = c.user.repositories
193 193 _repo_groups = c.user.repository_groups
194 194 _user_groups = c.user.user_groups
195 195
196 196 handle_repos = None
197 197 handle_repo_groups = None
198 198 handle_user_groups = None
199 199 # dummy call for flash of handle
200 200 set_handle_flash_repos = lambda: None
201 201 set_handle_flash_repo_groups = lambda: None
202 202 set_handle_flash_user_groups = lambda: None
203 203
204 204 if _repos and request.POST.get('user_repos'):
205 205 do = request.POST['user_repos']
206 206 if do == 'detach':
207 207 handle_repos = 'detach'
208 208 set_handle_flash_repos = lambda: h.flash(
209 209 _('Detached %s repositories') % len(_repos),
210 210 category='success')
211 211 elif do == 'delete':
212 212 handle_repos = 'delete'
213 213 set_handle_flash_repos = lambda: h.flash(
214 214 _('Deleted %s repositories') % len(_repos),
215 215 category='success')
216 216
217 217 if _repo_groups and request.POST.get('user_repo_groups'):
218 218 do = request.POST['user_repo_groups']
219 219 if do == 'detach':
220 220 handle_repo_groups = 'detach'
221 221 set_handle_flash_repo_groups = lambda: h.flash(
222 222 _('Detached %s repository groups') % len(_repo_groups),
223 223 category='success')
224 224 elif do == 'delete':
225 225 handle_repo_groups = 'delete'
226 226 set_handle_flash_repo_groups = lambda: h.flash(
227 227 _('Deleted %s repository groups') % len(_repo_groups),
228 228 category='success')
229 229
230 230 if _user_groups and request.POST.get('user_user_groups'):
231 231 do = request.POST['user_user_groups']
232 232 if do == 'detach':
233 233 handle_user_groups = 'detach'
234 234 set_handle_flash_user_groups = lambda: h.flash(
235 235 _('Detached %s user groups') % len(_user_groups),
236 236 category='success')
237 237 elif do == 'delete':
238 238 handle_user_groups = 'delete'
239 239 set_handle_flash_user_groups = lambda: h.flash(
240 240 _('Deleted %s user groups') % len(_user_groups),
241 241 category='success')
242 242
243 243 old_values = c.user.get_api_data()
244 244 try:
245 245 UserModel().delete(c.user, handle_repos=handle_repos,
246 246 handle_repo_groups=handle_repo_groups,
247 247 handle_user_groups=handle_user_groups)
248 248
249 249 audit_logger.store_web(
250 250 'user.delete', action_data={'old_data': old_values},
251 251 user=c.rhodecode_user)
252 252
253 253 Session().commit()
254 254 set_handle_flash_repos()
255 255 set_handle_flash_repo_groups()
256 256 set_handle_flash_user_groups()
257 257 h.flash(_('Successfully deleted user'), category='success')
258 258 except (UserOwnsReposException, UserOwnsRepoGroupsException,
259 259 UserOwnsUserGroupsException, DefaultUserException) as e:
260 260 h.flash(e, category='warning')
261 261 except Exception:
262 262 log.exception("Exception during deletion of user")
263 263 h.flash(_('An error occurred during deletion of user'),
264 264 category='error')
265 265 return redirect(h.route_path('users'))
266 266
267 267 @HasPermissionAllDecorator('hg.admin')
268 268 @auth.CSRFRequired()
269 269 def reset_password(self, user_id):
270 270 """
271 271 toggle reset password flag for this user
272 272 """
273 273 user_id = safe_int(user_id)
274 274 c.user = User.get_or_404(user_id)
275 275 try:
276 276 old_value = c.user.user_data.get('force_password_change')
277 277 c.user.update_userdata(force_password_change=not old_value)
278 278
279 279 if old_value:
280 280 msg = _('Force password change disabled for user')
281 281 audit_logger.store_web(
282 282 'user.edit.password_reset.disabled',
283 283 user=c.rhodecode_user)
284 284 else:
285 285 msg = _('Force password change enabled for user')
286 286 audit_logger.store_web(
287 287 'user.edit.password_reset.enabled',
288 288 user=c.rhodecode_user)
289 289
290 290 Session().commit()
291 291 h.flash(msg, category='success')
292 292 except Exception:
293 293 log.exception("Exception during password reset for user")
294 294 h.flash(_('An error occurred during password reset for user'),
295 295 category='error')
296 296
297 297 return redirect(url('edit_user_advanced', user_id=user_id))
298 298
299 299 @HasPermissionAllDecorator('hg.admin')
300 300 @auth.CSRFRequired()
301 301 def create_personal_repo_group(self, user_id):
302 302 """
303 303 Create personal repository group for this user
304 304 """
305 305 from rhodecode.model.repo_group import RepoGroupModel
306 306
307 307 user_id = safe_int(user_id)
308 308 c.user = User.get_or_404(user_id)
309 309 personal_repo_group = RepoGroup.get_user_personal_repo_group(
310 310 c.user.user_id)
311 311 if personal_repo_group:
312 312 return redirect(url('edit_user_advanced', user_id=user_id))
313 313
314 314 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
315 315 c.user)
316 316 named_personal_group = RepoGroup.get_by_group_name(
317 317 personal_repo_group_name)
318 318 try:
319 319
320 320 if named_personal_group and named_personal_group.user_id == c.user.user_id:
321 321 # migrate the same named group, and mark it as personal
322 322 named_personal_group.personal = True
323 323 Session().add(named_personal_group)
324 324 Session().commit()
325 325 msg = _('Linked repository group `%s` as personal' % (
326 326 personal_repo_group_name,))
327 327 h.flash(msg, category='success')
328 328 elif not named_personal_group:
329 329 RepoGroupModel().create_personal_repo_group(c.user)
330 330
331 331 msg = _('Created repository group `%s`' % (
332 332 personal_repo_group_name,))
333 333 h.flash(msg, category='success')
334 334 else:
335 335 msg = _('Repository group `%s` is already taken' % (
336 336 personal_repo_group_name,))
337 337 h.flash(msg, category='warning')
338 338 except Exception:
339 339 log.exception("Exception during repository group creation")
340 340 msg = _(
341 341 'An error occurred during repository group creation for user')
342 342 h.flash(msg, category='error')
343 343 Session().rollback()
344 344
345 345 return redirect(url('edit_user_advanced', user_id=user_id))
346 346
347 347 @HasPermissionAllDecorator('hg.admin')
348 348 def show(self, user_id):
349 349 """GET /users/user_id: Show a specific item"""
350 350 # url('user', user_id=ID)
351 351 User.get_or_404(-1)
352 352
353 353 @HasPermissionAllDecorator('hg.admin')
354 354 def edit(self, user_id):
355 355 """GET /users/user_id/edit: Form to edit an existing item"""
356 356 # url('edit_user', user_id=ID)
357 357 user_id = safe_int(user_id)
358 358 c.user = User.get_or_404(user_id)
359 359 if c.user.username == User.DEFAULT_USER:
360 360 h.flash(_("You can't edit this user"), category='warning')
361 361 return redirect(h.route_path('users'))
362 362
363 363 c.active = 'profile'
364 364 c.extern_type = c.user.extern_type
365 365 c.extern_name = c.user.extern_name
366 366 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
367 367
368 368 defaults = c.user.get_dict()
369 369 defaults.update({'language': c.user.user_data.get('language')})
370 370 return htmlfill.render(
371 371 render('admin/users/user_edit.mako'),
372 372 defaults=defaults,
373 373 encoding="UTF-8",
374 374 force_defaults=False)
375 375
376 376 @HasPermissionAllDecorator('hg.admin')
377 377 def edit_advanced(self, user_id):
378 378 user_id = safe_int(user_id)
379 379 user = c.user = User.get_or_404(user_id)
380 380 if user.username == User.DEFAULT_USER:
381 381 h.flash(_("You can't edit this user"), category='warning')
382 382 return redirect(h.route_path('users'))
383 383
384 384 c.active = 'advanced'
385 385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
386 386 c.personal_repo_group_name = RepoGroupModel()\
387 387 .get_personal_group_name(user)
388
389 c.user_to_review_rules = sorted(
390 (x.user for x in c.user.user_review_rules),
391 key=lambda u: u.username.lower())
392
388 393 c.first_admin = User.get_first_super_admin()
389 394 defaults = user.get_dict()
390 395
391 396 # Interim workaround if the user participated on any pull requests as a
392 397 # reviewer.
393 398 has_review = len(user.reviewer_pull_requests)
394 399 c.can_delete_user = not has_review
395 400 c.can_delete_user_message = ''
396 401 inactive_link = h.link_to(
397 402 'inactive', h.url('edit_user', user_id=user_id, anchor='active'))
398 403 if has_review == 1:
399 404 c.can_delete_user_message = h.literal(_(
400 405 'The user participates as reviewer in {} pull request and '
401 406 'cannot be deleted. \nYou can set the user to '
402 407 '"{}" instead of deleting it.').format(
403 408 has_review, inactive_link))
404 409 elif has_review:
405 410 c.can_delete_user_message = h.literal(_(
406 411 'The user participates as reviewer in {} pull requests and '
407 412 'cannot be deleted. \nYou can set the user to '
408 413 '"{}" instead of deleting it.').format(
409 414 has_review, inactive_link))
410 415
411 416 return htmlfill.render(
412 417 render('admin/users/user_edit.mako'),
413 418 defaults=defaults,
414 419 encoding="UTF-8",
415 420 force_defaults=False)
416 421
417 422 @HasPermissionAllDecorator('hg.admin')
418 423 def edit_global_perms(self, user_id):
419 424 user_id = safe_int(user_id)
420 425 c.user = User.get_or_404(user_id)
421 426 if c.user.username == User.DEFAULT_USER:
422 427 h.flash(_("You can't edit this user"), category='warning')
423 428 return redirect(h.route_path('users'))
424 429
425 430 c.active = 'global_perms'
426 431
427 432 c.default_user = User.get_default_user()
428 433 defaults = c.user.get_dict()
429 434 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
430 435 defaults.update(c.default_user.get_default_perms())
431 436 defaults.update(c.user.get_default_perms())
432 437
433 438 return htmlfill.render(
434 439 render('admin/users/user_edit.mako'),
435 440 defaults=defaults,
436 441 encoding="UTF-8",
437 442 force_defaults=False)
438 443
439 444 @HasPermissionAllDecorator('hg.admin')
440 445 @auth.CSRFRequired()
441 446 def update_global_perms(self, user_id):
442 447 user_id = safe_int(user_id)
443 448 user = User.get_or_404(user_id)
444 449 c.active = 'global_perms'
445 450 try:
446 451 # first stage that verifies the checkbox
447 452 _form = UserIndividualPermissionsForm()
448 453 form_result = _form.to_python(dict(request.POST))
449 454 inherit_perms = form_result['inherit_default_permissions']
450 455 user.inherit_default_permissions = inherit_perms
451 456 Session().add(user)
452 457
453 458 if not inherit_perms:
454 459 # only update the individual ones if we un check the flag
455 460 _form = UserPermissionsForm(
456 461 [x[0] for x in c.repo_create_choices],
457 462 [x[0] for x in c.repo_create_on_write_choices],
458 463 [x[0] for x in c.repo_group_create_choices],
459 464 [x[0] for x in c.user_group_create_choices],
460 465 [x[0] for x in c.fork_choices],
461 466 [x[0] for x in c.inherit_default_permission_choices])()
462 467
463 468 form_result = _form.to_python(dict(request.POST))
464 469 form_result.update({'perm_user_id': user.user_id})
465 470
466 471 PermissionModel().update_user_permissions(form_result)
467 472
468 473 # TODO(marcink): implement global permissions
469 474 # audit_log.store_web('user.edit.permissions')
470 475
471 476 Session().commit()
472 477 h.flash(_('User global permissions updated successfully'),
473 478 category='success')
474 479
475 480 except formencode.Invalid as errors:
476 481 defaults = errors.value
477 482 c.user = user
478 483 return htmlfill.render(
479 484 render('admin/users/user_edit.mako'),
480 485 defaults=defaults,
481 486 errors=errors.error_dict or {},
482 487 prefix_error=False,
483 488 encoding="UTF-8",
484 489 force_defaults=False)
485 490 except Exception:
486 491 log.exception("Exception during permissions saving")
487 492 h.flash(_('An error occurred during permissions saving'),
488 493 category='error')
489 494 return redirect(url('edit_user_global_perms', user_id=user_id))
490 495
491 496
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,83 +1,84 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.user_group.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.user_group.created_on), '', '',),
7 7
8 8 (_('Members'), len(c.group_members_obj),'', [x for x in c.group_members_obj]),
9 9 (_('Automatic member sync'), 'Yes' if c.user_group.group_data.get('extern_type') else 'No', '', '',),
10 10
11 11 (_('Assigned to repositories'), len(c.group_to_repos),'', [x for x in c.group_to_repos]),
12 12 (_('Assigned to repo groups'), len(c.group_to_repo_groups), '', [x for x in c.group_to_repo_groups]),
13 13
14 (_('Assigned to review rules'), len(c.group_to_review_rules), '', [x for x in c.group_to_review_rules]),
14 15 ]
15 16 %>
16 17
17 18 <div class="panel panel-default">
18 19 <div class="panel-heading">
19 20 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
20 21 </div>
21 22 <div class="panel-body">
22 23 ${base.dt_info_panel(elems)}
23 24 </div>
24 25
25 26 </div>
26 27
27 28 <div class="panel panel-default">
28 29 <div class="panel-heading">
29 30 <h3 class="panel-title">${_('Group members sync')}</h3>
30 31 </div>
31 32 <div class="panel-body">
32 33 <% sync_type = c.user_group.group_data.get('extern_type') %>
33 34
34 35 % if sync_type:
35 36 <p>
36 37 ${_('This group is set to be automatically synchronised.')}<br/>
37 38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
38 39 </p>
39 40 % else:
40 41 <p>
41 42 ${_('This group is not set to be automatically synchronised')}
42 43 </p>
43 44 % endif
44 45
45 46 <div>
46 47 ${h.secure_form(h.url('edit_user_group_advanced_sync', user_group_id=c.user_group.users_group_id), method='post')}
47 48 <div class="field">
48 49 <button class="btn btn-default" type="submit">
49 50 %if sync_type:
50 51 ${_('Disable synchronization')}
51 52 %else:
52 53 ${_('Enable synchronization')}
53 54 %endif
54 55 </button>
55 56 </div>
56 57 <div class="field">
57 58 <span class="help-block">
58 59 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
59 60 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
60 61 </span>
61 62 </div>
62 63 ${h.end_form()}
63 64 </div>
64 65
65 66 </div>
66 67 </div>
67 68
68 69
69 70 <div class="panel panel-danger">
70 71 <div class="panel-heading">
71 72 <h3 class="panel-title">${_('Delete User Group')}</h3>
72 73 </div>
73 74 <div class="panel-body">
74 75 ${h.secure_form(h.url('delete_users_group', user_group_id=c.user_group.users_group_id),method='delete')}
75 76 ${h.hidden('force', 1)}
76 77 <button class="btn btn-small btn-danger" type="submit"
77 78 onclick="return confirm('${_('Confirm to delete user group `%(ugroup)s` with all permission assignments') % {'ugroup': c.user_group.users_group_name}}');">
78 79 <i class="icon-remove-sign"></i>
79 80 ${_('Delete This User Group')}
80 81 </button>
81 82 ${h.end_form()}
82 83 </div>
83 84 </div>
@@ -1,159 +1,161 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
6 6 (_('Source of Record'), c.user.extern_type, '', ''),
7 7
8 8 (_('Last login'), c.user.last_login or '-', '', ''),
9 9 (_('Last activity'), c.user.last_activity, '', ''),
10 10
11 11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
12 12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
13 13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
14 14
15 15 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
16 (_('Assigned to review rules'), len(c.user_to_review_rules), '', [x for x in c.user_to_review_rules]),
17
16 18 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
17 19 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
18 20 ]
19 21 %>
20 22
21 23 <div class="panel panel-default">
22 24 <div class="panel-heading">
23 25 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
24 26 </div>
25 27 <div class="panel-body">
26 28 ${base.dt_info_panel(elems)}
27 29 </div>
28 30 </div>
29 31
30 32 <div class="panel panel-default">
31 33 <div class="panel-heading">
32 34 <h3 class="panel-title">${_('Force Password Reset')}</h3>
33 35 </div>
34 36 <div class="panel-body">
35 37 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
36 38 <div class="field">
37 39 <button class="btn btn-default" type="submit">
38 40 <i class="icon-lock"></i>
39 41 %if c.user.user_data.get('force_password_change'):
40 42 ${_('Disable forced password reset')}
41 43 %else:
42 44 ${_('Enable forced password reset')}
43 45 %endif
44 46 </button>
45 47 </div>
46 48 <div class="field">
47 49 <span class="help-block">
48 50 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
49 51 </span>
50 52 </div>
51 53 ${h.end_form()}
52 54 </div>
53 55 </div>
54 56
55 57 <div class="panel panel-default">
56 58 <div class="panel-heading">
57 59 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
58 60 </div>
59 61 <div class="panel-body">
60 62 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
61 63
62 64 %if c.personal_repo_group:
63 65 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
64 66 %else:
65 67 <div class="panel-body-title-text">
66 68 ${_('This user currently does not have a personal repository group')}
67 69 <br/>
68 70 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
69 71 </div>
70 72 %endif
71 73 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
72 74 <i class="icon-folder-close"></i>
73 75 ${_('Create personal repository group')}
74 76 </button>
75 77 ${h.end_form()}
76 78 </div>
77 79 </div>
78 80
79 81
80 82 <div class="panel panel-danger">
81 83 <div class="panel-heading">
82 84 <h3 class="panel-title">${_('Delete User')}</h3>
83 85 </div>
84 86 <div class="panel-body">
85 87 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
86 88
87 89 <table class="display">
88 90 <tr>
89 91 <td>
90 92 ${_ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
91 93 </td>
92 94 <td>
93 95 %if len(c.user.repositories) > 0:
94 96 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
95 97 %endif
96 98 </td>
97 99 <td>
98 100 %if len(c.user.repositories) > 0:
99 101 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
100 102 %endif
101 103 </td>
102 104 </tr>
103 105
104 106 <tr>
105 107 <td>
106 108 ${_ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
107 109 </td>
108 110 <td>
109 111 %if len(c.user.repository_groups) > 0:
110 112 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
111 113 %endif
112 114 </td>
113 115 <td>
114 116 %if len(c.user.repository_groups) > 0:
115 117 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
116 118 %endif
117 119 </td>
118 120 </tr>
119 121
120 122 <tr>
121 123 <td>
122 124 ${_ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
123 125 </td>
124 126 <td>
125 127 %if len(c.user.user_groups) > 0:
126 128 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
127 129 %endif
128 130 </td>
129 131 <td>
130 132 %if len(c.user.user_groups) > 0:
131 133 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
132 134 %endif
133 135 </td>
134 136 </tr>
135 137 </table>
136 138 <div style="margin: 0 0 20px 0" class="fake-space"></div>
137 139
138 140 <div class="field">
139 141 <button class="btn btn-small btn-danger" type="submit"
140 142 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
141 143 ${"disabled" if not c.can_delete_user else ""}>
142 144 ${_('Delete this user')}
143 145 </button>
144 146 </div>
145 147 % if c.can_delete_user_message:
146 148 <p class="help-block pre-formatting">${c.can_delete_user_message}</p>
147 149 % endif
148 150
149 151 <div class="field">
150 152 <span class="help-block">
151 153 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
152 154 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
153 155 %endif
154 156 </span>
155 157 </div>
156 158
157 159 ${h.end_form()}
158 160 </div>
159 161 </div>
General Comments 0
You need to be logged in to leave comments. Login now