##// END OF EJS Templates
users: added option to detach pull requests for users which we delete....
dan -
r4351:2d86851b default
parent child Browse files
Show More
@@ -39,7 +39,8 b' from rhodecode.model.db import true, Use'
39 39 from rhodecode.lib import audit_logger, rc_cache
40 40 from rhodecode.lib.exceptions import (
41 41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, DefaultUserException)
42 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
43 UserOwnsArtifactsException, DefaultUserException)
43 44 from rhodecode.lib.ext_json import json
44 45 from rhodecode.lib.auth import (
45 46 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
@@ -377,11 +378,13 b' class UsersView(UserAppView):'
377 378 _repos = c.user.repositories
378 379 _repo_groups = c.user.repository_groups
379 380 _user_groups = c.user.user_groups
381 _pull_requests = c.user.user_pull_requests
380 382 _artifacts = c.user.artifacts
381 383
382 384 handle_repos = None
383 385 handle_repo_groups = None
384 386 handle_user_groups = None
387 handle_pull_requests = None
385 388 handle_artifacts = None
386 389
387 390 # calls for flash of handle based on handle case detach or delete
@@ -412,6 +415,15 b' class UsersView(UserAppView):'
412 415 h.flash(_('Deleted %s user groups') % len(_user_groups),
413 416 category='success')
414 417
418 def set_handle_flash_pull_requests():
419 handle = handle_pull_requests
420 if handle == 'detach':
421 h.flash(_('Detached %s pull requests') % len(_pull_requests),
422 category='success')
423 elif handle == 'delete':
424 h.flash(_('Deleted %s pull requests') % len(_pull_requests),
425 category='success')
426
415 427 def set_handle_flash_artifacts():
416 428 handle = handle_artifacts
417 429 if handle == 'detach':
@@ -421,6 +433,12 b' class UsersView(UserAppView):'
421 433 h.flash(_('Deleted %s artifacts') % len(_artifacts),
422 434 category='success')
423 435
436 handle_user = User.get_first_super_admin()
437 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
438 if handle_user_id:
439 # NOTE(marcink): we get new owner for objects...
440 handle_user = User.get_or_404(handle_user_id)
441
424 442 if _repos and self.request.POST.get('user_repos'):
425 443 handle_repos = self.request.POST['user_repos']
426 444
@@ -430,16 +448,25 b' class UsersView(UserAppView):'
430 448 if _user_groups and self.request.POST.get('user_user_groups'):
431 449 handle_user_groups = self.request.POST['user_user_groups']
432 450
451 if _pull_requests and self.request.POST.get('user_pull_requests'):
452 handle_pull_requests = self.request.POST['user_pull_requests']
453
433 454 if _artifacts and self.request.POST.get('user_artifacts'):
434 455 handle_artifacts = self.request.POST['user_artifacts']
435 456
436 457 old_values = c.user.get_api_data()
437 458
438 459 try:
439 UserModel().delete(c.user, handle_repos=handle_repos,
460
461 UserModel().delete(
462 c.user,
463 handle_repos=handle_repos,
440 464 handle_repo_groups=handle_repo_groups,
441 465 handle_user_groups=handle_user_groups,
442 handle_artifacts=handle_artifacts)
466 handle_pull_requests=handle_pull_requests,
467 handle_artifacts=handle_artifacts,
468 handle_new_owner=handle_user
469 )
443 470
444 471 audit_logger.store_web(
445 472 'user.delete', action_data={'old_data': old_values},
@@ -449,11 +476,13 b' class UsersView(UserAppView):'
449 476 set_handle_flash_repos()
450 477 set_handle_flash_repo_groups()
451 478 set_handle_flash_user_groups()
479 set_handle_flash_pull_requests()
452 480 set_handle_flash_artifacts()
453 481 username = h.escape(old_values['username'])
454 482 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
455 483 except (UserOwnsReposException, UserOwnsRepoGroupsException,
456 UserOwnsUserGroupsException, DefaultUserException) as e:
484 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
485 UserOwnsArtifactsException, DefaultUserException) as e:
457 486 h.flash(e, category='warning')
458 487 except Exception:
459 488 log.exception("Exception during deletion of user")
@@ -502,6 +531,11 b' class UsersView(UserAppView):'
502 531 user_id = self.db_user_id
503 532 c.user = self.db_user
504 533
534 c.detach_user = User.get_first_super_admin()
535 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
536 if detach_user_id:
537 c.detach_user = User.get_or_404(detach_user_id)
538
505 539 c.active = 'advanced'
506 540 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
507 541 c.personal_repo_group_name = RepoGroupModel()\
@@ -511,7 +545,6 b' class UsersView(UserAppView):'
511 545 (x.user for x in c.user.user_review_rules),
512 546 key=lambda u: u.username.lower())
513 547
514 c.first_admin = User.get_first_super_admin()
515 548 defaults = c.user.get_dict()
516 549
517 550 # Interim workaround if the user participated on any pull requests as a
@@ -58,6 +58,10 b' class UserOwnsUserGroupsException(Except'
58 58 pass
59 59
60 60
61 class UserOwnsPullRequestsException(Exception):
62 pass
63
64
61 65 class UserOwnsArtifactsException(Exception):
62 66 pass
63 67
@@ -617,6 +617,7 b' class User(Base, BaseModel):'
617 617 user_gists = relationship('Gist', cascade='all')
618 618 # user pull requests
619 619 user_pull_requests = relationship('PullRequest', cascade='all')
620
620 621 # external identities
621 622 external_identities = relationship(
622 623 'ExternalIdentity',
@@ -43,7 +43,9 b' from rhodecode.lib.compat import Ordered'
43 43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
44 44 from rhodecode.lib.markup_renderer import (
45 45 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
46 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe, AttributeDict, safe_int
46 from rhodecode.lib.utils2 import (
47 safe_unicode, safe_str, md5_safe, AttributeDict, safe_int,
48 get_current_rhodecode_user)
47 49 from rhodecode.lib.vcs.backends.base import (
48 50 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason,
49 51 TargetRefMissing, SourceRefMissing)
@@ -1427,7 +1429,10 b' class PullRequestModel(BaseModel):'
1427 1429 email_kwargs=email_kwargs,
1428 1430 )
1429 1431
1430 def delete(self, pull_request, user):
1432 def delete(self, pull_request, user=None):
1433 if not user:
1434 user = getattr(get_current_rhodecode_user(), 'username', None)
1435
1431 1436 pull_request = self.__get_pull_request(pull_request)
1432 1437 old_data = pull_request.get_api_data(with_merge_state=False)
1433 1438 self._cleanup_merge_workspace(pull_request)
@@ -37,17 +37,17 b' from rhodecode.lib.utils2 import ('
37 37 AttributeDict, str2bool)
38 38 from rhodecode.lib.exceptions import (
39 39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
41 42 from rhodecode.lib.caching_query import FromCache
42 43 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 46 UserEmailMap, UserIpMap, UserLog)
47 47 from rhodecode.model.meta import Session
48 from rhodecode.model.auth_token import AuthTokenModel
48 49 from rhodecode.model.repo_group import RepoGroupModel
49 50
50
51 51 log = logging.getLogger(__name__)
52 52
53 53
@@ -261,7 +261,7 b' class UserModel(BaseModel):'
261 261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
262 262
263 263 from rhodecode.lib.auth import (
264 get_crypt_password, check_password, generate_auth_token)
264 get_crypt_password, check_password)
265 265 from rhodecode.lib.hooks_base import (
266 266 log_create_user, check_allowed_create_user)
267 267
@@ -443,15 +443,16 b' class UserModel(BaseModel):'
443 443 log.error(traceback.format_exc())
444 444 raise
445 445
446 def _handle_user_repos(self, username, repositories, handle_mode=None):
447 _superadmin = self.cls.get_first_super_admin()
446 def _handle_user_repos(self, username, repositories, handle_user,
447 handle_mode=None):
448
448 449 left_overs = True
449 450
450 451 from rhodecode.model.repo import RepoModel
451 452
452 453 if handle_mode == 'detach':
453 454 for obj in repositories:
454 obj.user = _superadmin
455 obj.user = handle_user
455 456 # set description we know why we super admin now owns
456 457 # additional repositories that were orphaned !
457 458 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
@@ -465,16 +466,16 b' class UserModel(BaseModel):'
465 466 # if nothing is done we have left overs left
466 467 return left_overs
467 468
468 def _handle_user_repo_groups(self, username, repository_groups,
469 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
469 470 handle_mode=None):
470 _superadmin = self.cls.get_first_super_admin()
471
471 472 left_overs = True
472 473
473 474 from rhodecode.model.repo_group import RepoGroupModel
474 475
475 476 if handle_mode == 'detach':
476 477 for r in repository_groups:
477 r.user = _superadmin
478 r.user = handle_user
478 479 # set description we know why we super admin now owns
479 480 # additional repositories that were orphaned !
480 481 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
@@ -489,8 +490,9 b' class UserModel(BaseModel):'
489 490 # if nothing is done we have left overs left
490 491 return left_overs
491 492
492 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
493 _superadmin = self.cls.get_first_super_admin()
493 def _handle_user_user_groups(self, username, user_groups, handle_user,
494 handle_mode=None):
495
494 496 left_overs = True
495 497
496 498 from rhodecode.model.user_group import UserGroupModel
@@ -499,8 +501,8 b' class UserModel(BaseModel):'
499 501 for r in user_groups:
500 502 for user_user_group_to_perm in r.user_user_group_to_perm:
501 503 if user_user_group_to_perm.user.username == username:
502 user_user_group_to_perm.user = _superadmin
503 r.user = _superadmin
504 user_user_group_to_perm.user = handle_user
505 r.user = handle_user
504 506 # set description we know why we super admin now owns
505 507 # additional repositories that were orphaned !
506 508 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
@@ -514,13 +516,37 b' class UserModel(BaseModel):'
514 516 # if nothing is done we have left overs left
515 517 return left_overs
516 518
517 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
518 _superadmin = self.cls.get_first_super_admin()
519 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
520 handle_mode=None):
521 left_overs = True
522
523 from rhodecode.model.pull_request import PullRequestModel
524
525 if handle_mode == 'detach':
526 for pr in pull_requests:
527 pr.user_id = handle_user.user_id
528 # set description we know why we super admin now owns
529 # additional repositories that were orphaned !
530 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
531 self.sa.add(pr)
532 left_overs = False
533 elif handle_mode == 'delete':
534 for pr in pull_requests:
535 PullRequestModel().delete(pr)
536
537 left_overs = False
538
539 # if nothing is done we have left overs left
540 return left_overs
541
542 def _handle_user_artifacts(self, username, artifacts, handle_user,
543 handle_mode=None):
544
519 545 left_overs = True
520 546
521 547 if handle_mode == 'detach':
522 548 for a in artifacts:
523 a.upload_user = _superadmin
549 a.upload_user = handle_user
524 550 # set description we know why we super admin now owns
525 551 # additional artifacts that were orphaned !
526 552 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
@@ -528,7 +554,8 b' class UserModel(BaseModel):'
528 554 left_overs = False
529 555 elif handle_mode == 'delete':
530 556 from rhodecode.apps.file_store import utils as store_utils
531 storage = store_utils.get_file_storage(self.request.registry.settings)
557 request = get_current_request()
558 storage = store_utils.get_file_storage(request.registry.settings)
532 559 for a in artifacts:
533 560 file_uid = a.file_uid
534 561 storage.delete(file_uid)
@@ -540,11 +567,13 b' class UserModel(BaseModel):'
540 567 return left_overs
541 568
542 569 def delete(self, user, cur_user=None, handle_repos=None,
543 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
570 handle_repo_groups=None, handle_user_groups=None,
571 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
544 572 from rhodecode.lib.hooks_base import log_delete_user
545 573
546 574 if not cur_user:
547 575 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
576
548 577 user = self._get_user(user)
549 578
550 579 try:
@@ -552,9 +581,11 b' class UserModel(BaseModel):'
552 581 raise DefaultUserException(
553 582 u"You can't remove this user since it's"
554 583 u" crucial for entire application")
584 handle_user = handle_new_owner or self.cls.get_first_super_admin()
585 log.debug('New detached objects owner %s', handle_user)
555 586
556 587 left_overs = self._handle_user_repos(
557 user.username, user.repositories, handle_repos)
588 user.username, user.repositories, handle_user, handle_repos)
558 589 if left_overs and user.repositories:
559 590 repos = [x.repo_name for x in user.repositories]
560 591 raise UserOwnsReposException(
@@ -564,7 +595,7 b' class UserModel(BaseModel):'
564 595 'list_repos': ', '.join(repos)})
565 596
566 597 left_overs = self._handle_user_repo_groups(
567 user.username, user.repository_groups, handle_repo_groups)
598 user.username, user.repository_groups, handle_user, handle_repo_groups)
568 599 if left_overs and user.repository_groups:
569 600 repo_groups = [x.group_name for x in user.repository_groups]
570 601 raise UserOwnsRepoGroupsException(
@@ -574,7 +605,7 b' class UserModel(BaseModel):'
574 605 'list_repo_groups': ', '.join(repo_groups)})
575 606
576 607 left_overs = self._handle_user_user_groups(
577 user.username, user.user_groups, handle_user_groups)
608 user.username, user.user_groups, handle_user, handle_user_groups)
578 609 if left_overs and user.user_groups:
579 610 user_groups = [x.users_group_name for x in user.user_groups]
580 611 raise UserOwnsUserGroupsException(
@@ -582,8 +613,17 b' class UserModel(BaseModel):'
582 613 u'removed. Switch owners or remove those user groups:%s'
583 614 % (user.username, len(user_groups), ', '.join(user_groups)))
584 615
616 left_overs = self._handle_user_pull_requests(
617 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
618 if left_overs and user.user_pull_requests:
619 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
620 raise UserOwnsPullRequestsException(
621 u'user "%s" still owns %s pull requests and cannot be '
622 u'removed. Switch owners or remove those pull requests:%s'
623 % (user.username, len(pull_requests), ', '.join(pull_requests)))
624
585 625 left_overs = self._handle_user_artifacts(
586 user.username, user.artifacts, handle_artifacts)
626 user.username, user.artifacts, handle_user, handle_artifacts)
587 627 if left_overs and user.artifacts:
588 628 artifacts = [x.file_uid for x in user.artifacts]
589 629 raise UserOwnsArtifactsException(
@@ -878,7 +918,7 b' class UserModel(BaseModel):'
878 918 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
879 919 parsed_ip_range = []
880 920
881 for index in xrange(int(start_ip), int(end_ip) + 1):
921 for index in range(int(start_ip), int(end_ip) + 1):
882 922 new_ip = ipaddress.ip_address(index)
883 923 parsed_ip_range.append(str(new_ip))
884 924 ip_list.extend(parsed_ip_range)
@@ -149,6 +149,18 b''
149 149
150 150 <tr>
151 151 <td>
152 ${_ungettext('This user owns %s pull request.', 'This user owns %s pull requests.', len(c.user.user_pull_requests)) % len(c.user.user_pull_requests)}
153 </td>
154 <td>
155 <input type="radio" id="user_pull_requests_1" name="user_pull_requests" value="detach" checked="checked" ${'disabled=1' if len(c.user.user_pull_requests) == 0 else ''}/> <label for="user_pull_requests_1">${_('Detach pull requests')}</label>
156 </td>
157 <td>
158 <input type="radio" id="user_pull_requests_2" name="user_pull_requests" value="delete" ${'disabled=1' if len(c.user.user_pull_requests) == 0 else ''}/> <label for="user_pull_requests_2">${_('Delete pull requests')}</label>
159 </td>
160 </tr>
161
162 <tr>
163 <td>
152 164 ${_ungettext('This user owns %s artifact.', 'This user owns %s artifacts.', len(c.user.artifacts)) % len(c.user.artifacts)}
153 165 </td>
154 166 <td>
@@ -166,7 +178,8 b''
166 178 % endif
167 179
168 180 <span style="padding: 0 5px 0 0">${_('New owner for detached objects')}:</span>
169 <div class="pull-right">${base.gravatar_with_user(c.first_admin.email, 16)}</div>
181 <div class="pull-right">${base.gravatar_with_user(c.detach_user.email, 16, tooltip=True)}</div>
182 <input type="hidden" name="detach_user_id" value="${c.detach_user.user_id}">
170 183 </div>
171 184 <div style="clear: both">
172 185
@@ -186,11 +199,11 b''
186 199 <div style="margin: 0 0 20px 0" class="fake-space"></div>
187 200
188 201 <div class="field">
189 <button class="btn btn-small btn-danger" type="submit"
190 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
191 ${"disabled" if not c.can_delete_user else ""}>
192 ${_('Delete this user')}
193 </button>
202 <input class="btn btn-small btn-danger" id="remove_user" name="remove_user"
203 onclick="submitConfirm(event, this, _gettext('Confirm to delete this user'), _gettext('Confirm Delete'), '${c.user.username}')"
204 ${("disabled=1" if not c.can_delete_user else "")}
205 type="submit" value="${_('Delete this user')}"
206 >
194 207 </div>
195 208
196 209 ${h.end_form()}
General Comments 0
You need to be logged in to leave comments. Login now