Show More
@@ -517,7 +517,7 b' class TestAdminUsersView(TestController)' | |||
|
517 | 517 | route_path('user_delete', user_id=new_user.user_id), |
|
518 | 518 | params={'csrf_token': self.csrf_token}) |
|
519 | 519 | |
|
520 | assert_session_flash(response, 'Successfully deleted user') | |
|
520 | assert_session_flash(response, 'Successfully deleted user `{}`'.format(username)) | |
|
521 | 521 | |
|
522 | 522 | def test_delete_owner_of_repository(self, request, user_util): |
|
523 | 523 | self.log_user() |
@@ -376,59 +376,69 b' class UsersView(UserAppView):' | |||
|
376 | 376 | _repos = c.user.repositories |
|
377 | 377 | _repo_groups = c.user.repository_groups |
|
378 | 378 | _user_groups = c.user.user_groups |
|
379 | _artifacts = c.user.artifacts | |
|
379 | 380 | |
|
380 | 381 | handle_repos = None |
|
381 | 382 | handle_repo_groups = None |
|
382 | 383 | handle_user_groups = None |
|
383 | # dummy call for flash of handle | |
|
384 | set_handle_flash_repos = lambda: None | |
|
385 | set_handle_flash_repo_groups = lambda: None | |
|
386 |
|
|
|
384 | handle_artifacts = None | |
|
385 | ||
|
386 | # calls for flash of handle based on handle case detach or delete | |
|
387 | def set_handle_flash_repos(): | |
|
388 | handle = handle_repos | |
|
389 | if handle == 'detach': | |
|
390 | h.flash(_('Detached %s repositories') % len(_repos), | |
|
391 | category='success') | |
|
392 | elif handle == 'delete': | |
|
393 | h.flash(_('Deleted %s repositories') % len(_repos), | |
|
394 | category='success') | |
|
395 | ||
|
396 | def set_handle_flash_repo_groups(): | |
|
397 | handle = handle_repo_groups | |
|
398 | if handle == 'detach': | |
|
399 | h.flash(_('Detached %s repository groups') % len(_repo_groups), | |
|
400 | category='success') | |
|
401 | elif handle == 'delete': | |
|
402 | h.flash(_('Deleted %s repository groups') % len(_repo_groups), | |
|
403 | category='success') | |
|
404 | ||
|
405 | def set_handle_flash_user_groups(): | |
|
406 | handle = handle_user_groups | |
|
407 | if handle == 'detach': | |
|
408 | h.flash(_('Detached %s user groups') % len(_user_groups), | |
|
409 | category='success') | |
|
410 | elif handle == 'delete': | |
|
411 | h.flash(_('Deleted %s user groups') % len(_user_groups), | |
|
412 | category='success') | |
|
413 | ||
|
414 | def set_handle_flash_artifacts(): | |
|
415 | handle = handle_artifacts | |
|
416 | if handle == 'detach': | |
|
417 | h.flash(_('Detached %s artifacts') % len(_artifacts), | |
|
418 | category='success') | |
|
419 | elif handle == 'delete': | |
|
420 | h.flash(_('Deleted %s artifacts') % len(_artifacts), | |
|
421 | category='success') | |
|
387 | 422 | |
|
388 | 423 | if _repos and self.request.POST.get('user_repos'): |
|
389 | do = self.request.POST['user_repos'] | |
|
390 | if do == 'detach': | |
|
391 | handle_repos = 'detach' | |
|
392 | set_handle_flash_repos = lambda: h.flash( | |
|
393 | _('Detached %s repositories') % len(_repos), | |
|
394 | category='success') | |
|
395 | elif do == 'delete': | |
|
396 | handle_repos = 'delete' | |
|
397 | set_handle_flash_repos = lambda: h.flash( | |
|
398 | _('Deleted %s repositories') % len(_repos), | |
|
399 | category='success') | |
|
424 | handle_repos = self.request.POST['user_repos'] | |
|
400 | 425 | |
|
401 | 426 | if _repo_groups and self.request.POST.get('user_repo_groups'): |
|
402 | do = self.request.POST['user_repo_groups'] | |
|
403 | if do == 'detach': | |
|
404 | handle_repo_groups = 'detach' | |
|
405 | set_handle_flash_repo_groups = lambda: h.flash( | |
|
406 | _('Detached %s repository groups') % len(_repo_groups), | |
|
407 | category='success') | |
|
408 | elif do == 'delete': | |
|
409 | handle_repo_groups = 'delete' | |
|
410 | set_handle_flash_repo_groups = lambda: h.flash( | |
|
411 | _('Deleted %s repository groups') % len(_repo_groups), | |
|
412 | category='success') | |
|
427 | handle_repo_groups = self.request.POST['user_repo_groups'] | |
|
413 | 428 | |
|
414 | 429 | if _user_groups and self.request.POST.get('user_user_groups'): |
|
415 | do = self.request.POST['user_user_groups'] | |
|
416 | if do == 'detach': | |
|
417 | handle_user_groups = 'detach' | |
|
418 | set_handle_flash_user_groups = lambda: h.flash( | |
|
419 | _('Detached %s user groups') % len(_user_groups), | |
|
420 | category='success') | |
|
421 | elif do == 'delete': | |
|
422 | handle_user_groups = 'delete' | |
|
423 | set_handle_flash_user_groups = lambda: h.flash( | |
|
424 | _('Deleted %s user groups') % len(_user_groups), | |
|
425 | category='success') | |
|
430 | handle_user_groups = self.request.POST['user_user_groups'] | |
|
431 | ||
|
432 | if _artifacts and self.request.POST.get('user_artifacts'): | |
|
433 | handle_artifacts = self.request.POST['user_artifacts'] | |
|
426 | 434 | |
|
427 | 435 | old_values = c.user.get_api_data() |
|
436 | ||
|
428 | 437 | try: |
|
429 | 438 | UserModel().delete(c.user, handle_repos=handle_repos, |
|
430 | 439 | handle_repo_groups=handle_repo_groups, |
|
431 |
handle_user_groups=handle_user_groups |
|
|
440 | handle_user_groups=handle_user_groups, | |
|
441 | handle_artifacts=handle_artifacts) | |
|
432 | 442 | |
|
433 | 443 | audit_logger.store_web( |
|
434 | 444 | 'user.delete', action_data={'old_data': old_values}, |
@@ -438,7 +448,9 b' class UsersView(UserAppView):' | |||
|
438 | 448 | set_handle_flash_repos() |
|
439 | 449 | set_handle_flash_repo_groups() |
|
440 | 450 | set_handle_flash_user_groups() |
|
441 | h.flash(_('Successfully deleted user'), category='success') | |
|
451 | set_handle_flash_artifacts() | |
|
452 | username = h.escape(old_values['username']) | |
|
453 | h.flash(_('Successfully deleted user `{}`').format(username), category='success') | |
|
442 | 454 | except (UserOwnsReposException, UserOwnsRepoGroupsException, |
|
443 | 455 | UserOwnsUserGroupsException, DefaultUserException) as e: |
|
444 | 456 | h.flash(e, category='warning') |
@@ -58,6 +58,10 b' class UserOwnsUserGroupsException(Except' | |||
|
58 | 58 | pass |
|
59 | 59 | |
|
60 | 60 | |
|
61 | class UserOwnsArtifactsException(Exception): | |
|
62 | pass | |
|
63 | ||
|
64 | ||
|
61 | 65 | class UserGroupAssignedException(Exception): |
|
62 | 66 | pass |
|
63 | 67 |
@@ -617,13 +617,19 b' class User(Base, BaseModel):' | |||
|
617 | 617 | # user pull requests |
|
618 | 618 | user_pull_requests = relationship('PullRequest', cascade='all') |
|
619 | 619 | # external identities |
|
620 | extenal_identities = relationship( | |
|
620 | external_identities = relationship( | |
|
621 | 621 | 'ExternalIdentity', |
|
622 | 622 | primaryjoin="User.user_id==ExternalIdentity.local_user_id", |
|
623 | 623 | cascade='all') |
|
624 | 624 | # review rules |
|
625 | 625 | user_review_rules = relationship('RepoReviewRuleUser', cascade='all') |
|
626 | 626 | |
|
627 | # artifacts owned | |
|
628 | artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id') | |
|
629 | ||
|
630 | # no cascade, set NULL | |
|
631 | scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id') | |
|
632 | ||
|
627 | 633 | def __unicode__(self): |
|
628 | 634 | return u"<%s('id:%s:%s')>" % (self.__class__.__name__, |
|
629 | 635 | self.user_id, self.username) |
@@ -1704,7 +1710,8 b' class Repository(Base, BaseModel):' | |||
|
1704 | 1710 | |
|
1705 | 1711 | scoped_tokens = relationship('UserApiKeys', cascade="all") |
|
1706 | 1712 | |
|
1707 | artifacts = relationship('FileStore', cascade="all") | |
|
1713 | # no cascade, set NULL | |
|
1714 | artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id') | |
|
1708 | 1715 | |
|
1709 | 1716 | def __unicode__(self): |
|
1710 | 1717 | return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, |
@@ -2579,6 +2586,9 b' class RepoGroup(Base, BaseModel):' | |||
|
2579 | 2586 | user = relationship('User') |
|
2580 | 2587 | integrations = relationship('Integration', cascade="all, delete-orphan") |
|
2581 | 2588 | |
|
2589 | # no cascade, set NULL | |
|
2590 | scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id') | |
|
2591 | ||
|
2582 | 2592 | def __init__(self, group_name='', parent_group=None): |
|
2583 | 2593 | self.group_name = group_name |
|
2584 | 2594 | self.parent_group = parent_group |
@@ -3870,6 +3880,7 b' class _SetState(object):' | |||
|
3870 | 3880 | log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state) |
|
3871 | 3881 | raise |
|
3872 | 3882 | |
|
3883 | ||
|
3873 | 3884 | class _PullRequestBase(BaseModel): |
|
3874 | 3885 | """ |
|
3875 | 3886 | Common attributes of pull request and version entries. |
@@ -4148,14 +4159,10 b' class PullRequest(Base, _PullRequestBase' | |||
|
4148 | 4159 | else: |
|
4149 | 4160 | return '<DB:PullRequest at %#x>' % id(self) |
|
4150 | 4161 | |
|
4151 | reviewers = relationship('PullRequestReviewers', | |
|
4152 | cascade="all, delete-orphan") | |
|
4153 | statuses = relationship('ChangesetStatus', | |
|
4154 | cascade="all, delete-orphan") | |
|
4155 | comments = relationship('ChangesetComment', | |
|
4156 | cascade="all, delete-orphan") | |
|
4157 | versions = relationship('PullRequestVersion', | |
|
4158 | cascade="all, delete-orphan", | |
|
4162 | reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan") | |
|
4163 | statuses = relationship('ChangesetStatus', cascade="all, delete-orphan") | |
|
4164 | comments = relationship('ChangesetComment', cascade="all, delete-orphan") | |
|
4165 | versions = relationship('PullRequestVersion', cascade="all, delete-orphan", | |
|
4159 | 4166 | lazy='dynamic') |
|
4160 | 4167 | |
|
4161 | 4168 | @classmethod |
@@ -37,7 +37,7 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) | |
|
40 | UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException) | |
|
41 | 41 | from rhodecode.lib.caching_query import FromCache |
|
42 | 42 | from rhodecode.model import BaseModel |
|
43 | 43 | from rhodecode.model.auth_token import AuthTokenModel |
@@ -505,8 +505,33 b' class UserModel(BaseModel):' | |||
|
505 | 505 | # if nothing is done we have left overs left |
|
506 | 506 | return left_overs |
|
507 | 507 | |
|
508 | def _handle_user_artifacts(self, username, artifacts, handle_mode=None): | |
|
509 | _superadmin = self.cls.get_first_super_admin() | |
|
510 | left_overs = True | |
|
511 | ||
|
512 | if handle_mode == 'detach': | |
|
513 | for a in artifacts: | |
|
514 | a.upload_user = _superadmin | |
|
515 | # set description we know why we super admin now owns | |
|
516 | # additional artifacts that were orphaned ! | |
|
517 | a.file_description += ' \n::detached artifact from deleted user: %s' % (username,) | |
|
518 | self.sa.add(a) | |
|
519 | left_overs = False | |
|
520 | elif handle_mode == 'delete': | |
|
521 | from rhodecode.apps.file_store import utils as store_utils | |
|
522 | storage = store_utils.get_file_storage(self.request.registry.settings) | |
|
523 | for a in artifacts: | |
|
524 | file_uid = a.file_uid | |
|
525 | storage.delete(file_uid) | |
|
526 | self.sa.delete(a) | |
|
527 | ||
|
528 | left_overs = False | |
|
529 | ||
|
530 | # if nothing is done we have left overs left | |
|
531 | return left_overs | |
|
532 | ||
|
508 | 533 | def delete(self, user, cur_user=None, handle_repos=None, |
|
509 | handle_repo_groups=None, handle_user_groups=None): | |
|
534 | handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None): | |
|
510 | 535 | from rhodecode.lib.hooks_base import log_delete_user |
|
511 | 536 | |
|
512 | 537 | if not cur_user: |
@@ -548,6 +573,15 b' class UserModel(BaseModel):' | |||
|
548 | 573 | u'removed. Switch owners or remove those user groups:%s' |
|
549 | 574 | % (user.username, len(user_groups), ', '.join(user_groups))) |
|
550 | 575 | |
|
576 | left_overs = self._handle_user_artifacts( | |
|
577 | user.username, user.artifacts, handle_artifacts) | |
|
578 | if left_overs and user.artifacts: | |
|
579 | artifacts = [x.file_uid for x in user.artifacts] | |
|
580 | raise UserOwnsArtifactsException( | |
|
581 | u'user "%s" still owns %s artifacts and cannot be ' | |
|
582 | u'removed. Switch owners or remove those artifacts:%s' | |
|
583 | % (user.username, len(artifacts), ', '.join(artifacts))) | |
|
584 | ||
|
551 | 585 | user_data = user.get_dict() # fetch user data before expire |
|
552 | 586 | |
|
553 | 587 | # we might change the user data with detach/delete, make sure |
@@ -13,6 +13,8 b'' | |||
|
13 | 13 | (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]), |
|
14 | 14 | (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]), |
|
15 | 15 | |
|
16 | (_('Owned Artifacts'), len(c.user.artifacts), '', [x.file_uid for x in c.user.artifacts]), | |
|
17 | ||
|
16 | 18 | (_('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]), |
|
17 | 19 | (_('Assigned to review rules'), len(c.user_to_review_rules), '', [x for x in c.user_to_review_rules]), |
|
18 | 20 | |
@@ -23,10 +25,19 b'' | |||
|
23 | 25 | |
|
24 | 26 | <div class="panel panel-default"> |
|
25 | 27 | <div class="panel-heading"> |
|
26 |
<h3 class="panel-title">${_('User: |
|
|
28 | <h3 class="panel-title">${_('User: {}').format(c.user.username)}</h3> | |
|
27 | 29 | </div> |
|
28 | 30 | <div class="panel-body"> |
|
29 | ${base.dt_info_panel(elems)} | |
|
31 | <table class="rctable"> | |
|
32 | <tr> | |
|
33 | <th>Name</th> | |
|
34 | <th>Value</th> | |
|
35 | <th>Action</th> | |
|
36 | </tr> | |
|
37 | % for elem in elems: | |
|
38 | ${base.tr_info_entry(elem)} | |
|
39 | % endfor | |
|
40 | </table> | |
|
30 | 41 | </div> |
|
31 | 42 | </div> |
|
32 | 43 | |
@@ -132,6 +143,19 b'' | |||
|
132 | 143 | <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" ${'disabled=1' if len(c.user.user_groups) == 0 else ''}/> <label for="user_user_groups_2">${_('Delete repositories')}</label> |
|
133 | 144 | </td> |
|
134 | 145 | </tr> |
|
146 | ||
|
147 | <tr> | |
|
148 | <td> | |
|
149 | ${_ungettext('This user owns %s artifact.', 'This user owns %s artifacts.', len(c.user.artifacts)) % len(c.user.artifacts)} | |
|
150 | </td> | |
|
151 | <td> | |
|
152 | <input type="radio" id="user_artifacts_1" name="user_artifacts" value="detach" checked="checked" ${'disabled=1' if len(c.user.artifacts) == 0 else ''}/> <label for="user_artifacts_1">${_('Detach Artifacts')}</label> | |
|
153 | </td> | |
|
154 | <td> | |
|
155 | <input type="radio" id="user_artifacts_2" name="user_artifacts" value="delete" ${'disabled=1' if len(c.user.artifacts) == 0 else ''}/> <label for="user_artifacts_2">${_('Delete Artifacts')}</label> | |
|
156 | </td> | |
|
157 | </tr> | |
|
158 | ||
|
135 | 159 | </table> |
|
136 | 160 | <div style="margin: 0 0 20px 0" class="fake-space"></div> |
|
137 | 161 | <div class="pull-left"> |
@@ -164,6 +164,36 b'' | |||
|
164 | 164 | </dl> |
|
165 | 165 | </%def> |
|
166 | 166 | |
|
167 | <%def name="tr_info_entry(element)"> | |
|
168 | <% key, val, title, show_items = element %> | |
|
169 | ||
|
170 | <tr> | |
|
171 | <td style="vertical-align: top">${key}</td> | |
|
172 | <td title="${h.tooltip(title)}"> | |
|
173 | %if callable(val): | |
|
174 | ## allow lazy evaluation of elements | |
|
175 | ${val()} | |
|
176 | %else: | |
|
177 | ${val} | |
|
178 | %endif | |
|
179 | %if show_items: | |
|
180 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none"> | |
|
181 | % for item in show_items: | |
|
182 | <dt></dt> | |
|
183 | <dd>${item}</dd> | |
|
184 | % endfor | |
|
185 | </div> | |
|
186 | %endif | |
|
187 | </td> | |
|
188 | <td style="vertical-align: top"> | |
|
189 | %if show_items: | |
|
190 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span> | |
|
191 | %endif | |
|
192 | </td> | |
|
193 | </tr> | |
|
194 | ||
|
195 | </%def> | |
|
196 | ||
|
167 | 197 | <%def name="gravatar(email, size=16)"> |
|
168 | 198 | <% |
|
169 | 199 | if (size > 16): |
General Comments 0
You need to be logged in to leave comments.
Login now