mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-11 03:41:12 +00:00
update inactive user (#6019)
* update inactive user * is_active del RepoAPITokens * update del_repo_api_token * add inactive_user * [user admin] add confirmation to 'set user inactive' * system admin - users * system admin - organizations - org - member * org admin - users --------- Co-authored-by: llj <lingjun.li1@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ const propTypes = {
|
|||||||
currentSelectedOption: PropTypes.object.isRequired,
|
currentSelectedOption: PropTypes.object.isRequired,
|
||||||
options: PropTypes.array.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
selectOption: PropTypes.func.isRequired,
|
selectOption: PropTypes.func.isRequired,
|
||||||
|
operationBeforeSelect: PropTypes.func,
|
||||||
toggleItemFreezed: PropTypes.func
|
toggleItemFreezed: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +53,11 @@ class Selector extends Component {
|
|||||||
|
|
||||||
selectItem = (e, targetItem) => {
|
selectItem = (e, targetItem) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.selectOption(targetItem);
|
if (this.props.operationBeforeSelect) {
|
||||||
|
this.props.operationBeforeSelect();
|
||||||
|
} else {
|
||||||
|
this.props.selectOption(targetItem);
|
||||||
|
}
|
||||||
this.togglePopover();
|
this.togglePopover();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ import { seafileAPI } from '../../utils/seafile-api';
|
|||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Selector from '../../components/single-selector';
|
import Selector from '../../components/single-selector';
|
||||||
|
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
@@ -84,6 +85,11 @@ class UserItem extends React.Component {
|
|||||||
this.props.changeStatus(this.props.user.email, isActive);
|
this.props.changeStatus(this.props.user.email, isActive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setUserInactive = () => {
|
||||||
|
const isActive = false;
|
||||||
|
this.props.changeStatus(this.props.user.email, isActive);
|
||||||
|
};
|
||||||
|
|
||||||
onDropdownToggleClick = (e) => {
|
onDropdownToggleClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.toggleOperationMenu(e);
|
this.toggleOperationMenu(e);
|
||||||
@@ -126,8 +132,12 @@ class UserItem extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleConfirmInactiveDialog= () => {
|
||||||
|
this.setState({isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { highlight } = this.state;
|
const { highlight, isConfirmInactiveDialogOpen } = this.state;
|
||||||
let { user, currentTab } = this.props;
|
let { user, currentTab } = this.props;
|
||||||
let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/';
|
let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/';
|
||||||
let isOperationMenuShow = (user.email !== username) && this.state.showMenu;
|
let isOperationMenuShow = (user.email !== username) && this.state.showMenu;
|
||||||
@@ -143,47 +153,62 @@ class UserItem extends React.Component {
|
|||||||
});
|
});
|
||||||
const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0];
|
const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0];
|
||||||
|
|
||||||
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(user.name) + '</span>';
|
||||||
|
const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<>
|
||||||
<td>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<a href={href}>{user.name}</a>
|
<td>
|
||||||
</td>
|
<a href={href}>{user.name}</a>
|
||||||
<td>
|
</td>
|
||||||
<Selector
|
<td>
|
||||||
isDropdownToggleShown={highlight}
|
<Selector
|
||||||
currentSelectedOption={currentSelectedStatusOption}
|
isDropdownToggleShown={highlight}
|
||||||
options={this.statusOptions}
|
currentSelectedOption={currentSelectedStatusOption}
|
||||||
selectOption={this.changeStatus}
|
options={this.statusOptions}
|
||||||
toggleItemFreezed={this.props.toggleItemFreezed}
|
selectOption={this.changeStatus}
|
||||||
/>
|
toggleItemFreezed={this.props.toggleItemFreezed}
|
||||||
</td>
|
operationBeforeSelect={user.is_active ? this.toggleConfirmInactiveDialog : undefined}
|
||||||
<td>{`${Utils.formatSize({bytes: user.quota_usage})} / ${this.getQuotaTotal(user.quota_total)}`}</td>
|
/>
|
||||||
<td>
|
</td>
|
||||||
{user.ctime} /
|
<td>{`${Utils.formatSize({bytes: user.quota_usage})} / ${this.getQuotaTotal(user.quota_total)}`}</td>
|
||||||
<br />
|
<td>
|
||||||
{user.last_login ? user.last_login : '--'}
|
{user.ctime} /
|
||||||
</td>
|
<br />
|
||||||
<td className="text-center cursor-pointer">
|
{user.last_login ? user.last_login : '--'}
|
||||||
{isOperationMenuShow && (
|
</td>
|
||||||
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
<td className="text-center cursor-pointer">
|
||||||
<DropdownToggle
|
{isOperationMenuShow && (
|
||||||
tag="a"
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
className="attr-action-icon fas fa-ellipsis-v"
|
<DropdownToggle
|
||||||
title={gettext('More operations')}
|
tag="a"
|
||||||
aria-label={gettext('More operations')}
|
className="attr-action-icon fas fa-ellipsis-v"
|
||||||
data-toggle="dropdown"
|
title={gettext('More operations')}
|
||||||
aria-expanded={this.state.isItemMenuShow}
|
aria-label={gettext('More operations')}
|
||||||
onClick={this.onDropdownToggleClick}
|
data-toggle="dropdown"
|
||||||
/>
|
aria-expanded={this.state.isItemMenuShow}
|
||||||
<DropdownMenu>
|
onClick={this.onDropdownToggleClick}
|
||||||
<DropdownItem onClick={this.toggleDelete}>{gettext('Delete')}</DropdownItem>
|
/>
|
||||||
<DropdownItem onClick={this.toggleResetPW}>{gettext('ResetPwd')}</DropdownItem>
|
<DropdownMenu>
|
||||||
{currentTab == 'admins' && <DropdownItem onClick={this.toggleRevokeAdmin}>{gettext('Revoke Admin')}</DropdownItem>}
|
<DropdownItem onClick={this.toggleDelete}>{gettext('Delete')}</DropdownItem>
|
||||||
</DropdownMenu>
|
<DropdownItem onClick={this.toggleResetPW}>{gettext('ResetPwd')}</DropdownItem>
|
||||||
</Dropdown>
|
{currentTab == 'admins' && <DropdownItem onClick={this.toggleRevokeAdmin}>{gettext('Revoke Admin')}</DropdownItem>}
|
||||||
)}
|
</DropdownMenu>
|
||||||
</td>
|
</Dropdown>
|
||||||
</tr>
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{isConfirmInactiveDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Set user inactive')}
|
||||||
|
message={confirmSetUserInactiveMsg}
|
||||||
|
executeOperation={this.setUserInactive}
|
||||||
|
confirmBtnText={gettext('Set')}
|
||||||
|
toggleDialog={this.toggleConfirmInactiveDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -158,10 +158,18 @@ class Item extends Component {
|
|||||||
this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen});
|
this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleConfirmInactiveDialog= () => {
|
||||||
|
this.setState({isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen});
|
||||||
|
};
|
||||||
|
|
||||||
updateStatus= (statusOption) => {
|
updateStatus= (statusOption) => {
|
||||||
this.props.updateStatus(this.props.item.email, statusOption.value);
|
this.props.updateStatus(this.props.item.email, statusOption.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setUserInactive = () => {
|
||||||
|
this.props.updateStatus(this.props.item.email, 'inactive');
|
||||||
|
};
|
||||||
|
|
||||||
updateMembership= (membershipOption) => {
|
updateMembership= (membershipOption) => {
|
||||||
this.props.updateMembership(this.props.item.email, membershipOption.value);
|
this.props.updateMembership(this.props.item.email, membershipOption.value);
|
||||||
};
|
};
|
||||||
@@ -214,11 +222,12 @@ class Item extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
const { highlight, isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state;
|
const { highlight, isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen, isConfirmInactiveDialogOpen } = this.state;
|
||||||
|
|
||||||
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||||
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
|
let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName);
|
||||||
|
|
||||||
// for 'user status'
|
// for 'user status'
|
||||||
const curStatus = item.active ? 'active' : 'inactive';
|
const curStatus = item.active ? 'active' : 'inactive';
|
||||||
@@ -253,6 +262,7 @@ class Item extends Component {
|
|||||||
options={this.statusOptions}
|
options={this.statusOptions}
|
||||||
selectOption={this.updateStatus}
|
selectOption={this.updateStatus}
|
||||||
toggleItemFreezed={this.props.toggleItemFreezed}
|
toggleItemFreezed={this.props.toggleItemFreezed}
|
||||||
|
operationBeforeSelect={item.active ? this.toggleConfirmInactiveDialog : undefined}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -298,6 +308,15 @@ class Item extends Component {
|
|||||||
toggleDialog={this.toggleResetPasswordDialog}
|
toggleDialog={this.toggleResetPasswordDialog}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{isConfirmInactiveDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Set user inactive')}
|
||||||
|
message={confirmSetUserInactiveMsg}
|
||||||
|
executeOperation={this.setUserInactive}
|
||||||
|
confirmBtnText={gettext('Set')}
|
||||||
|
toggleDialog={this.toggleConfirmInactiveDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -200,7 +200,8 @@ class Item extends Component {
|
|||||||
isSetQuotaDialogOpen: false,
|
isSetQuotaDialogOpen: false,
|
||||||
isDeleteUserDialogOpen: false,
|
isDeleteUserDialogOpen: false,
|
||||||
isResetUserPasswordDialogOpen: false,
|
isResetUserPasswordDialogOpen: false,
|
||||||
isRevokeAdminDialogOpen: false
|
isRevokeAdminDialogOpen: false,
|
||||||
|
isConfirmInactiveDialogOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +247,10 @@ class Item extends Component {
|
|||||||
this.setState({isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen});
|
this.setState({isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleConfirmInactiveDialog= () => {
|
||||||
|
this.setState({isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen});
|
||||||
|
};
|
||||||
|
|
||||||
onUserSelected = () => {
|
onUserSelected = () => {
|
||||||
this.props.onUserSelected(this.props.item);
|
this.props.onUserSelected(this.props.item);
|
||||||
};
|
};
|
||||||
@@ -258,6 +263,10 @@ class Item extends Component {
|
|||||||
this.props.updateUser(this.props.item.email, 'is_active', isActive);
|
this.props.updateUser(this.props.item.email, 'is_active', isActive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setUserInactive = () => {
|
||||||
|
this.props.updateUser(this.props.item.email, 'is_active', false);
|
||||||
|
};
|
||||||
|
|
||||||
updateRole = (roleOption) => {
|
updateRole = (roleOption) => {
|
||||||
this.props.updateUser(this.props.item.email, 'role', roleOption.value);
|
this.props.updateUser(this.props.item.email, 'role', roleOption.value);
|
||||||
};
|
};
|
||||||
@@ -386,13 +395,15 @@ class Item extends Component {
|
|||||||
isSetQuotaDialogOpen,
|
isSetQuotaDialogOpen,
|
||||||
isDeleteUserDialogOpen,
|
isDeleteUserDialogOpen,
|
||||||
isResetUserPasswordDialogOpen,
|
isResetUserPasswordDialogOpen,
|
||||||
isRevokeAdminDialogOpen
|
isRevokeAdminDialogOpen,
|
||||||
|
isConfirmInactiveDialogOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||||
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
|
const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName);
|
const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName);
|
||||||
|
|
||||||
// for 'user status'
|
// for 'user status'
|
||||||
const curStatus = item.is_active ? 'active' : 'inactive';
|
const curStatus = item.is_active ? 'active' : 'inactive';
|
||||||
@@ -473,6 +484,7 @@ class Item extends Component {
|
|||||||
options={this.statusOptions}
|
options={this.statusOptions}
|
||||||
selectOption={this.updateStatus}
|
selectOption={this.updateStatus}
|
||||||
toggleItemFreezed={this.props.toggleItemFreezed}
|
toggleItemFreezed={this.props.toggleItemFreezed}
|
||||||
|
operationBeforeSelect={item.is_active ? this.toggleConfirmInactiveDialog : undefined}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{isPro &&
|
{isPro &&
|
||||||
@@ -567,6 +579,15 @@ class Item extends Component {
|
|||||||
toggleDialog={this.toggleRevokeAdminDialog}
|
toggleDialog={this.toggleRevokeAdminDialog}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{isConfirmInactiveDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Set user inactive')}
|
||||||
|
message={confirmSetUserInactiveMsg}
|
||||||
|
executeOperation={this.setUserInactive}
|
||||||
|
confirmBtnText={gettext('Set')}
|
||||||
|
toggleDialog={this.toggleConfirmInactiveDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -282,6 +282,13 @@ class Account(APIView):
|
|||||||
'is_active invalid.')
|
'is_active invalid.')
|
||||||
|
|
||||||
user.is_active = is_active
|
user.is_active = is_active
|
||||||
|
if is_active == False:
|
||||||
|
# del tokens and personal repo api tokens (not department)
|
||||||
|
from seahub.utils import inactive_user
|
||||||
|
try:
|
||||||
|
inactive_user(email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to inactive_user %s: %s." % (email, e))
|
||||||
|
|
||||||
# update password
|
# update password
|
||||||
password = request.data.get("password", None)
|
password = request.data.get("password", None)
|
||||||
|
@@ -355,9 +355,18 @@ def update_user_info(request, user, password, is_active, is_staff, role,
|
|||||||
quota_total_mb, institution_name,
|
quota_total_mb, institution_name,
|
||||||
upload_rate_limit, download_rate_limit):
|
upload_rate_limit, download_rate_limit):
|
||||||
|
|
||||||
|
email = user.username
|
||||||
|
|
||||||
# update basic user info
|
# update basic user info
|
||||||
if is_active is not None:
|
if is_active is not None:
|
||||||
user.is_active = is_active
|
user.is_active = is_active
|
||||||
|
if is_active == False:
|
||||||
|
# del tokens and personal repo api tokens (not department)
|
||||||
|
from seahub.utils import inactive_user
|
||||||
|
try:
|
||||||
|
inactive_user(email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to inactive_user %s: %s." % (email, e))
|
||||||
|
|
||||||
if password:
|
if password:
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
@@ -368,8 +377,6 @@ def update_user_info(request, user, password, is_active, is_staff, role,
|
|||||||
# update user
|
# update user
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
email = user.username
|
|
||||||
|
|
||||||
# update additional user info
|
# update additional user info
|
||||||
if is_pro_version() and role:
|
if is_pro_version() and role:
|
||||||
User.objects.update_role(email, role)
|
User.objects.update_role(email, role)
|
||||||
|
@@ -442,6 +442,13 @@ class OrgAdminUser(APIView):
|
|||||||
|
|
||||||
user.is_active = is_active == 'true'
|
user.is_active = is_active == 'true'
|
||||||
user.save()
|
user.save()
|
||||||
|
if not is_active == 'true':
|
||||||
|
# del tokens and personal repo api tokens (not department)
|
||||||
|
from seahub.utils import inactive_user
|
||||||
|
try:
|
||||||
|
inactive_user(email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to inactive_user %s: %s." % (email, e))
|
||||||
|
|
||||||
# update quota_total
|
# update quota_total
|
||||||
quota_total_mb = request.data.get("quota_total", None)
|
quota_total_mb = request.data.get("quota_total", None)
|
||||||
|
@@ -18,7 +18,7 @@ from urllib.parse import urlparse, urljoin
|
|||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
import seaserv
|
import seaserv
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api, ccnet_api
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
@@ -1280,6 +1280,38 @@ def clear_token(username):
|
|||||||
TokenV2.objects.filter(user = username).delete()
|
TokenV2.objects.filter(user = username).delete()
|
||||||
seafile_api.delete_repo_tokens_by_email(username)
|
seafile_api.delete_repo_tokens_by_email(username)
|
||||||
|
|
||||||
|
|
||||||
|
def inactive_user(username):
|
||||||
|
# del tokens and personal repo api tokens (not department)
|
||||||
|
from seahub.repo_api_tokens.models import RepoAPITokens
|
||||||
|
try:
|
||||||
|
clear_token(username)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to delete tokens for user %s: %s." % (username, e))
|
||||||
|
try:
|
||||||
|
from seahub.settings import MULTI_TENANCY
|
||||||
|
except ImportError:
|
||||||
|
MULTI_TENANCY = False
|
||||||
|
try:
|
||||||
|
org_id = -1
|
||||||
|
if MULTI_TENANCY:
|
||||||
|
orgs = ccnet_api.get_orgs_by_user(username)
|
||||||
|
if orgs:
|
||||||
|
org = orgs[0]
|
||||||
|
org_id = org.org_id
|
||||||
|
if org_id > 0:
|
||||||
|
owned_repos = seafile_api.get_org_owned_repo_list(
|
||||||
|
org_id, username, ret_corrupted=True)
|
||||||
|
else:
|
||||||
|
owned_repos = seafile_api.get_owned_repo_list(
|
||||||
|
username, ret_corrupted=True)
|
||||||
|
owned_repo_ids = [item.repo_id for item in owned_repos]
|
||||||
|
RepoAPITokens.objects.filter(
|
||||||
|
repo_id__in=owned_repo_ids).delete()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to delete repo api tokens for user %s: %s." % (username, e))
|
||||||
|
|
||||||
|
|
||||||
def send_perm_audit_msg(etype, from_user, to, repo_id, path, perm):
|
def send_perm_audit_msg(etype, from_user, to, repo_id, path, perm):
|
||||||
"""Send repo permission audit msg.
|
"""Send repo permission audit msg.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user