1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-07 01:41:39 +00:00

Import work weixin dep (#3803)

* dropdown import work weixin department

* API import work weixin department

* admin check group name conflict

* work weixin department batch import

* work weixin department import update return msg

* work weixin department import not support batch

* react work weixin department import

* add IsProVersion work weixin department import

* change style

* check group name conflict
This commit is contained in:
sniper-py
2019-07-16 15:16:22 +08:00
committed by Daniel Pan
parent 593345d39d
commit d6bba483be
7 changed files with 434 additions and 13 deletions

View File

@@ -0,0 +1,54 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import Loading from '../loading';
const propTypes = {
importDepartmentDialogToggle: PropTypes.func.isRequired,
onImportDepartmentSubmit: PropTypes.func.isRequired,
departmentsCount: PropTypes.number.isRequired,
membersCount: PropTypes.number.isRequired,
departmentName: PropTypes.string.isRequired,
};
class ImportWorkWeixinDepartmentDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading : false,
};
}
toggle = () => {
this.props.importDepartmentDialogToggle(null);
};
handleSubmit = () => {
this.props.onImportDepartmentSubmit();
this.setState({ isLoading : true });
};
render() {
const { departmentsCount, membersCount, departmentName } = this.props;
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>
<span>{'导入部门 '}</span><span className="op-target" title={departmentName}>{departmentName}</span>
</ModalHeader>
<ModalBody>
<p>{'将要导入 '}<strong>{departmentsCount}</strong>{' '}<strong>{membersCount}</strong>{' '}</p>
{this.state.isLoading && <Loading/>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{'取消'}</Button>
<Button color="primary" onClick={this.handleSubmit}>{'导入'}</Button>
</ModalFooter>
</Modal>
);
}
}
ImportWorkWeixinDepartmentDialog.propTypes = propTypes;
export default ImportWorkWeixinDepartmentDialog;

View File

