mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 02:42:47 +00:00
68
frontend/src/components/dialog/import-members-dialog.js
Normal file
68
frontend/src/components/dialog/import-members-dialog.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
|
||||||
|
import { gettext, siteRoot } from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleImportMembersDialog: PropTypes.func.isRequired,
|
||||||
|
importMembersInBatch: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImportMembersDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.fileInputRef = React.createRef();
|
||||||
|
this.state = {
|
||||||
|
errorMsg: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleImportMembersDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
openFileInput = () => {
|
||||||
|
this.fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadFile = (e) => {
|
||||||
|
// no file selected
|
||||||
|
if (!this.fileInputRef.current.files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check file extension
|
||||||
|
let fileName = this.fileInputRef.current.files[0].name;
|
||||||
|
if(fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
|
||||||
|
this.setState({
|
||||||
|
errorMsg: gettext('Please choose a .xlsx file.')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = this.fileInputRef.current.files[0];
|
||||||
|
this.props.importMembersInBatch(file);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { errorMsg } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Import members from a .xlsx file')}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<p><a className="text-secondary small" href={`${siteRoot}api/v2.1/group-members-import-example/`}>{gettext('Download an example file')}</a></p>
|
||||||
|
<button className="btn btn-outline-primary" onClick={this.openFileInput}>{gettext('Upload file')}</button>
|
||||||
|
<input className="d-none" type="file" onChange={this.uploadFile} ref={this.fileInputRef} />
|
||||||
|
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportMembersDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default ImportMembersDialog;
|
@@ -18,7 +18,7 @@ import CreateDepartmentRepoDialog from '../../components/dialog/create-departmen
|
|||||||
import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog';
|
import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog';
|
||||||
import RenameGroupDialog from '../../components/dialog/rename-group-dialog';
|
import RenameGroupDialog from '../../components/dialog/rename-group-dialog';
|
||||||
import TransferGroupDialog from '../../components/dialog/transfer-group-dialog';
|
import TransferGroupDialog from '../../components/dialog/transfer-group-dialog';
|
||||||
// import ImportMembersDialog from '../../components/dialog/import-members-dialog';
|
import ImportMembersDialog from '../../components/dialog/import-members-dialog';
|
||||||
import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
|
import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
|
||||||
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
||||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||||
@@ -63,7 +63,7 @@ class GroupView extends React.Component {
|
|||||||
showRenameGroupDialog: false,
|
showRenameGroupDialog: false,
|
||||||
showDismissGroupDialog: false,
|
showDismissGroupDialog: false,
|
||||||
showTransferGroupDialog: false,
|
showTransferGroupDialog: false,
|
||||||
// showImportMembersDialog: false,
|
showImportMembersDialog: false,
|
||||||
showManageMembersDialog: false,
|
showManageMembersDialog: false,
|
||||||
groupMembers: [],
|
groupMembers: [],
|
||||||
isShowDetails: false,
|
isShowDetails: false,
|
||||||
@@ -283,11 +283,24 @@ class GroupView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggleImportMembersDialog= () => {
|
toggleImportMembersDialog= () => {
|
||||||
// this.setState({
|
this.setState({
|
||||||
// showImportMembersDialog: !this.state.showImportMembersDialog
|
showImportMembersDialog: !this.state.showImportMembersDialog
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
importMembersInBatch= (file) => {
|
||||||
|
toaster.notify(gettext('It may take some time, please wait.'));
|
||||||
|
seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => {
|
||||||
|
res.data.failed.map(item => {
|
||||||
|
const msg = `${item.email}: ${item.error_msg}`;
|
||||||
|
toaster.danger(msg);
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMsg = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toggleManageMembersDialog = () => {
|
toggleManageMembersDialog = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -464,7 +477,7 @@ class GroupView extends React.Component {
|
|||||||
}
|
}
|
||||||
{(this.state.isStaff || this.state.isOwner) &&
|
{(this.state.isStaff || this.state.isOwner) &&
|
||||||
<ul className="sf-popover-list">
|
<ul className="sf-popover-list">
|
||||||
{/* <li><a href="#" className="sf-popover-item" onClick={this.toggleImportMembersDialog} >{gettext('Import Members')}</a></li> */}
|
<li><a href="#" className="sf-popover-item" onClick={this.toggleImportMembersDialog} >{gettext('Import Members')}</a></li>
|
||||||
<li><a href="#" className="sf-popover-item" onClick={this.toggleManageMembersDialog} >{gettext('Manage Members')}</a></li>
|
<li><a href="#" className="sf-popover-item" onClick={this.toggleManageMembersDialog} >{gettext('Manage Members')}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -596,13 +609,12 @@ class GroupView extends React.Component {
|
|||||||
onGroupChanged={this.props.onGroupChanged}
|
onGroupChanged={this.props.onGroupChanged}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{/* this.state.showImportMembersDialog &&
|
{ this.state.showImportMembersDialog &&
|
||||||
<ImportMembersDialog
|
<ImportMembersDialog
|
||||||
toggleImportMembersDialog={this.toggleImportMembersDialog}
|
toggleImportMembersDialog={this.toggleImportMembersDialog}
|
||||||
groupID={this.props.groupID}
|
importMembersInBatch={this.importMembersInBatch}
|
||||||
onGroupChanged={this.props.onGroupChanged}
|
|
||||||
/>
|
/>
|
||||||
*/}
|
}
|
||||||
{this.state.showManageMembersDialog &&
|
{this.state.showManageMembersDialog &&
|
||||||
<ManageMembersDialog
|
<ManageMembersDialog
|
||||||
toggleManageMembersDialog={this.toggleManageMembersDialog}
|
toggleManageMembersDialog={this.toggleManageMembersDialog}
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import logging
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
@@ -9,16 +12,18 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
import seaserv
|
|
||||||
from seaserv import seafile_api, ccnet_api
|
from seaserv import seafile_api, ccnet_api
|
||||||
from pysearpc import SearpcError
|
from pysearpc import SearpcError
|
||||||
|
|
||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
|
from seahub.api2.endpoints.utils import is_org_user
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
from seahub.utils import string2list, is_org_context
|
from seahub.utils import string2list, is_org_context, get_file_type_and_ext
|
||||||
|
from seahub.utils.ms_excel import write_xls
|
||||||
|
from seahub.utils.error_msg import file_type_error_msg
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
from seahub.group.signals import add_user_to_group
|
from seahub.group.signals import add_user_to_group
|
||||||
from seahub.group.utils import is_group_member, is_group_admin, \
|
from seahub.group.utils import is_group_member, is_group_admin, \
|
||||||
@@ -28,6 +33,7 @@ from .utils import api_check_group
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GroupMembers(APIView):
|
class GroupMembers(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
@@ -40,8 +46,7 @@ class GroupMembers(APIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
avatar_size = int(request.GET.get('avatar_size',
|
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
|
||||||
AVATAR_DEFAULT_SIZE))
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
avatar_size = AVATAR_DEFAULT_SIZE
|
avatar_size = AVATAR_DEFAULT_SIZE
|
||||||
|
|
||||||
@@ -109,6 +114,9 @@ class GroupMembers(APIView):
|
|||||||
if not ccnet_api.org_user_exists(org_id, email):
|
if not ccnet_api.org_user_exists(org_id, email):
|
||||||
error_msg = _('User %s not found in organization.') % email2nickname(email)
|
error_msg = _('User %s not found in organization.') % email2nickname(email)
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
elif is_org_user(email):
|
||||||
|
error_msg = _('User %s is an organization user.') % email
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
ccnet_api.group_add_member(group_id, username, email)
|
ccnet_api.group_add_member(group_id, username, email)
|
||||||
add_user_to_group.send(sender=None,
|
add_user_to_group.send(sender=None,
|
||||||
@@ -151,8 +159,7 @@ class GroupMember(APIView):
|
|||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
avatar_size = int(request.GET.get('avatar_size',
|
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
|
||||||
AVATAR_DEFAULT_SIZE))
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
avatar_size = AVATAR_DEFAULT_SIZE
|
avatar_size = AVATAR_DEFAULT_SIZE
|
||||||
|
|
||||||
@@ -305,8 +312,7 @@ class GroupMembersBulk(APIView):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Can only invite organization users to group
|
# Can only invite organization users to group
|
||||||
if org_id and not \
|
if org_id and not ccnet_api.org_user_exists(org_id, email):
|
||||||
seaserv.ccnet_threaded_rpc.org_user_exists(org_id, email):
|
|
||||||
result['failed'].append({
|
result['failed'].append({
|
||||||
'email': email,
|
'email': email,
|
||||||
'email_name': email_name,
|
'email_name': email_name,
|
||||||
@@ -314,13 +320,20 @@ class GroupMembersBulk(APIView):
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not org_id and is_org_user(email):
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'email_name': email_name,
|
||||||
|
'error_msg': _('User %s is an organization user.') % email_name
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
emails_need_add.append(email)
|
emails_need_add.append(email)
|
||||||
|
|
||||||
# Add user to group.
|
# Add user to group.
|
||||||
for email in emails_need_add:
|
for email in emails_need_add:
|
||||||
try:
|
try:
|
||||||
seaserv.ccnet_threaded_rpc.group_add_member(group_id,
|
ccnet_api.group_add_member(group_id, username, email)
|
||||||
username, email)
|
|
||||||
member_info = get_group_member_info(request, group_id, email)
|
member_info = get_group_member_info(request, group_id, email)
|
||||||
result['success'].append(member_info)
|
result['success'].append(member_info)
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
@@ -335,3 +348,158 @@ class GroupMembersBulk(APIView):
|
|||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
added_user=email)
|
added_user=email)
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMembersImport(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def post(self, request, group_id):
|
||||||
|
|
||||||
|
""" Import members from xlsx file
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. group admin or owner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
xlsx_file = request.FILES.get('file', None)
|
||||||
|
if not xlsx_file:
|
||||||
|
error_msg = 'file can not be found.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
file_type, ext = get_file_type_and_ext(xlsx_file.name)
|
||||||
|
if ext != 'xlsx':
|
||||||
|
error_msg = file_type_error_msg(ext, 'xlsx')
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
# recourse check
|
||||||
|
group_id = int(group_id)
|
||||||
|
group = ccnet_api.get_group(group_id)
|
||||||
|
if not group:
|
||||||
|
error_msg = _('Group does not exist')
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# check permission
|
||||||
|
# only group owner/admin can add group members
|
||||||
|
username = request.user.username
|
||||||
|
if not is_group_admin_or_owner(group_id, username):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
content = xlsx_file.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs = BytesIO(content)
|
||||||
|
wb = load_workbook(filename=fs, read_only=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
# example file is like:
|
||||||
|
# Email
|
||||||
|
# a@a.com
|
||||||
|
# b@b.com
|
||||||
|
|
||||||
|
rows = wb.worksheets[0].rows
|
||||||
|
records = []
|
||||||
|
# skip first row(head field).
|
||||||
|
next(rows)
|
||||||
|
for row in rows:
|
||||||
|
records.append([col.value for col in row])
|
||||||
|
|
||||||
|
emails_list = []
|
||||||
|
for record in records:
|
||||||
|
if record[0]:
|
||||||
|
email = record[0].strip().lower()
|
||||||
|
emails_list.append(email)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
result['failed'] = []
|
||||||
|
result['success'] = []
|
||||||
|
emails_need_add = []
|
||||||
|
|
||||||
|
org_id = None
|
||||||
|
if is_org_context(request):
|
||||||
|
org_id = request.user.org.org_id
|
||||||
|
|
||||||
|
for email in emails_list:
|
||||||
|
email_name = email2nickname(email)
|
||||||
|
try:
|
||||||
|
User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'email_name': email_name,
|
||||||
|
'error_msg': 'User %s not found.' % email_name
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
if is_group_member(group_id, email, in_structure=False):
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'email_name': email_name,
|
||||||
|
'error_msg': _('User %s is already a group member.') % email_name
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Can only invite organization users to group
|
||||||
|
if org_id and not ccnet_api.org_user_exists(org_id, email):
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'email_name': email_name,
|
||||||
|
'error_msg': _('User %s not found in organization.') % email_name
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not org_id and is_org_user(email):
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'email_name': email_name,
|
||||||
|
'error_msg': _('User %s is an organization user.') % email_name
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
emails_need_add.append(email)
|
||||||
|
|
||||||
|
# Add user to group.
|
||||||
|
for email in emails_need_add:
|
||||||
|
try:
|
||||||
|
ccnet_api.group_add_member(group_id, username, email)
|
||||||
|
member_info = get_group_member_info(request, group_id, email)
|
||||||
|
result['success'].append(member_info)
|
||||||
|
except SearpcError as e:
|
||||||
|
logger.error(e)
|
||||||
|
result['failed'].append({
|
||||||
|
'email': email,
|
||||||
|
'error_msg': 'Internal Server Error'
|
||||||
|
})
|
||||||
|
|
||||||
|
add_user_to_group.send(sender=None,
|
||||||
|
group_staff=username,
|
||||||
|
group_id=group_id,
|
||||||
|
added_user=email)
|
||||||
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMembersImportExample(APIView):
|
||||||
|
|
||||||
|
throttle_classes = (UserRateThrottle, )
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
|
||||||
|
data_list = []
|
||||||
|
head = [_('Email')]
|
||||||
|
for i in range(5):
|
||||||
|
username = "test" + str(i) + "@example.com"
|
||||||
|
data_list.append([username])
|
||||||
|
|
||||||
|
wb = write_xls('sample', head, data_list)
|
||||||
|
if not wb:
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, _('Failed to export Excel'))
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='application/ms-excel')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename=members.xlsx'
|
||||||
|
wb.save(response)
|
||||||
|
|
||||||
|
return response
|
||||||
|
@@ -34,7 +34,8 @@ from seahub.api2.endpoints.group_owned_libraries import GroupOwnedLibraries, \
|
|||||||
from seahub.api2.endpoints.address_book.groups import AddressBookGroupsSubGroups
|
from seahub.api2.endpoints.address_book.groups import AddressBookGroupsSubGroups
|
||||||
from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember
|
from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember
|
||||||
|
|
||||||
from seahub.api2.endpoints.group_members import GroupMembers, GroupMembersBulk, GroupMember
|
from seahub.api2.endpoints.group_members import GroupMembers, GroupMember, \
|
||||||
|
GroupMembersBulk, GroupMembersImport, GroupMembersImportExample
|
||||||
from seahub.api2.endpoints.search_group import SearchGroup
|
from seahub.api2.endpoints.search_group import SearchGroup
|
||||||
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
||||||
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo
|
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo
|
||||||
@@ -290,6 +291,8 @@ urlpatterns = [
|
|||||||
url(r'^api/v2.1/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/$', GroupOwnedLibrary.as_view(), name='api-v2.1-owned-group-library'),
|
url(r'^api/v2.1/groups/(?P<group_id>\d+)/group-owned-libraries/(?P<repo_id>[-0-9a-f]{36})/$', GroupOwnedLibrary.as_view(), name='api-v2.1-owned-group-library'),
|
||||||
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/$', GroupMembers.as_view(), name='api-v2.1-group-members'),
|
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/$', GroupMembers.as_view(), name='api-v2.1-group-members'),
|
||||||
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/bulk/$', GroupMembersBulk.as_view(), name='api-v2.1-group-members-bulk'),
|
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/bulk/$', GroupMembersBulk.as_view(), name='api-v2.1-group-members-bulk'),
|
||||||
|
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/import/$', GroupMembersImport.as_view(), name='api-v2.1-group-members-import'),
|
||||||
|
url(r'^api/v2.1/group-members-import-example/$', GroupMembersImportExample.as_view(), name='api-v2.1-group-members-import-example'),
|
||||||
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'),
|
url(r'^api/v2.1/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'),
|
||||||
url(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'),
|
url(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'),
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user