mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-08 10:22:46 +00:00
org invite user (#5815)
This commit is contained in:
@@ -1,46 +1,88 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
import { Button, Modal, Input, ModalHeader, ModalBody, ModalFooter, Label, Form, InputGroup, InputGroupAddon, FormGroup } from 'reactstrap';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import toaster from '../toast';
|
|
||||||
import copy from '../copy-to-clipboard';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
toggle: PropTypes.func.isRequired,
|
toggle: PropTypes.func.isRequired,
|
||||||
invitationLink: PropTypes.string.isRequired
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
class InviteUserDialog extends React.Component {
|
class OrgAdminInviteUserDialog extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
email: '',
|
||||||
|
errMessage: '',
|
||||||
|
isAddingUser: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
copyLink = () => {
|
handleSubmit = () => {
|
||||||
copy(this.props.invitationLink);
|
let isValid = this.validateInputParams();
|
||||||
this.props.toggle();
|
if (isValid) {
|
||||||
const message = gettext('Internal link has been copied to clipboard');
|
let { email } = this.state;
|
||||||
toaster.success(message, {
|
this.setState({isAddingUser: true});
|
||||||
duration: 2
|
this.props.handleSubmit(email.trim());
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.handleSubmit(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inputEmail = (e) => {
|
||||||
|
let email = e.target.value.trim();
|
||||||
|
this.setState({email: email});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
validateInputParams() {
|
||||||
|
let errMessage;
|
||||||
|
let email = this.state.email.trim();
|
||||||
|
if (!email.length) {
|
||||||
|
errMessage = gettext('email is required');
|
||||||
|
this.setState({errMessage: errMessage});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
<ModalHeader toggle={this.props.toggle}>{gettext('Invite user')}</ModalHeader>
|
<ModalHeader toggle={this.toggle}>{gettext('Invite users')}</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>{gettext('Send the invitation link to the others, and they will be able to join the organization via scanning the QR code.')}</p>
|
<p>{gettext('You can enter multiple emails, separated by commas. An invitation link will be sent to each user.')}</p>
|
||||||
<p>{this.props.invitationLink}</p>
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Label for="emails">{gettext('Emails')}</Label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="emails"
|
||||||
|
placeholder={gettext('Emails, separated by \',\'')}
|
||||||
|
value={this.state.email || ''}
|
||||||
|
onChange={this.inputEmail}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
{this.state.errMessage && <Label className="err-message">{this.state.errMessage}</Label>}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="primary" onClick={this.copyLink}>{gettext('Copy')}</Button>
|
<Button color="primary" disabled={this.state.isAddingUser} onClick={this.handleSubmit} className={this.state.isAddingUser ? 'btn-loading' : ''}>{gettext('Submit')}</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InviteUserDialog.propTypes = propTypes;
|
OrgAdminInviteUserDialog.propTypes = propTypes;
|
||||||
|
|
||||||
export default InviteUserDialog;
|
export default OrgAdminInviteUserDialog;
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import copy from '../copy-to-clipboard';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
invitationLink: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class OrgAdminInviteUserViaWeiXinDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyLink = () => {
|
||||||
|
copy(this.props.invitationLink);
|
||||||
|
this.props.toggle();
|
||||||
|
const message = gettext('Internal link has been copied to clipboard');
|
||||||
|
toaster.success(message), {
|
||||||
|
duration: 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{'通过微信邀请用户'}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p>{'请将邀请链接发送给其他人,这样他们就可以通过扫描链接里的二维码来加入组织。'}</p>
|
||||||
|
<p>{this.props.invitationLink}</p>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.copyLink}>{gettext('Copy')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgAdminInviteUserViaWeiXinDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default OrgAdminInviteUserViaWeiXinDialog;
|
@@ -8,10 +8,11 @@ import ModalPortal from '../../components/modal-portal';
|
|||||||
import ImportOrgUsersDialog from '../../components/dialog/org-import-users-dialog';
|
import ImportOrgUsersDialog from '../../components/dialog/org-import-users-dialog';
|
||||||
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
|
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
|
||||||
import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog';
|
import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog';
|
||||||
|
import InviteUserViaWeiXinDialog from '../../components/dialog/org-admin-invite-user-via-weixin-dialog';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import OrgUserInfo from '../../models/org-user';
|
import OrgUserInfo from '../../models/org-user';
|
||||||
import { gettext, invitationLink, orgID, siteRoot } from '../../utils/constants';
|
import { gettext, invitationLink, orgID, siteRoot, orgEnableAdminInviteUser} from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
class Search extends React.Component {
|
class Search extends React.Component {
|
||||||
@@ -80,7 +81,8 @@ class OrgUsers extends Component {
|
|||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
isShowAddOrgUserDialog: false,
|
isShowAddOrgUserDialog: false,
|
||||||
isImportOrgUsersDialogOpen: false,
|
isImportOrgUsersDialogOpen: false,
|
||||||
isInviteUserDialogOpen: false
|
isInviteUserDialogOpen: false,
|
||||||
|
isInviteUserViaWeiXinDialogOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +132,10 @@ class OrgUsers extends Component {
|
|||||||
this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen});
|
this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleInviteUserViaWeiXinDialog = () => {
|
||||||
|
this.setState({isInviteUserViaWeiXinDialogOpen: !this.state.isInviteUserViaWeiXinDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
initOrgUsersData = (page) => {
|
initOrgUsersData = (page) => {
|
||||||
const { sortBy, sortOrder } = this.state;
|
const { sortBy, sortOrder } = this.state;
|
||||||
seafileAPI.orgAdminListOrgUsers(orgID, '', page, sortBy, sortOrder).then(res => {
|
seafileAPI.orgAdminListOrgUsers(orgID, '', page, sortBy, sortOrder).then(res => {
|
||||||
@@ -202,6 +208,33 @@ class OrgUsers extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inviteOrgUser = (emails) => {
|
||||||
|
seafileAPI.orgAdminInviteOrgUsers(orgID, emails.split(',')).then(res => {
|
||||||
|
this.toggleInviteUserDialog();
|
||||||
|
let users = res.data.success.map(user => {
|
||||||
|
return new OrgUserInfo(user);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
orgUsers: users.concat(this.state.orgUsers)
|
||||||
|
});
|
||||||
|
|
||||||
|
res.data.success.map(item => {
|
||||||
|
let msg = gettext('successfully sent email to %s.');
|
||||||
|
msg = msg.replace('%s', item.email);
|
||||||
|
toaster.success(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.data.failed.map(item => {
|
||||||
|
const msg = `${item.email}: ${item.error_msg}`;
|
||||||
|
toaster.danger(msg);
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
this.toggleInviteUserDialog();
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
changeStatus= (email, isActive) => {
|
changeStatus= (email, isActive) => {
|
||||||
seafileAPI.orgAdminChangeOrgUserStatus(orgID, email, isActive).then(res => {
|
seafileAPI.orgAdminChangeOrgUserStatus(orgID, email, isActive).then(res => {
|
||||||
let users = this.state.orgUsers.map(item => {
|
let users = this.state.orgUsers.map(item => {
|
||||||
@@ -234,12 +267,16 @@ class OrgUsers extends Component {
|
|||||||
let topbarChildren;
|
let topbarChildren;
|
||||||
topbarChildren = (
|
topbarChildren = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<button className="btn btn-secondary operation-item" onClick={this.toggleImportOrgUsersDialog}>{gettext('Import Users')}</button>
|
<button className="btn btn-secondary operation-item" onClick={this.toggleImportOrgUsersDialog}>{gettext('Import users')}</button>
|
||||||
<button className={topBtn} title={gettext('Add User')} onClick={this.toggleAddOrgUser}>
|
<button className={topBtn} title={gettext('Add user')} onClick={this.toggleAddOrgUser}>
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add User')}</button>
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add user')}</button>
|
||||||
|
{orgEnableAdminInviteUser &&
|
||||||
|
<button className={topBtn} title={gettext('Invite users')} onClick={this.toggleInviteUserDialog}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite users')}</button>
|
||||||
|
}
|
||||||
{invitationLink &&
|
{invitationLink &&
|
||||||
<button className={topBtn} title={gettext('Invite user')} onClick={this.toggleInviteUserDialog}>
|
<button className={topBtn} title={'通过微信邀请用户'} onClick={this.toggleInviteUserViaWeiXinDialog}>
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite user')}</button>
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{'通过微信邀请用户'}</button>
|
||||||
}
|
}
|
||||||
{this.state.isImportOrgUsersDialogOpen &&
|
{this.state.isImportOrgUsersDialogOpen &&
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
@@ -253,7 +290,12 @@ class OrgUsers extends Component {
|
|||||||
}
|
}
|
||||||
{this.state.isInviteUserDialogOpen &&
|
{this.state.isInviteUserDialogOpen &&
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<InviteUserDialog invitationLink={invitationLink} toggle={this.toggleInviteUserDialog}/>
|
<InviteUserDialog handleSubmit={this.inviteOrgUser} toggle={this.toggleInviteUserDialog}/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
|
{this.state.isInviteUserViaWeiXinDialogOpen &&
|
||||||
|
<ModalPortal>
|
||||||
|
<InviteUserViaWeiXinDialog invitationLink={invitationLink} toggle={this.toggleInviteUserViaWeiXinDialog}/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
}
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@@ -175,6 +175,15 @@ class Item extends Component {
|
|||||||
case 'Guest':
|
case 'Guest':
|
||||||
translateResult = gettext('Guest');
|
translateResult = gettext('Guest');
|
||||||
break;
|
break;
|
||||||
|
case 'guest':
|
||||||
|
translateResult = gettext('Guest');
|
||||||
|
break;
|
||||||
|
case 'Default':
|
||||||
|
translateResult = gettext('Default');
|
||||||
|
break;
|
||||||
|
case 'default':
|
||||||
|
translateResult = gettext('Default');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return translateResult;
|
return translateResult;
|
||||||
};
|
};
|
||||||
|
@@ -148,6 +148,7 @@ export const invitationLink = window.org ? window.org.pageOptions.invitationLink
|
|||||||
export const orgMemberQuotaEnabled = window.org ? window.org.pageOptions.orgMemberQuotaEnabled : '';
|
export const orgMemberQuotaEnabled = window.org ? window.org.pageOptions.orgMemberQuotaEnabled : '';
|
||||||
export const orgEnableAdminCustomLogo = window.org ? window.org.pageOptions.orgEnableAdminCustomLogo === 'True' : false;
|
export const orgEnableAdminCustomLogo = window.org ? window.org.pageOptions.orgEnableAdminCustomLogo === 'True' : false;
|
||||||
export const orgEnableAdminCustomName = window.org ? window.org.pageOptions.orgEnableAdminCustomName === 'True' : false;
|
export const orgEnableAdminCustomName = window.org ? window.org.pageOptions.orgEnableAdminCustomName === 'True' : false;
|
||||||
|
export const orgEnableAdminInviteUser = window.org ? window.org.pageOptions.orgEnableAdminInviteUser === 'True' : false;
|
||||||
export const enableMultiADFS = window.org ? window.org.pageOptions.enableMultiADFS === 'True' : false;
|
export const enableMultiADFS = window.org ? window.org.pageOptions.enableMultiADFS === 'True' : false;
|
||||||
|
|
||||||
// sys admin
|
// sys admin
|
||||||
|
@@ -10,13 +10,12 @@ from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
|
|||||||
from seahub.utils import gen_token, get_site_name
|
from seahub.utils import gen_token, get_site_name
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, \
|
||||||
|
GUEST_USER, DEFAULT_USER
|
||||||
GUEST = 'Guest'
|
|
||||||
|
|
||||||
|
|
||||||
class InvitationManager(models.Manager):
|
class InvitationManager(models.Manager):
|
||||||
def add(self, inviter, accepter, invite_type=GUEST):
|
def add(self, inviter, accepter, invite_type=GUEST_USER):
|
||||||
token = gen_token(max_length=32)
|
token = gen_token(max_length=32)
|
||||||
expire_at = timezone.now() + timedelta(hours=int(INVITATIONS_TOKEN_AGE))
|
expire_at = timezone.now() + timedelta(hours=int(INVITATIONS_TOKEN_AGE))
|
||||||
|
|
||||||
@@ -40,7 +39,8 @@ class InvitationManager(models.Manager):
|
|||||||
|
|
||||||
class Invitation(models.Model):
|
class Invitation(models.Model):
|
||||||
INVITE_TYPE_CHOICES = (
|
INVITE_TYPE_CHOICES = (
|
||||||
(GUEST, 'Guest'),
|
(GUEST_USER, 'guest'),
|
||||||
|
(DEFAULT_USER, 'default'),
|
||||||
)
|
)
|
||||||
|
|
||||||
token = models.CharField(max_length=40, db_index=True)
|
token = models.CharField(max_length=40, db_index=True)
|
||||||
@@ -48,7 +48,7 @@ class Invitation(models.Model):
|
|||||||
accepter = LowerCaseCharField(max_length=255)
|
accepter = LowerCaseCharField(max_length=255)
|
||||||
invite_type = models.CharField(max_length=20,
|
invite_type = models.CharField(max_length=20,
|
||||||
choices=INVITE_TYPE_CHOICES,
|
choices=INVITE_TYPE_CHOICES,
|
||||||
default=GUEST)
|
default=GUEST_USER)
|
||||||
invite_time = models.DateTimeField(auto_now_add=True)
|
invite_time = models.DateTimeField(auto_now_add=True)
|
||||||
accept_time = models.DateTimeField(null=True, blank=True)
|
accept_time = models.DateTimeField(null=True, blank=True)
|
||||||
expire_time = models.DateTimeField()
|
expire_time = models.DateTimeField()
|
||||||
@@ -77,12 +77,12 @@ class Invitation(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def is_guest(self):
|
def is_guest(self):
|
||||||
return self.invite_type == GUEST
|
return self.invite_type == GUEST_USER
|
||||||
|
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
return timezone.now() >= self.expire_time
|
return timezone.now() >= self.expire_time
|
||||||
|
|
||||||
def send_to(self, email=None):
|
def send_to(self, email=None, org_name=None):
|
||||||
"""
|
"""
|
||||||
Send an invitation email to ``email``.
|
Send an invitation email to ``email``.
|
||||||
"""
|
"""
|
||||||
@@ -94,6 +94,10 @@ class Invitation(models.Model):
|
|||||||
|
|
||||||
subject = _('You are invited to join %(site_name)s.') % {'site_name': get_site_name()}
|
subject = _('You are invited to join %(site_name)s.') % {'site_name': get_site_name()}
|
||||||
|
|
||||||
|
if org_name:
|
||||||
|
subject = _(f'You are invited to join team {org_name}.')
|
||||||
|
context['org_name'] = org_name
|
||||||
|
|
||||||
return send_html_email_with_dj_template(email,
|
return send_html_email_with_dj_template(email,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
dj_template='invitations/invitation_email.html',
|
dj_template='invitations/invitation_email.html',
|
||||||
|
@@ -9,7 +9,11 @@
|
|||||||
<p style="color:#121214;font-size:14px;">{% trans "Hi," %}</p>
|
<p style="color:#121214;font-size:14px;">{% trans "Hi," %}</p>
|
||||||
|
|
||||||
<p style="font-size:14px;color:#434144;">
|
<p style="font-size:14px;color:#434144;">
|
||||||
{% blocktrans with inviter_name=inviter|email2nickname %}{{ inviter_name }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %}
|
{% if org_name %}
|
||||||
|
{% blocktrans with org_name=org_name|escape %}You are invited to join team {{ org_name }}. Please click the link below:{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans with inviter_name=inviter|email2nickname|escape %}{{ inviter_name }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="{{ url_base }}{% url 'invitations:token_view' token %}" target="_blank">{{ url_base }}{% url 'invitations:token_view' token %}</a>
|
<a href="{{ url_base }}{% url 'invitations:token_view' token %}" target="_blank">{{ url_base }}{% url 'invitations:token_view' token %}</a>
|
||||||
|
@@ -26,18 +26,24 @@ from seahub.utils import is_valid_email, IS_EMAIL_CONFIGURED, \
|
|||||||
get_file_type_and_ext
|
get_file_type_and_ext
|
||||||
from seahub.utils.file_size import get_file_size_unit
|
from seahub.utils.file_size import get_file_size_unit
|
||||||
from seahub.utils.error_msg import file_type_error_msg
|
from seahub.utils.error_msg import file_type_error_msg
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
|
||||||
|
datetime_to_isoformat_timestr
|
||||||
from seahub.utils.licenseparse import user_number_over_limit
|
from seahub.utils.licenseparse import user_number_over_limit
|
||||||
from seahub.views.sysadmin import send_user_add_mail
|
from seahub.views.sysadmin import send_user_add_mail
|
||||||
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
||||||
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
|
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
|
||||||
|
from seahub.invitations.models import Invitation
|
||||||
|
from seahub.constants import DEFAULT_USER
|
||||||
|
|
||||||
from pysearpc import SearpcError
|
from pysearpc import SearpcError
|
||||||
|
|
||||||
import seahub.settings as settings
|
import seahub.settings as settings
|
||||||
from seahub.organizations.settings import ORG_MEMBER_QUOTA_ENABLED
|
from seahub.organizations.models import OrgMemberQuota
|
||||||
from seahub.organizations.views import get_org_user_self_usage, get_org_user_quota, \
|
from seahub.organizations.settings import ORG_MEMBER_QUOTA_ENABLED, \
|
||||||
is_org_staff, org_user_exists, unset_org_user, set_org_user, set_org_staff, unset_org_staff
|
ORG_ENABLE_ADMIN_INVITE_USER
|
||||||
|
from seahub.organizations.views import get_org_user_self_usage, \
|
||||||
|
get_org_user_quota, is_org_staff, org_user_exists, \
|
||||||
|
unset_org_user, set_org_user, set_org_staff, unset_org_staff
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -223,7 +229,6 @@ class OrgAdminUsers(APIView):
|
|||||||
org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1))
|
org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1))
|
||||||
|
|
||||||
if ORG_MEMBER_QUOTA_ENABLED:
|
if ORG_MEMBER_QUOTA_ENABLED:
|
||||||
from seahub.organizations.models import OrgMemberQuota
|
|
||||||
org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id)
|
org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id)
|
||||||
if org_members_quota is not None and org_members >= org_members_quota:
|
if org_members_quota is not None and org_members >= org_members_quota:
|
||||||
err_msg = 'Failed. You can only invite %d members.' % org_members_quota
|
err_msg = 'Failed. You can only invite %d members.' % org_members_quota
|
||||||
@@ -758,3 +763,92 @@ class OrgAdminImportUsers(APIView):
|
|||||||
result['success'].append(info)
|
result['success'].append(info)
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class OrgAdminInviteUser(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsProVersion, IsOrgAdminUser)
|
||||||
|
|
||||||
|
def post(self, request, org_id):
|
||||||
|
|
||||||
|
"""Invite organization user
|
||||||
|
"""
|
||||||
|
if not ORG_ENABLE_ADMIN_INVITE_USER:
|
||||||
|
error_msg = _('Feature disabled.')
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
if not IS_EMAIL_CONFIGURED:
|
||||||
|
error_msg = _('Failed to send email, email service is not properly configured, \
|
||||||
|
please contact administrator.')
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
# parameter check
|
||||||
|
email_list = request.data.getlist("email", None)
|
||||||
|
if not email_list:
|
||||||
|
error_msg = 'email invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
org_id = int(org_id)
|
||||||
|
org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
if not org:
|
||||||
|
error_msg = f'Organization {org_id} not found.'
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# check plan
|
||||||
|
url_prefix = request.user.org.url_prefix
|
||||||
|
org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1))
|
||||||
|
|
||||||
|
if ORG_MEMBER_QUOTA_ENABLED:
|
||||||
|
org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id)
|
||||||
|
if org_members_quota is not None and \
|
||||||
|
org_members + len(email_list) > org_members_quota:
|
||||||
|
err_msg = f'Failed. You can only invite {org_members_quota} members.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, err_msg)
|
||||||
|
|
||||||
|
if user_number_over_limit(len(email_list)):
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'The number of users exceeds the limit')
|
||||||
|
|
||||||
|
username = request.user.username
|
||||||
|
quota_total = seafile_api.get_org_quota(org_id)
|
||||||
|
|
||||||
|
# add user
|
||||||
|
result = {}
|
||||||
|
result['failed'] = []
|
||||||
|
result['success'] = []
|
||||||
|
|
||||||
|
for email in email_list:
|
||||||
|
|
||||||
|
try:
|
||||||
|
User.objects.get(email=email)
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'error_msg': f'User {email} already exists.'
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
except User.DoesNotExist:
|
||||||
|
new_user = User.objects.create_user(email, '!',
|
||||||
|
is_staff=False,
|
||||||
|
is_active=False)
|
||||||
|
set_org_user(org_id, email)
|
||||||
|
|
||||||
|
# send invitation link
|
||||||
|
i = Invitation.objects.add(inviter=username,
|
||||||
|
accepter=email,
|
||||||
|
invite_type=DEFAULT_USER)
|
||||||
|
i.send_to(email=email, org_name=org.org_name)
|
||||||
|
|
||||||
|
user_info = {}
|
||||||
|
user_info['email'] = email
|
||||||
|
user_info['name'] = email2nickname(email)
|
||||||
|
user_info['contact_email'] = email2contact_email(email)
|
||||||
|
user_info['is_staff'] = False
|
||||||
|
user_info['is_active'] = False
|
||||||
|
user_info['create_time'] = timestamp_to_isoformat_timestr(new_user.ctime)
|
||||||
|
user_info['quota_usage'] = 0
|
||||||
|
user_info['quota_total'] = quota_total
|
||||||
|
user_info['last_login'] = ''
|
||||||
|
result['success'].append(user_info)
|
||||||
|
|
||||||
|
return Response(result)
|
||||||
|
@@ -11,7 +11,7 @@ from .api.group_owned_libraries import (
|
|||||||
)
|
)
|
||||||
from .api.group_members import AdminGroupMembers, AdminGroupMember
|
from .api.group_members import AdminGroupMembers, AdminGroupMember
|
||||||
from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \
|
from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \
|
||||||
OrgAdminImportUsers
|
OrgAdminImportUsers, OrgAdminInviteUser
|
||||||
from .api.admin.user_set_password import OrgAdminUserSetPassword
|
from .api.admin.user_set_password import OrgAdminUserSetPassword
|
||||||
from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup
|
from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup
|
||||||
from .api.admin.repos import OrgAdminRepos, OrgAdminRepo
|
from .api.admin.repos import OrgAdminRepos, OrgAdminRepo
|
||||||
@@ -87,6 +87,7 @@ urlpatterns = [
|
|||||||
path('<int:org_id>/admin/groups/<int:group_id>/members/<str:email>/', AdminGroupMember.as_view(), name='api-admin-group-member'),
|
path('<int:org_id>/admin/groups/<int:group_id>/members/<str:email>/', AdminGroupMember.as_view(), name='api-admin-group-member'),
|
||||||
path('<int:org_id>/admin/users/', OrgAdminUsers.as_view(), name='api-v2.1-org-admin-users'),
|
path('<int:org_id>/admin/users/', OrgAdminUsers.as_view(), name='api-v2.1-org-admin-users'),
|
||||||
path('<int:org_id>/admin/import-users/', OrgAdminImportUsers.as_view(), name='api-v2.1-org-admin-import-users'),
|
path('<int:org_id>/admin/import-users/', OrgAdminImportUsers.as_view(), name='api-v2.1-org-admin-import-users'),
|
||||||
|
path('<int:org_id>/admin/invite-users/', OrgAdminInviteUser.as_view(), name='api-v2.1-org-admin-invite-users'),
|
||||||
path('<int:org_id>/admin/search-user/', OrgAdminSearchUser.as_view(), name='api-v2.1-org-admin-search-user'),
|
path('<int:org_id>/admin/search-user/', OrgAdminSearchUser.as_view(), name='api-v2.1-org-admin-search-user'),
|
||||||
path('<int:org_id>/admin/users/<str:email>/', OrgAdminUser.as_view(), name='api-v2.1-org-admin-user'),
|
path('<int:org_id>/admin/users/<str:email>/', OrgAdminUser.as_view(), name='api-v2.1-org-admin-user'),
|
||||||
re_path(r'^(?P<org_id>\d+)/admin/users/(?P<email>[^/]+)/set-password/', OrgAdminUserSetPassword.as_view(), name='api-v2.1-org-admin-user-reset-password'),
|
re_path(r'^(?P<org_id>\d+)/admin/users/(?P<email>[^/]+)/set-password/', OrgAdminUserSetPassword.as_view(), name='api-v2.1-org-admin-user-reset-password'),
|
||||||
@@ -102,3 +103,4 @@ urlpatterns = [
|
|||||||
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
|
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
|
||||||
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
|
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -13,7 +13,10 @@ ORG_TRIAL_DAYS = getattr(settings, 'ORG_TRIAL_DAYS', -1)
|
|||||||
|
|
||||||
ORG_AUTO_URL_PREFIX = getattr(settings, 'ORG_AUTO_URL_PREFIX', True)
|
ORG_AUTO_URL_PREFIX = getattr(settings, 'ORG_AUTO_URL_PREFIX', True)
|
||||||
|
|
||||||
ORG_ENABLE_ADMIN_INVITE_USER = getattr(settings, 'ORG_ENABLE_ADMIN_INVITE_USER', False)
|
ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN = getattr(settings,
|
||||||
|
'ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN',
|
||||||
|
False)
|
||||||
|
|
||||||
|
ORG_ENABLE_ADMIN_INVITE_USER = getattr(settings, 'ORG_ENABLE_ADMIN_INVITE_USER', True)
|
||||||
ORG_ENABLE_ADMIN_CUSTOM_NAME = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_NAME', True)
|
ORG_ENABLE_ADMIN_CUSTOM_NAME = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_NAME', True)
|
||||||
ORG_ENABLE_ADMIN_CUSTOM_LOGO = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_LOGO', True)
|
ORG_ENABLE_ADMIN_CUSTOM_LOGO = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_LOGO', True)
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
orgMemberQuotaEnabled: '{{ org_member_quota_enabled }}',
|
orgMemberQuotaEnabled: '{{ org_member_quota_enabled }}',
|
||||||
orgEnableAdminCustomLogo: '{{ org_enable_admin_custom_logo }}',
|
orgEnableAdminCustomLogo: '{{ org_enable_admin_custom_logo }}',
|
||||||
orgEnableAdminCustomName: '{{ org_enable_admin_custom_name }}',
|
orgEnableAdminCustomName: '{{ org_enable_admin_custom_name }}',
|
||||||
|
orgEnableAdminInviteUser: '{{ org_enable_admin_invite_user }}',
|
||||||
enableMultiADFS: '{{ enable_multi_adfs }}',
|
enableMultiADFS: '{{ enable_multi_adfs }}',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,8 +31,9 @@ from seahub.organizations.signals import org_created
|
|||||||
from seahub.organizations.decorators import org_staff_required
|
from seahub.organizations.decorators import org_staff_required
|
||||||
from seahub.organizations.forms import OrgRegistrationForm
|
from seahub.organizations.forms import OrgRegistrationForm
|
||||||
from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \
|
from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \
|
||||||
ORG_MEMBER_QUOTA_ENABLED, ORG_ENABLE_ADMIN_INVITE_USER, \
|
ORG_MEMBER_QUOTA_ENABLED, ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN, \
|
||||||
ORG_ENABLE_ADMIN_CUSTOM_LOGO, ORG_ENABLE_ADMIN_CUSTOM_NAME
|
ORG_ENABLE_ADMIN_CUSTOM_LOGO, ORG_ENABLE_ADMIN_CUSTOM_NAME, \
|
||||||
|
ORG_ENABLE_ADMIN_INVITE_USER
|
||||||
from seahub.organizations.utils import get_or_create_invitation_link
|
from seahub.organizations.utils import get_or_create_invitation_link
|
||||||
|
|
||||||
# Get an instance of a logger
|
# Get an instance of a logger
|
||||||
@@ -247,7 +248,7 @@ def react_fake_view(request, **kwargs):
|
|||||||
group_id = kwargs.get('group_id', '')
|
group_id = kwargs.get('group_id', '')
|
||||||
org = request.user.org
|
org = request.user.org
|
||||||
|
|
||||||
invitation_link = get_or_create_invitation_link(org.org_id) if ORG_ENABLE_ADMIN_INVITE_USER else ''
|
invitation_link = get_or_create_invitation_link(org.org_id) if ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN else ''
|
||||||
|
|
||||||
# Whether use new page
|
# Whether use new page
|
||||||
return render(request, "organizations/org_admin_react.html", {
|
return render(request, "organizations/org_admin_react.html", {
|
||||||
@@ -255,6 +256,7 @@ def react_fake_view(request, **kwargs):
|
|||||||
'org_member_quota_enabled': ORG_MEMBER_QUOTA_ENABLED,
|
'org_member_quota_enabled': ORG_MEMBER_QUOTA_ENABLED,
|
||||||
'org_enable_admin_custom_logo': ORG_ENABLE_ADMIN_CUSTOM_LOGO,
|
'org_enable_admin_custom_logo': ORG_ENABLE_ADMIN_CUSTOM_LOGO,
|
||||||
'org_enable_admin_custom_name': ORG_ENABLE_ADMIN_CUSTOM_NAME,
|
'org_enable_admin_custom_name': ORG_ENABLE_ADMIN_CUSTOM_NAME,
|
||||||
|
'org_enable_admin_invite_user': ORG_ENABLE_ADMIN_INVITE_USER,
|
||||||
'group_id': group_id,
|
'group_id': group_id,
|
||||||
'invitation_link': invitation_link,
|
'invitation_link': invitation_link,
|
||||||
'enable_multi_adfs': ENABLE_MULTI_ADFS,
|
'enable_multi_adfs': ENABLE_MULTI_ADFS,
|
||||||
|
@@ -41,7 +41,7 @@ class InvitationsTest(BaseTestCase):
|
|||||||
entry = models.Invitation(token=models.gen_token(max_length=32),
|
entry = models.Invitation(token=models.gen_token(max_length=32),
|
||||||
inviter=self.admin,
|
inviter=self.admin,
|
||||||
accepter=email,
|
accepter=email,
|
||||||
invite_type=models.GUEST,
|
invite_type=models.GUEST_USER,
|
||||||
expire_time=timezone.now())
|
expire_time=timezone.now())
|
||||||
entry.save()
|
entry.save()
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ class InvitationTest(BaseTestCase):
|
|||||||
entry = models.Invitation(token=token,
|
entry = models.Invitation(token=token,
|
||||||
inviter=self.admin,
|
inviter=self.admin,
|
||||||
accepter=email,
|
accepter=email,
|
||||||
invite_type=models.GUEST,
|
invite_type=models.GUEST_USER,
|
||||||
expire_time=timezone.now())
|
expire_time=timezone.now())
|
||||||
entry.save()
|
entry.save()
|
||||||
return token
|
return token
|
||||||
|
Reference in New Issue
Block a user