1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-30 13:23:14 +00:00

Merge branch '8.0' into master

This commit is contained in:
lian 2021-04-19 11:05:01 +08:00
commit d6fe19dd27
26 changed files with 468 additions and 86 deletions

View File

@ -33,6 +33,7 @@ class NoticeItem extends React.Component {
let groupStaff = detail.group_staff_name;
// group name does not support special characters
let userHref = siteRoot + 'profile/' + detail.group_staff_email + '/';
let groupHref = siteRoot + 'group/' + detail.group_id + '/';
let groupName = detail.group_name;
@ -58,15 +59,22 @@ class NoticeItem extends React.Component {
let path = detail.path;
let notice = '';
let repoLink = '<a href=' + repoUrl + '>' + repoName + '</a>';
// 1. handle translate
if (path === '/') { // share repo
notice = gettext('{share_from} has shared a library named {repo_link} to you.');
} else { // share folder
notice = gettext('{share_from} has shared a folder named {repo_link} to you.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', repoLink);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
return {avatar_url, notice};
}
@ -84,16 +92,24 @@ class NoticeItem extends React.Component {
let path = detail.path;
let notice = '';
let repoLink = '<a href=' + repoUrl + '>' + repoName + '</a>';
let groupLink = '<a href=' + groupUrl + '>' + groupName + '</a>';
// 1. handle translate
if (path === '/') {
notice = gettext('{share_from} has shared a library named {repo_link} to group {group_link}.');
} else {
notice = gettext('{share_from} has shared a folder named {repo_link} to group {group_link}.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', repoLink);
notice = notice.replace('{group_link}', groupLink);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = notice.replace('{group_link}', `{tagB}${groupName}{/tagB}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
notice = notice.replace('{tagB}', `<a href='${Utils.encodePath(groupUrl)}'>`);
notice = notice.replace('{/tagB}', '</a>');
return {avatar_url, notice};
}
@ -105,32 +121,50 @@ class NoticeItem extends React.Component {
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
// 1. handle translate
let notice = gettext('{user} has transfered a library named {repo_link} to you.');
let repoLink = '<a href=' + repoUrl + '>' + repoName + '</a>';
// 2. handle xss(cross-site scripting)
notice = notice.replace('{user}', repoOwner);
notice = notice.replace('{repo_link}', repoLink);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(repoUrl)}>`);
notice = notice.replace('{/tagA}', '</a>');
return {avatar_url, notice};
}
if (noticeType === MSG_TYPE_FILE_UPLOADED) {
let avatar_url = detail.uploaded_user_avatar_url;
let fileName = detail.file_name;
let fileLink = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + Utils.encodePath(detail.file_path);
let fileLink = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + detail.file_path;
let folderName = detail.folder_name;
let folderLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + Utils.encodePath(detail.folder_path);
let folderLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + detail.folder_path;
let notice = '';
if (detail.repo_id) { // todo is repo exist ?
let uploadFileLink = '<a href=' + fileLink + '>' + fileName + '</a>';
let uploadedLink = '<a href=' + folderLink + '>' + folderName + '</a>';
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded to {uploaded_link}.');
notice = gettext('A file named {upload_file_link} is uploaded to {uploaded_link}.');
notice = notice.replace('{upload_file_link}', uploadFileLink);
notice = notice.replace('{uploaded_link}', uploadedLink);
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `{tagA}${fileName}{/tagA}`);
notice = notice.replace('{uploaded_link}', `{tagB}${folderName}{/tagB}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileLink)}>`);
notice = notice.replace('{/tagA}', '</a>');
notice = notice.replace('{tagB}', `<a href=${Utils.encodePath(folderLink)}>`);
notice = notice.replace('{/tagB}', '</a>');
} else {
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded to {uploaded_link}.');
notice = notice.replace('{upload_file_link}', fileName);
notice = notice.replace('{uploaded_link}', '<strong>Deleted Library</strong>');
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `${fileName}`);
notice = Utils.HTMLescape(notice);
notice = notice.replace('{uploaded_link}', `<strong>Deleted Library</strong>`);
}
return {avatar_url, notice};
}
@ -144,10 +178,17 @@ class NoticeItem extends React.Component {
let fileName = detail.file_name;
let fileUrl = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + detail.file_path;
// 1. handle translate
let notice = gettext('File {file_link} has a new comment form user {author}.');
let fileLink = '<a href=' + fileUrl + '>' + fileName + '</a>';
notice = notice.replace('{file_link}', fileLink);
// 2. handle xss(cross-site scripting)
notice = notice.replace('{file_link}', `{tagA}${fileName}{/tagA}`);
notice = notice.replace('{author}', author);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileUrl)}>`);
notice = notice.replace('{/tagA}', '</a>');
return {avatar_url, notice};
}

View File

@ -84,6 +84,7 @@ class MainPanel extends Component {
const isViewingFile = this.props.pathExist && !this.props.isDataLoading && this.props.isViewFile;
return (
<div className="main-panel wiki-main-panel o-hidden">
<div className="main-panel-hide hide">{this.props.content}</div>
<div className={`main-panel-north panel-top ${this.props.permission === 'rw' ? 'border-left-show' : ''}`}>
{!username &&
<Fragment>

View File

@ -20,7 +20,7 @@ moment.locale(window.app.config.lang);
let loginUser = window.app.pageOptions.name;
const {
token, dirName, sharedBy,
token, dirName, dirPath, sharedBy,
repoID, path,
mode, thumbnailSize, zipped,
trafficOverLimit, canDownload,
@ -309,7 +309,7 @@ class SharedDirView extends React.Component {
ref={uploader => this.uploader = uploader}
dragAndDrop={false}
token={token}
path={path}
path={dirPath}
repoID={repoID}
onFileUploadSuccess={this.onFileUploadSuccess}
/>

View File

@ -24,6 +24,7 @@ from seahub.api2.utils import to_python_boolean, api_error
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.permissions import IsProVersion
from seahub.api2.authentication import TokenAuthentication
from seahub.auth.models import ExternalDepartment
logger = logging.getLogger(__name__)
@ -254,6 +255,12 @@ class AdminAddressBookGroup(APIView):
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# del external department
try:
ExternalDepartment.objects.delete_by_group_id(group_id)
except Exception as e:
logger.error(e)
# send admin operation log signal
group_owner = group.creator_name
group_name = group.group_name

View File

@ -26,13 +26,14 @@ from seahub.auth.models import SocialAuthUser
from seahub.profile.models import Profile
from seahub.avatar.models import Avatar
from seahub.group.utils import validate_group_name
from seahub.auth.models import ExternalDepartment
from seahub.dingtalk.utils import dingtalk_get_access_token
from seahub.dingtalk.settings import ENABLE_DINGTALK, \
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL, \
DINGTALK_DEPARTMENT_USER_SIZE
DINGTALK_DEPARTMENT_USER_SIZE, DINGTALK_PROVIDER
DEPARTMENT_OWNER = 'system admin'
@ -232,15 +233,6 @@ class AdminDingtalkDepartmentsImport(APIView):
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser, IsProVersion)
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',
@ -301,12 +293,14 @@ class AdminDingtalkDepartmentsImport(APIView):
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# get department list
# https://developers.dingtalk.com/document/app/obtain-the-department-list
data = {'access_token': access_token, 'id': department_id}
current_department_resp_json = requests.get(DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, params=data).json()
current_department_list = [current_department_resp_json]
sub_department_resp_json = requests.get(DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, params=data).json()
sub_department_list = sub_department_resp_json.get('department', [])
department_list = current_department_list + sub_department_list
department_list = sorted(department_list, key=lambda x:x['id'])
# get department user list
data = {
@ -327,6 +321,7 @@ class AdminDingtalkDepartmentsImport(APIView):
# check department argument
new_group_name = department_obj.get('name')
department_obj_id = department_obj.get('id')
parent_department_id = department_obj.get('parentid', 0)
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, '部门参数错误')
@ -337,7 +332,6 @@ class AdminDingtalkDepartmentsImport(APIView):
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:
@ -346,10 +340,11 @@ class AdminDingtalkDepartmentsImport(APIView):
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
# check department exist
exist_department = ExternalDepartment.objects.get_by_provider_and_outer_id(
DINGTALK_PROVIDER, department_obj_id)
if exist_department:
department_map_to_group_dict[department_obj_id] = exist_department.group_id
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '部门已存在')
failed.append(failed_msg)
@ -362,6 +357,12 @@ class AdminDingtalkDepartmentsImport(APIView):
seafile_api.set_group_quota(group_id, -2)
ExternalDepartment.objects.create(
group_id=group_id,
provider=DINGTALK_PROVIDER,
outer_id=department_obj_id,
)
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)

View File

@ -13,6 +13,7 @@ from rest_framework.views import APIView
from django.db.models import Q
from django.core.cache import cache
from django.utils.translation import ugettext as _
from django.utils.timezone import make_naive, is_aware
from seaserv import seafile_api, ccnet_api
@ -81,17 +82,32 @@ def get_user_last_access_time(email, last_login_time):
if update_events:
update_last_access = update_events[0].timestamp
# before make_naive
# 2021-04-09 05:32:30+00:00
# tzinfo: UTC
# after make_naive
# 2021-04-09 13:32:30
# tzinfo: None
last_access_time_list = []
if last_login_time:
if is_aware(last_login_time):
last_login_time = make_naive(last_login_time)
last_access_time_list.append(last_login_time)
if device_last_access:
if is_aware(device_last_access):
device_last_access = make_naive(device_last_access)
last_access_time_list.append(device_last_access)
if audit_last_access:
if is_aware(audit_last_access):
audit_last_access = make_naive(audit_last_access)
last_access_time_list.append(utc_to_local(audit_last_access))
if update_last_access:
if is_aware(update_last_access):
update_last_access = make_naive(update_last_access)
last_access_time_list.append(utc_to_local(update_last_access))
if not last_access_time_list:
@ -914,8 +930,15 @@ class AdminSearchUser(APIView):
user.institution = profile.institution
data = []
has_appended = []
for user in users:
if user.email in has_appended:
continue
else:
has_appended.append(user.email)
info = {}
info['email'] = user.email
info['name'] = email2nickname(user.email)

View File

@ -26,6 +26,7 @@ from seahub.base.accounts import User
from seahub.utils.auth import gen_user_virtual_id
from seahub.auth.models import SocialAuthUser
from seahub.group.utils import validate_group_name
from seahub.auth.models import ExternalDepartment
logger = logging.getLogger(__name__)
WORK_WEIXIN_DEPARTMENT_FIELD = 'department'
@ -226,6 +227,7 @@ class AdminWorkWeixinDepartmentsImport(APIView):
permission_classes = (IsAdminUser,)
def _list_departments_from_work_weixin(self, access_token, department_id):
# https://work.weixin.qq.com/api/doc/90000/90135/90208
data = {
'access_token': access_token,
'id': department_id,
@ -264,15 +266,6 @@ class AdminWorkWeixinDepartmentsImport(APIView):
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',
@ -341,6 +334,7 @@ class AdminWorkWeixinDepartmentsImport(APIView):
if api_department_list is None:
error_msg = '获取企业微信组织架构失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
api_department_list = sorted(api_department_list, key=lambda x:x['id'])
# list department members from work weixin
api_user_list = self._list_department_members_from_work_weixin(access_token, department_id)
@ -357,6 +351,7 @@ class AdminWorkWeixinDepartmentsImport(APIView):
# check department argument
new_group_name = department_obj.get('name')
department_obj_id = department_obj.get('id')
parent_department_id = department_obj.get('parentid', 0)
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, '部门参数错误')
@ -367,7 +362,6 @@ class AdminWorkWeixinDepartmentsImport(APIView):
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:
@ -376,10 +370,11 @@ class AdminWorkWeixinDepartmentsImport(APIView):
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
# check department exist
exist_department = ExternalDepartment.objects.get_by_provider_and_outer_id(
WORK_WEIXIN_PROVIDER, department_obj_id)
if exist_department:
department_map_to_group_dict[department_obj_id] = exist_department.group_id
failed_msg = self._api_department_failed_msg(
department_obj_id, new_group_name, '部门已存在')
failed.append(failed_msg)
@ -392,6 +387,12 @@ class AdminWorkWeixinDepartmentsImport(APIView):
seafile_api.set_group_quota(group_id, -2)
ExternalDepartment.objects.create(
group_id=group_id,
provider=WORK_WEIXIN_PROVIDER,
outer_id=department_obj_id,
)
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)

View File

@ -392,11 +392,8 @@ class AccountInfo(APIView):
username, file_updates_email_interval)
if collaborate_email_interval is not None:
if collaborate_email_interval <= 0:
UserOptions.objects.unset_collaborate_email_interval(username)
else:
UserOptions.objects.set_collaborate_email_interval(
username, collaborate_email_interval)
UserOptions.objects.set_collaborate_email_interval(
username, collaborate_email_interval)
return Response(self._get_account_info(request))

View File

@ -162,6 +162,28 @@ class SocialAuthUser(models.Model):
db_table = 'social_auth_usersocialauth'
class ExternalDepartmentManager(models.Manager):
def get_by_provider_and_outer_id(self, provider, outer_id):
return self.filter(provider=provider, outer_id=outer_id).first()
def delete_by_group_id(self, group_id):
self.filter(group_id=group_id).delete()
class ExternalDepartment(models.Model):
group_id = models.IntegerField(unique=True)
provider = models.CharField(max_length=32)
outer_id = models.BigIntegerField()
objects = ExternalDepartmentManager()
class Meta:
"""Meta data"""
app_label = "base"
unique_together = ('provider', 'outer_id')
db_table = 'external_department'
# # handle signals
from django.dispatch import receiver
from registration.signals import user_deleted

View File

View File

@ -0,0 +1,122 @@
import logging
import requests
import json
from datetime import datetime
from django.core.management.base import BaseCommand
from seaserv import ccnet_api
from seahub.dingtalk.utils import dingtalk_get_access_token
from seahub.dingtalk.settings import ENABLE_DINGTALK, \
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, DINGTALK_PROVIDER
from seahub.auth.models import ExternalDepartment
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Fix sync the imported dingtalk departments to the database."
def println(self, msg):
self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
def log_error(self, msg):
logger.error(msg)
self.println(msg)
def log_info(self, msg):
logger.info(msg)
self.println(msg)
def log_debug(self, msg):
logger.debug(msg)
self.println(msg)
def handle(self, *args, **options):
self.log_debug('Start fix sync dingtalk departments...')
self.do_action()
self.log_debug('Finish fix sync dingtalk departments.\n')
def get_group_by_name(self, group_name):
checked_groups = ccnet_api.search_groups(group_name, -1, -1)
for g in checked_groups:
if g.group_name == group_name:
return g
return None
def list_departments_from_dingtalk(self, access_token):
# https://developers.dingtalk.com/document/app/obtain-the-department-list
data = {
'access_token': access_token,
}
api_response = requests.get(
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, params=data)
api_response_dic = api_response.json()
if not api_response_dic:
self.log_error('can not get dingtalk departments response')
return None
if 'department' not in api_response_dic:
self.log_error(json.dumps(api_response_dic))
self.log_error(
'can not get department list in dingtalk departments response')
return None
return api_response_dic['department']
def do_action(self):
# dingtalk check
if not ENABLE_DINGTALK:
self.log_error('Feature is not enabled.')
return
access_token = dingtalk_get_access_token()
if not access_token:
self.log_error('can not get dingtalk access_token')
return
# get department list
# https://developers.dingtalk.com/document/app/obtain-the-department-list-v2
api_department_list = self.list_departments_from_dingtalk(
access_token)
if api_department_list is None:
self.log_error('获取钉钉组织架构失败')
return
api_department_list = sorted(
api_department_list, key=lambda x: x['id'])
self.log_debug(
'Total %d dingtalk departments.' % len(api_department_list))
# main
count = 0
exists_count = 0
for department_obj in api_department_list:
# check department argument
group_name = department_obj.get('name')
department_obj_id = department_obj.get('id')
if department_obj_id is None or not group_name:
continue
# check department exist
exist_department = ExternalDepartment.objects.get_by_provider_and_outer_id(
DINGTALK_PROVIDER, department_obj_id)
if exist_department:
exists_count += 1
continue
# sync to db
group = self.get_group_by_name(group_name)
if group:
ExternalDepartment.objects.create(
group_id=group.id,
provider=DINGTALK_PROVIDER,
outer_id=department_obj_id,
)
count += 1
self.log_debug('%d dingtalk departments exists in db.' % exists_count)
self.log_debug('Sync %d dingtalk departments to db.' % count)

View File

@ -28,3 +28,6 @@ DINGTALK_DEPARTMENT_USER_SIZE = 100
DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL = getattr(settings, 'DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL', 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2')
DINGTALK_GET_DETAILED_USER_INFO_URL = getattr(settings, 'DINGTALK_GET_DETAILED_USER_INFO_URL', 'https://oapi.dingtalk.com/user/get')
# constants
DINGTALK_PROVIDER = 'dingtalk'

View File

@ -23,7 +23,8 @@ from seahub.invitations.models import Invitation
from seahub.profile.models import Profile
from seahub.constants import HASH_URLS
from seahub.utils import get_site_name
from seahub.options.models import UserOptions, KEY_COLLABORATE_EMAIL_INTERVAL, KEY_COLLABORATE_LAST_EMAILED_TIME
from seahub.options.models import UserOptions, KEY_COLLABORATE_EMAIL_INTERVAL, \
KEY_COLLABORATE_LAST_EMAILED_TIME, DEFAULT_COLLABORATE_EMAIL_INTERVAL
# Get an instance of a logger
logger = logging.getLogger(__name__)
@ -208,34 +209,49 @@ class Command(BaseCommand):
def get_user_language(self, username):
return Profile.objects.get_user_language(username)
def do_action(self):
emails = []
user_file_updates_email_intervals = []
for ele in UserOptions.objects.filter(
option_key=KEY_COLLABORATE_EMAIL_INTERVAL):
try:
user_file_updates_email_intervals.append(
(ele.email, int(ele.option_val))
)
emails.append(ele.email)
except Exception as e:
logger.error(e)
self.stderr.write('[%s]: %s' % (str(datetime.datetime.now()), e))
continue
def get_user_intervals_and_notices(self):
"""
filter users who have collaborate-notices in last longest interval
And right now, the longest interval is DEFAULT_COLLABORATE_EMAIL_INTERVAL
"""
last_longest_interval_time = datetime.datetime.now() - datetime.timedelta(
seconds=DEFAULT_COLLABORATE_EMAIL_INTERVAL)
user_last_emailed_time_dict = {}
for ele in UserOptions.objects.filter(option_key=KEY_COLLABORATE_LAST_EMAILED_TIME).filter(email__in=emails):
all_unseen_notices = UserNotification.objects.get_all_notifications(
seen=False, time_since=last_longest_interval_time).order_by('-timestamp')
results = {}
for notice in all_unseen_notices:
if notice.to_user not in results:
results[notice.to_user] = {'notices': [notice], 'interval': DEFAULT_COLLABORATE_EMAIL_INTERVAL}
else:
results[notice.to_user]['notices'].append(notice)
user_options = UserOptions.objects.filter(
email__in=results.keys(), option_key=KEY_COLLABORATE_EMAIL_INTERVAL)
for option in user_options:
email, interval = option.email, option.option_val
try:
user_last_emailed_time_dict[ele.email] = datetime.datetime.strptime(
ele.option_val, "%Y-%m-%d %H:%M:%S")
except Exception as e:
logger.error(e)
self.stderr.write('[%s]: %s' % (str(datetime.datetime.now()), e))
continue
interval = int(interval)
except ValueError:
logger.warning('user: %s, %s invalid, val: %s', email, KEY_COLLABORATE_EMAIL_INTERVAL, interval)
interval = DEFAULT_COLLABORATE_EMAIL_INTERVAL
if interval <= 0:
del results[email]
else:
results[email]['interval'] = interval
return [(key, value['interval'], value['notices']) for key, value in results.items()]
def do_action(self):
user_interval_notices = self.get_user_intervals_and_notices()
last_emailed_list = UserOptions.objects.filter(option_key=KEY_COLLABORATE_LAST_EMAILED_TIME).values_list('email', 'option_val')
user_last_emailed_time_dict = {le[0]: datetime.datetime.strptime(le[1], "%Y-%m-%d %H:%M:%S") for le in last_emailed_list}
# save current language
cur_language = translation.get_language()
for (to_user, interval_val) in user_file_updates_email_intervals:
for (to_user, interval_val, notices) in user_interval_notices:
# get last_emailed_time if any, defaults to today 00:00:00.0
last_emailed_time = user_last_emailed_time_dict.get(to_user, None)
now = datetime.datetime.now().replace(microsecond=0)
@ -246,10 +262,8 @@ class Command(BaseCommand):
if (now - last_emailed_time).total_seconds() < interval_val:
continue
# get notices
user_notices_qs = UserNotification.objects.get_all_notifications(seen=False, time_since=last_emailed_time)
user_notices, count = list(user_notices_qs), user_notices_qs.count()
if not count:
user_notices = list(filter(lambda notice: notice.timestamp > last_emailed_time, notices))
if not user_notices:
continue
# get and active user language
@ -312,7 +326,7 @@ class Command(BaseCommand):
to_user = contact_email # use contact email if any
c = {
'to_user': to_user,
'notice_count': count,
'notice_count': len(notices),
'notices': notices,
'user_name': user_name,
}

View File

@ -39,6 +39,8 @@ KEY_FILE_UPDATES_LAST_EMAILED_TIME = "file_updates_last_emailed_time"
KEY_COLLABORATE_EMAIL_INTERVAL = 'collaborate_email_interval'
KEY_COLLABORATE_LAST_EMAILED_TIME = 'collaborate_last_emailed_time'
DEFAULT_COLLABORATE_EMAIL_INTERVAL = 3600
class CryptoOptionNotSetError(Exception):
pass

View File

@ -18,7 +18,7 @@ from seahub.utils import is_org_context, is_pro_version, is_valid_username
from seahub.base.accounts import User, UNUSABLE_PASSWORD
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.contacts.models import Contact
from seahub.options.models import UserOptions, CryptoOptionNotSetError
from seahub.options.models import UserOptions, CryptoOptionNotSetError, DEFAULT_COLLABORATE_EMAIL_INTERVAL
from seahub.utils import is_ldap_user
from seahub.utils.two_factor_auth import has_two_factor_auth
from seahub.views import get_owned_repo_list
@ -87,7 +87,7 @@ def edit_profile(request):
file_updates_email_interval = UserOptions.objects.get_file_updates_email_interval(username)
file_updates_email_interval = file_updates_email_interval if file_updates_email_interval is not None else 0
collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(username)
collaborate_email_interval = collaborate_email_interval if collaborate_email_interval is not None else 0
collaborate_email_interval = collaborate_email_interval if collaborate_email_interval is not None else DEFAULT_COLLABORATE_EMAIL_INTERVAL
if work_weixin_oauth_check():
enable_wechat_work = True

View File

@ -248,6 +248,7 @@ INSTALLED_APPS = [
'seahub.file_tags',
'seahub.related_files',
'seahub.work_weixin',
'seahub.dingtalk',
'seahub.file_participants',
'seahub.repo_api_tokens',
'seahub.abuse_reports',

View File

@ -20,6 +20,7 @@
window.shared = {
pageOptions: {
dirName: '{{ dir_name|escapejs }}',
dirPath: '{{ dir_path|escapejs }}',
sharedBy: '{{ username|email2nickname|escapejs }}',
repoID: '{{repo.id}}',
path: '{{ path|escapejs }}',

View File

@ -29,6 +29,7 @@ from django.utils.translation import ugettext as _
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseNotModified
from django.utils.http import urlquote
from django.utils.html import escape
from django.utils.timezone import make_naive, is_aware
from django.views.static import serve as django_static_serve
from seahub.auth import REDIRECT_FIELD_NAME
@ -1332,6 +1333,12 @@ def get_origin_repo_info(repo_id):
def within_time_range(d1, d2, maxdiff_seconds):
'''Return true if two datetime.datetime object differs less than the given seconds'''
if is_aware(d1):
d1 = make_naive(d1)
if is_aware(d2):
d2 = make_naive(d2)
delta = d2 - d1 if d2 > d1 else d1 - d2
# delta.total_seconds() is only available in python 2.7+
diff = (delta.microseconds + (delta.seconds + delta.days*24*3600) * 1e6) / 1e6

View File

@ -60,6 +60,11 @@ def timestamp_to_isoformat_timestr(timestamp):
# https://pypi.org/project/pytz/
def datetime_to_isoformat_timestr(datetime):
from django.utils.timezone import make_naive, is_aware
if is_aware(datetime):
datetime = make_naive(datetime)
try:
# This library only supports two ways of building a localized time.
# The first is to use the localize() method provided by the pytz library.

View File

@ -344,6 +344,7 @@ def view_shared_dir(request, fileshare):
'path': req_path,
'username': username,
'dir_name': dir_name,
'dir_path': real_path,
'file_list': file_list,
'dir_list': dir_list,
'zipped': zipped,

View File

@ -0,0 +1,121 @@
import logging
import requests
import json
from datetime import datetime
from django.core.management.base import BaseCommand
from seaserv import ccnet_api
from seahub.work_weixin.utils import handler_work_weixin_api_response, \
get_work_weixin_access_token, admin_work_weixin_departments_check
from seahub.work_weixin.settings import WORK_WEIXIN_DEPARTMENTS_URL, \
WORK_WEIXIN_PROVIDER
from seahub.auth.models import ExternalDepartment
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Fix sync the imported work-weixin departments to the database."
def println(self, msg):
self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
def log_error(self, msg):
logger.error(msg)
self.println(msg)
def log_info(self, msg):
logger.info(msg)
self.println(msg)
def log_debug(self, msg):
logger.debug(msg)
self.println(msg)
def handle(self, *args, **options):
self.log_debug('Start fix sync work-weixin departments...')
self.do_action()
self.log_debug('Finish fix sync work-weixin departments.\n')
def get_group_by_name(self, group_name):
checked_groups = ccnet_api.search_groups(group_name, -1, -1)
for g in checked_groups:
if g.group_name == group_name:
return g
return None
def list_departments_from_work_weixin(self, access_token):
# https://work.weixin.qq.com/api/doc/90000/90135/90208
data = {
'access_token': access_token,
}
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:
self.log_error('can not get work weixin departments response')
return None
if 'department' not in api_response_dic:
self.log_error(json.dumps(api_response_dic))
self.log_error(
'can not get department list in work weixin departments response')
return None
return api_response_dic['department']
def do_action(self):
# work weixin check
if not admin_work_weixin_departments_check():
self.log_error('Feature is not enabled.')
return
access_token = get_work_weixin_access_token()
if not access_token:
self.log_error('can not get work weixin access_token')
return
# list departments from work weixin
api_department_list = self.list_departments_from_work_weixin(
access_token)
if api_department_list is None:
self.log_error('获取企业微信组织架构失败')
return
api_department_list = sorted(
api_department_list, key=lambda x: x['id'])
self.log_debug(
'Total %d work-weixin departments.' % len(api_department_list))
# main
count = 0
exists_count = 0
for department_obj in api_department_list:
# check department argument
group_name = department_obj.get('name')
department_obj_id = department_obj.get('id')
if department_obj_id is None or not group_name:
continue
# check department exist
exist_department = ExternalDepartment.objects.get_by_provider_and_outer_id(
WORK_WEIXIN_PROVIDER, department_obj_id)
if exist_department:
exists_count += 1
continue
# sync to db
group = self.get_group_by_name(group_name)
if group:
ExternalDepartment.objects.create(
group_id=group.id,
provider=WORK_WEIXIN_PROVIDER,
outer_id=department_obj_id,
)
count += 1
self.log_debug('%d work-weixin departments exists in db.' % exists_count)
self.log_debug('Sync %d work-weixin departments to db.' % count)

View File

@ -1282,3 +1282,13 @@ CREATE TABLE `repo_auto_delete` (
PRIMARY KEY (`id`),
UNIQUE KEY `repo_id` (`repo_id`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `external_department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`provider` varchar(32) NOT NULL,
`outer_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `group_id` (`group_id`),
UNIQUE KEY `external_department_provider_outer_id_8dns6vkw_uniq` (`provider`,`outer_id`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8;

View File

@ -600,4 +600,6 @@ CREATE INDEX "ocm_share_received_from_server_url_10527b80" ON "ocm_share_receive
CREATE INDEX "ocm_share_received_repo_id_9e77a1b9" ON "ocm_share_received" ("repo_id");
CREATE INDEX "ocm_share_received_provider_id_60c873e0" ON "ocm_share_received" ("provider_id");
CREATE TABLE "repo_auto_delete" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "repo_id" varchar(36) NOT NULL UNIQUE, "days" integer NOT NULL);
CREATE TABLE "external_department" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "group_id" integer NOT NULL UNIQUE, "provider" varchar(32) NOT NULL, "outer_id" bigint NOT NULL);
CREATE UNIQUE INDEX "external_department_provider_outer_id_8dns6vkw_uniq" ON "external_department" (`provider`,`outer_id`);
COMMIT;