@@ -23,8 +23,9 @@
height: 140px; height: 140px;
} }
.dir-content-nav { .dir-content-nav {
position: fixed; position: absolute;
overflow: hidden; overflow: hidden;
width: 24%;
} }
.dir-content-nav:hover { .dir-content-nav:hover {
overflow: auto; overflow: auto;
@@ -42,6 +43,8 @@
} }
.tree-node-inner { .tree-node-inner {
position: relative; position: relative;
display: flex;
padding-right: 1rem;
} }
.tree-node-inner i { .tree-node-inner i {
position: absolute; position: absolute;

View File

@@ -2,10 +2,12 @@ import React, { Component, Fragment } from 'react';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import _ from 'lodash'; import _ from 'lodash';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot } from '../../utils/constants'; import { gettext, siteRoot, isPro } from '../../utils/constants';
import toaster from '../../components/toast'; import toaster from '../../components/toast';
import Account from '../../components/common/account'; import Account from '../../components/common/account';
import { WorkWeixinDepartmentMembersList, WorkWeixinDepartmentsTreePanel } from './work-weixin'; import { WorkWeixinDepartmentMembersList, WorkWeixinDepartmentsTreePanel } from './work-weixin';
import ImportWorkWeixinDepartmentDialog from '../../components/dialog/import-work-weixin-department-dialog';
import '../../css/work-weixin-departments.css'; import '../../css/work-weixin-departments.css';
class WorkWeixinDepartments extends Component { class WorkWeixinDepartments extends Component {
@@ -22,6 +24,10 @@ class WorkWeixinDepartments extends Component {
newUsersTempObj: {}, newUsersTempObj: {},
isCheckedAll: false, isCheckedAll: false,
canCheckUserIds: [], canCheckUserIds: [],
isImportDepartmentDialogShow: false,
importDepartment: null,
importDepartmentChildrenCount: 0,
importDepartmentMembersCount: 0,
}; };
} }
@@ -50,13 +56,20 @@ class WorkWeixinDepartments extends Component {
}); });
}; };
getWorkWeixinDepartmentsList = () => { getWorkWeixinDepartmentsList = (departmentID) => {
seafileAPI.adminListWorkWeixinDepartments().then((res) => { seafileAPI.adminListWorkWeixinDepartments(departmentID).then((res) => {
let departmentsTree = this.getDepartmentsTree(res.data.department); if (!departmentID) {
this.setState({ let departmentsTree = this.getDepartmentsTree(res.data.department);
isTreeLoading: false, this.setState({
departmentsTree: departmentsTree, isTreeLoading: false,
}); departmentsTree: departmentsTree,
});
} else {
this.setState({
importDepartmentChildrenCount: res.data.department.length,
importDepartmentMembersCount: this.state.membersTempObj[departmentID].length,
});
}
}).catch((error) => { }).catch((error) => {
this.handleError(error); this.handleError(error);
this.setState({ this.setState({
@@ -213,6 +226,59 @@ class WorkWeixinDepartments extends Component {
}); });
} }
importDepartmentDialogToggle = (importDepartment) => {
this.setState({
isImportDepartmentDialogShow: !this.state.isImportDepartmentDialogShow,
importDepartment: importDepartment,
}, () => {
if (importDepartment) {
this.getWorkWeixinDepartmentsList(importDepartment.id);
}
});
};
onImportDepartmentSubmit = () => {
let importDepartment = this.state.importDepartment;
if (!importDepartment) return;
seafileAPI.adminImportWorkWeixinDepartment(importDepartment.id).then((res) => {
this.setState({
isMembersListLoading: true,
checkedDepartmentId: importDepartment.id,
membersTempObj: {},
membersList: [],
newUsersTempObj: {},
isCheckedAll: false,
canCheckUserIds: [],
});
this.getWorkWeixinDepartmentMembersList(importDepartment.id);
this.importDepartmentDialogToggle(null);
if (res.data.success) {
this.handleImportDepartmentSubmitSuccess(res.data.success);
}
if (res.data.failed) {
this.handleImportDepartmentSubmitFailed(res.data.failed);
}
}).catch((error) => {
this.handleError(error);
});
};
handleImportDepartmentSubmitSuccess = (successes) => {
for (let i = 0, len = successes.length; i < len; i++) {
let success = successes[i];
let successMsg = success.type === 'department' ? '部门 ' + success.department_name + ' 导入成功' : success.api_user_name + ' 导入成功' ;
toaster.success(successMsg, { duration: 3 });
}
};
handleImportDepartmentSubmitFailed = (fails) => {
for (let i = 0, len = fails.length; i < len; i++) {
let fail = fails[i];
let failName = fail.type === 'department' ? fail.department_name : fail.api_user_name;
toaster.danger(failName + ' ' + fail.msg, { duration: 3} );
}
};
handleError = (e) => { handleError = (e) => {
if (e.response) { if (e.response) {
toaster.danger(e.response.data.error_msg || e.response.data.detail || gettext('Error'), {duration: 3}); toaster.danger(e.response.data.error_msg || e.response.data.detail || gettext('Error'), {duration: 3});
@@ -222,7 +288,7 @@ class WorkWeixinDepartments extends Component {
} }
componentDidMount() { componentDidMount() {
this.getWorkWeixinDepartmentsList(); this.getWorkWeixinDepartmentsList(null);
} }
renderNav() { renderNav() {
@@ -242,6 +308,8 @@ class WorkWeixinDepartments extends Component {
} }
render() { render() {
const { isImportDepartmentDialogShow, isTreeLoading, importDepartment, importDepartmentChildrenCount, importDepartmentMembersCount } = this.state;
let canImportDepartment = !!(isPro && isImportDepartmentDialogShow && !isTreeLoading && importDepartment);
return ( return (
<Fragment> <Fragment>
{this.renderNav()} {this.renderNav()}
@@ -256,6 +324,7 @@ class WorkWeixinDepartments extends Component {
isTreeLoading={this.state.isTreeLoading} isTreeLoading={this.state.isTreeLoading}
onChangeDepartment={this.onChangeDepartment} onChangeDepartment={this.onChangeDepartment}
checkedDepartmentId={this.state.checkedDepartmentId} checkedDepartmentId={this.state.checkedDepartmentId}
importDepartmentDialogToggle={this.importDepartmentDialogToggle}
/> />
<div className="dir-content-resize"></div> <div className="dir-content-resize"></div>
<WorkWeixinDepartmentMembersList <WorkWeixinDepartmentMembersList
@@ -271,6 +340,15 @@ class WorkWeixinDepartments extends Component {
</div> </div>
</div> </div>
</div> </div>
{canImportDepartment &&
<ImportWorkWeixinDepartmentDialog
importDepartmentDialogToggle={this.importDepartmentDialogToggle}
onImportDepartmentSubmit={this.onImportDepartmentSubmit}
departmentsCount={importDepartmentChildrenCount}
membersCount={importDepartmentMembersCount}
departmentName={importDepartment.name}
/>
}
</Fragment> </Fragment>
); );
} }

View File

@@ -1,6 +1,8 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { gettext, isPro } from '../../../utils/constants';
const WorkWeixinDepartmentsTreeNodePropTypes = { const WorkWeixinDepartmentsTreeNodePropTypes = {
index: PropTypes.number, index: PropTypes.number,
@@ -8,6 +10,7 @@ const WorkWeixinDepartmentsTreeNodePropTypes = {
isChildrenShow: PropTypes.bool.isRequired, isChildrenShow: PropTypes.bool.isRequired,
onChangeDepartment: PropTypes.func.isRequired, onChangeDepartment: PropTypes.func.isRequired,
checkedDepartmentId: PropTypes.number.isRequired, checkedDepartmentId: PropTypes.number.isRequired,
importDepartmentDialogToggle: PropTypes.func.isRequired,
}; };
class WorkWeixinDepartmentsTreeNode extends Component { class WorkWeixinDepartmentsTreeNode extends Component {
@@ -16,6 +19,8 @@ class WorkWeixinDepartmentsTreeNode extends Component {
super(props); super(props);
this.state = { this.state = {
isChildrenShow: false, isChildrenShow: false,
dropdownOpen: false,
active: false,
}; };
} }
@@ -27,6 +32,18 @@ class WorkWeixinDepartmentsTreeNode extends Component {
}); });
}; };
dropdownToggle = () => {
this.setState({ dropdownOpen: !this.state.dropdownOpen });
};
onMouseEnter = () => {
this.setState({ active: true });
};
onMouseLeave = () => {
this.setState({ active: false });
};
componentDidMount() { componentDidMount() {
if (this.props.index === 0) { if (this.props.index === 0) {
this.setState({ isChildrenShow: true }); this.setState({ isChildrenShow: true });
@@ -44,6 +61,7 @@ class WorkWeixinDepartmentsTreeNode extends Component {
isChildrenShow={this.state.isChildrenShow} isChildrenShow={this.state.isChildrenShow}
onChangeDepartment={this.props.onChangeDepartment} onChangeDepartment={this.props.onChangeDepartment}
checkedDepartmentId={this.props.checkedDepartmentId} checkedDepartmentId={this.props.checkedDepartmentId}
importDepartmentDialogToggle={this.props.importDepartmentDialogToggle}
/> />
); );
}); });
@@ -63,9 +81,37 @@ class WorkWeixinDepartmentsTreeNode extends Component {
return ( return (
<Fragment> <Fragment>
{isChildrenShow && {isChildrenShow &&
<div className={nodeInnerClass} onClick={() => this.props.onChangeDepartment(department.id)}> <div
className={nodeInnerClass}
onClick={() => this.props.onChangeDepartment(department.id)}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<i className={toggleClass} onClick={(e) => this.toggleChildren(e)}></i>{' '} <i className={toggleClass} onClick={(e) => this.toggleChildren(e)}></i>{' '}
<span className="tree-node-text">{department.name}</span> <span className="tree-node-text">{department.name}</span>
{isPro &&
<Dropdown
isOpen={this.state.dropdownOpen}
toggle={this.dropdownToggle}
direction="down"
style={this.state.active ? {} : { opacity: 0 }}
>
<DropdownToggle
tag='i'
className='fa fa-ellipsis-v cursor-pointer attr-action-icon'
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.dropdownOpen}
>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem
onClick={this.props.importDepartmentDialogToggle.bind(this, department)}
id={department.id}
>{'导入部门'}</DropdownItem>
</DropdownMenu>
</Dropdown>
}
</div> </div>
} }
{this.state.isChildrenShow && {this.state.isChildrenShow &&

View File

@@ -8,6 +8,7 @@ const WorkWeixinDepartmentsTreePanelPropTypes = {
departmentsTree: PropTypes.array.isRequired, departmentsTree: PropTypes.array.isRequired,
onChangeDepartment: PropTypes.func.isRequired, onChangeDepartment: PropTypes.func.isRequired,
checkedDepartmentId: PropTypes.number.isRequired, checkedDepartmentId: PropTypes.number.isRequired,
importDepartmentDialogToggle: PropTypes.func.isRequired,
}; };
class WorkWeixinDepartmentsTreePanel extends Component { class WorkWeixinDepartmentsTreePanel extends Component {
@@ -33,6 +34,7 @@ class WorkWeixinDepartmentsTreePanel extends Component {
isChildrenShow={true} isChildrenShow={true}
onChangeDepartment={this.props.onChangeDepartment} onChangeDepartment={this.props.onChangeDepartment}
checkedDepartmentId={this.props.checkedDepartmentId} checkedDepartmentId={this.props.checkedDepartmentId}
importDepartmentDialogToggle={this.props.importDepartmentDialogToggle}
/> />
); );
})} })}

View File

@@ -5,6 +5,7 @@ import logging
import requests import requests
import json import json
from seaserv import seafile_api, ccnet_api
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response from rest_framework.response import Response
@@ -13,6 +14,8 @@ from rest_framework import status
from seahub.api2.authentication import TokenAuthentication from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error from seahub.api2.utils import api_error
from seahub.api2.permissions import IsProVersion
from seahub.work_weixin.utils import handler_work_weixin_api_response, \ from seahub.work_weixin.utils import handler_work_weixin_api_response, \
get_work_weixin_access_token, admin_work_weixin_departments_check, \ get_work_weixin_access_token, admin_work_weixin_departments_check, \
update_work_weixin_user_info update_work_weixin_user_info
@@ -21,10 +24,13 @@ from seahub.work_weixin.settings import WORK_WEIXIN_DEPARTMENTS_URL, \
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.utils.auth import gen_user_virtual_id from seahub.utils.auth import gen_user_virtual_id
from seahub.auth.models import SocialAuthUser from seahub.auth.models import SocialAuthUser
from seahub.group.utils import validate_group_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
WORK_WEIXIN_DEPARTMENT_FIELD = 'department' WORK_WEIXIN_DEPARTMENT_FIELD = 'department'
WORK_WEIXIN_DEPARTMENT_MEMBERS_FIELD = 'userlist' WORK_WEIXIN_DEPARTMENT_MEMBERS_FIELD = 'userlist'
DEPARTMENT_OWNER = 'system admin'
# # uid = corpid + '_' + userid # # uid = corpid + '_' + userid
# from social_django.models import UserSocialAuth # from social_django.models import UserSocialAuth
@@ -149,7 +155,6 @@ def _handler_work_weixin_user_data(api_user, social_auth_queryset):
def _import_user_from_work_weixin(email, api_user): def _import_user_from_work_weixin(email, api_user):
api_user['username'] = email api_user['username'] = email
uid = WORK_WEIXIN_UID_PREFIX + api_user.get('userid') uid = WORK_WEIXIN_UID_PREFIX + api_user.get('userid')
try: try:
@@ -204,3 +209,235 @@ class AdminWorkWeixinUsersBatch(APIView):
failed.append(error_data) failed.append(error_data)
return Response({'success': success, 'failed': failed}) return Response({'success': success, 'failed': failed})
class AdminWorkWeixinDepartmentsImport(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser,)
def _list_departments_from_work_weixin(self, access_token, department_id):
data = {
'access_token': access_token,
'id': department_id,
}
api_response = requests.get(WORK_WEIXIN_DEPARTMENTS_URL, params=data)
api_response_dic = handler_work_weixin_api_response(api_response)
if not api_response_dic:
logger.error('can not get work weixin departments response')
return None
if WORK_WEIXIN_DEPARTMENT_FIELD not in api_response_dic:
logger.error(json.dumps(api_response_dic))
logger.error('can not get department list in work weixin departments response')
return None
return api_response_dic[WORK_WEIXIN_DEPARTMENT_FIELD]
def _list_department_members_from_work_weixin(self, access_token, department_id):
data = {
'access_token': access_token,
'department_id': department_id,
'fetch_child': 1,
}
api_response = requests.get(WORK_WEIXIN_DEPARTMENT_MEMBERS_URL, params=data)
api_response_dic = handler_work_weixin_api_response(api_response)
if not api_response_dic:
logger.error('can not get work weixin department members response')
return None
if WORK_WEIXIN_DEPARTMENT_MEMBERS_FIELD not in api_response_dic:
logger.error(json.dumps(api_response_dic))
logger.error('can not get userlist in work weixin department members response')
return None
return api_response_dic[WORK_WEIXIN_DEPARTMENT_MEMBERS_FIELD]
def _admin_check_group_name_conflict(self, new_group_name):
checked_groups = ccnet_api.search_groups(new_group_name, -1, -1)
for g in checked_groups:
if g.group_name == new_group_name:
return True, g
return False, None
def _api_department_success_msg(self, department_obj_id, department_obj_name, group_id):
return {
'type': 'department',
'department_id': department_obj_id,
'department_name': department_obj_name,
'group_id': group_id,
}
def _api_department_failed_msg(self, department_obj_id, department_obj_name, msg):
return {
'type': 'department',
'department_id': department_obj_id,
'department_name': department_obj_name,
'msg': msg,
}
def _api_user_success_msg(self, email, api_user_name, department_obj_id, group_id):
return {
'type': 'user',
'email': email,
'api_user_name': api_user_name,
'department_id': department_obj_id,
'group_id': group_id,
}
def _api_user_failed_msg(self, email, api_user_name, department_obj_id, msg):
return {
'type': 'user',
'email': email,
'api_user_name': api_user_name,
'department_id': department_obj_id,
'msg': msg,
}
def post(self, request):
"""import department from work weixin
permission: IsProVersion
"""
# argument check
department_id = request.data.get('work_weixin_department_id')
try:
department_id = int(department_id)
except Exception as e:
logger.error(e)
error_msg = 'work_weixin_department_ids invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# is pro version and work weixin check
if not IsProVersion or not admin_work_weixin_departments_check():
error_msg = 'Feature is not enabled.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
access_token = get_work_weixin_access_token()
if not access_token:
logger.error('can not get work weixin access_token')
error_msg = '获取企业微信组织架构失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# list departments from work weixin
api_department_list = self._list_departments_from_work_weixin(access_token, department_id)
if api_department_list is None:
error_msg = '获取企业微信组织架构失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# list department members from work weixin
api_user_list = self._list_department_members_from_work_weixin(access_token, department_id)
if api_user_list is None:
error_msg = '获取企业微信组织架构成员失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# main
success = list()
failed = list()
department_map_to_group_dict = dict()
for index, department_obj in enumerate(api_department_list):
# check department argument
new_group_name = department_obj.get('name')
department_obj_id = department_obj.get('id')
if department_obj_id is None or not new_group_name or not validate_group_name(new_group_name):
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '部门参数错误')
failed.append(failed_msg)
continue
# check parent group
if index == 0:
parent_group_id = -1
else:
parent_department_id = department_obj.get('parentid')
parent_group_id = department_map_to_group_dict.get(parent_department_id)
if parent_group_id is None:
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '父级部门不存在')
failed.append(failed_msg)
continue
# check department exist by group name
exist, exist_group = self._admin_check_group_name_conflict(new_group_name)
if exist:
department_map_to_group_dict[department_obj_id] = exist_group.id
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '部门已存在')
failed.append(failed_msg)
continue
# import department
try:
group_id = ccnet_api.create_group(
new_group_name, DEPARTMENT_OWNER, parent_group_id=parent_group_id)
seafile_api.set_group_quota(group_id, -2)
department_map_to_group_dict[department_obj_id] = group_id
success_msg = self._api_department_success_msg(
department_obj_id, new_group_name, group_id)
success.append(success_msg)
except Exception as e:
logger.error(e)
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '部门导入失败')
failed.append(failed_msg)
# todo filter ccnet User database
social_auth_queryset = SocialAuthUser.objects.filter(
provider=WORK_WEIXIN_PROVIDER, uid__contains=WORK_WEIXIN_UID_PREFIX)
# import api_user
for api_user in api_user_list:
uid = WORK_WEIXIN_UID_PREFIX + api_user.get('userid', '')
api_user['contact_email'] = api_user['email']
api_user_name = api_user.get('name')
# determine the user exists
if social_auth_queryset.filter(uid=uid).exists():
email = social_auth_queryset.get(uid=uid).username
else:
# create user
email = gen_user_virtual_id()
create_user_success = _import_user_from_work_weixin(email, api_user)
if not create_user_success:
failed_msg = self._api_user_failed_msg(
'', api_user_name, department_id, '导入用户失败')
failed.append(failed_msg)
continue
# bind user to department
api_user_department_list = api_user.get('department')
for department_obj_id in api_user_department_list:
group_id = department_map_to_group_dict.get(department_obj_id)
if group_id is None:
# the api_user also exist in the brother department which not import
continue
if ccnet_api.is_group_user(group_id, email):
failed_msg = self._api_user_failed_msg(
email, api_user_name, department_obj_id, '部门成员已存在')
failed.append(failed_msg)
continue
try:
ccnet_api.group_add_member(group_id, DEPARTMENT_OWNER, email)
success_msg = self._api_user_success_msg(
email, api_user_name, department_obj_id, group_id)
success.append(success_msg)
except Exception as e:
logger.error(e)
failed_msg = self._api_user_failed_msg(
email, api_user_name, department_id, '导入部门成员失败')
failed.append(failed_msg)
return Response({
'success': success,
'failed': failed,
})

View File

@@ -141,7 +141,7 @@ from seahub.api2.endpoints.admin.user_activities import UserActivitiesView
from seahub.api2.endpoints.admin.file_scan_records import AdminFileScanRecords from seahub.api2.endpoints.admin.file_scan_records import AdminFileScanRecords
from seahub.api2.endpoints.admin.notifications import AdminNotificationsView from seahub.api2.endpoints.admin.notifications import AdminNotificationsView
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \ from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch, AdminWorkWeixinDepartmentsImport
urlpatterns = [ urlpatterns = [
url(r'^accounts/', include('seahub.base.registration_urls')), url(r'^accounts/', include('seahub.base.registration_urls')),
@@ -549,6 +549,7 @@ urlpatterns = [
url(r'^api/v2.1/admin/work-weixin/departments/$', AdminWorkWeixinDepartments.as_view(), name='api-v2.1-admin-work-weixin-departments'), url(r'^api/v2.1/admin/work-weixin/departments/$', AdminWorkWeixinDepartments.as_view(), name='api-v2.1-admin-work-weixin-departments'),
url(r'^api/v2.1/admin/work-weixin/departments/(?P<department_id>\d+)/members/$', AdminWorkWeixinDepartmentMembers.as_view(), name='api-v2.1-admin-work-weixin-department-members'), url(r'^api/v2.1/admin/work-weixin/departments/(?P<department_id>\d+)/members/$', AdminWorkWeixinDepartmentMembers.as_view(), name='api-v2.1-admin-work-weixin-department-members'),
url(r'^api/v2.1/admin/work-weixin/users/batch/$', AdminWorkWeixinUsersBatch.as_view(), name='api-v2.1-admin-work-weixin-users'), url(r'^api/v2.1/admin/work-weixin/users/batch/$', AdminWorkWeixinUsersBatch.as_view(), name='api-v2.1-admin-work-weixin-users'),
url(r'^api/v2.1/admin/work-weixin/departments/import/$', AdminWorkWeixinDepartmentsImport.as_view(), name='api-v2.1-admin-work-weixin-department-import'),
### system admin ### ### system admin ###
url(r'^sysadmin/$', sysadmin, name='sysadmin'), url(r'^sysadmin/$', sysadmin, name='sysadmin'),