diff --git a/frontend/src/components/user-settings/email-notice.js b/frontend/src/components/user-settings/email-notice.js
index 408e4e14be..136cce2567 100644
--- a/frontend/src/components/user-settings/email-notice.js
+++ b/frontend/src/components/user-settings/email-notice.js
@@ -5,7 +5,8 @@ import { Utils } from '../../utils/utils';
import toaster from '../toast';
const {
- initialEmailNotificationInterval
+ fileUpdatesEmailInterval,
+ collaborateEmailInterval
} = window.app.pageOptions;
class EmailNotice extends React.Component {
@@ -14,7 +15,7 @@ class EmailNotice extends React.Component {
super(props);
// interval: in seconds
- this.intervalOptions = [
+ this.fileUpdatesOptions = [
{interval: 0, text: gettext('Don\'t send emails')},
{interval: 3600, text: gettext('Per hour')},
{interval: 14400, text: gettext('Per 4 hours')},
@@ -22,22 +23,37 @@ class EmailNotice extends React.Component {
{interval: 604800, text: gettext('Per week')}
];
+ this.collaborateOptions = [
+ {interval: 0, text: gettext('Don\'t send emails')},
+ {interval: 3600, text: gettext('Per hour') + ' (' + gettext('If notifications have not be read within one hour, they will be sent to your mailbox') + ')'}
+ ];
+
this.state = {
- currentInterval: initialEmailNotificationInterval
+ fileUpdatesEmailInterval: fileUpdatesEmailInterval,
+ collaborateEmailInterval: collaborateEmailInterval
};
}
- inputChange = (e) => {
+ inputFileUpdatesEmailIntervalChange = (e) => {
if (e.target.checked) {
this.setState({
- currentInterval: e.target.value
+ fileUpdatesEmailInterval: parseInt(e.target.value)
+ });
+ }
+ }
+
+ inputCollaborateEmailIntervalChange = (e) => {
+ if (e.target.checked) {
+ this.setState({
+ collaborateEmailInterval: parseInt(e.target.value)
});
}
}
formSubmit = (e) => {
e.preventDefault();
- seafileAPI.updateEmailNotificationInterval(this.state.currentInterval).then((res) => {
+ let { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
+ seafileAPI.updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval).then((res) => {
toaster.success(gettext('Success'));
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error);
@@ -46,23 +62,38 @@ class EmailNotice extends React.Component {
}
render() {
- const { currentInterval } = this.state;
+ const { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
return (
-
{gettext('Email Notification of File Changes')}
+
{gettext('Email Notification')}
+
{gettext('Notifications of file changes')}
{gettext('The list of added, deleted and modified files will be sent to your mailbox.')}
+
+
{gettext('Notifications of collaboration')}
+
{gettext('Whether the notifications of collaboration such as sharing library or joining group should be sent to your mailbox.')}
+
+
);
}
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index e47af3b271..7af19ccae1 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -336,8 +336,10 @@ class AccountInfo(APIView):
except InstitutionAdmin.DoesNotExist:
info['is_inst_admin'] = False
- interval = UserOptions.objects.get_file_updates_email_interval(email)
- info['email_notification_interval'] = 0 if interval is None else interval
+ file_updates_email_interval = UserOptions.objects.get_file_updates_email_interval(email)
+ info['file_updates_email_interval'] = 0 if file_updates_email_interval is None else file_updates_email_interval
+ collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(email)
+ info['collaborate_email_interval'] = 0 if collaborate_email_interval is None else collaborate_email_interval
return info
def get(self, request, format=None):
@@ -358,13 +360,20 @@ class AccountInfo(APIView):
return api_error(status.HTTP_400_BAD_REQUEST,
_("Name should not include '/'."))
- email_interval = request.data.get("email_notification_interval", None)
- if email_interval is not None:
+ file_updates_email_interval = request.data.get("file_updates_email_interval", None)
+ if file_updates_email_interval is not None:
try:
- email_interval = int(email_interval)
+ file_updates_email_interval = int(file_updates_email_interval)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
- 'email_interval invalid')
+ 'file_updates_email_interval invalid')
+ collaborate_email_interval = request.data.get("collaborate_email_interval", None)
+ if collaborate_email_interval is not None:
+ try:
+ collaborate_email_interval = int(collaborate_email_interval)
+ except ValueError:
+ return api_error(status.HTTP_400_BAD_REQUEST,
+ 'collaborate_email_interval invalid')
# update user info
@@ -375,12 +384,19 @@ class AccountInfo(APIView):
profile.nickname = name
profile.save()
- if email_interval is not None:
- if email_interval <= 0:
+ if file_updates_email_interval is not None:
+ if file_updates_email_interval <= 0:
UserOptions.objects.unset_file_updates_email_interval(username)
else:
UserOptions.objects.set_file_updates_email_interval(
- username, email_interval)
+ 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)
return Response(self._get_account_info(request))
diff --git a/seahub/notifications/management/commands/send_notices.py b/seahub/notifications/management/commands/send_notices.py
index 1138a1ce9c..a4e37217ac 100644
--- a/seahub/notifications/management/commands/send_notices.py
+++ b/seahub/notifications/management/commands/send_notices.py
@@ -23,6 +23,7 @@ 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
# Get an instance of a logger
logger = logging.getLogger(__name__)
@@ -208,64 +209,73 @@ class Command(BaseCommand):
return Profile.objects.get_user_language(username)
def do_action(self):
- now = datetime.datetime.now()
+ 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
- try:
- cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
- logger.debug('Last check time is %s' % cmd_last_check.last_check)
+ user_last_emailed_time_dict = {}
+ for ele in UserOptions.objects.filter(option_key=KEY_COLLABORATE_LAST_EMAILED_TIME).filter(email__in=emails):
+ 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
- unseen_notices = UserNotification.objects.get_all_notifications(
- seen=False, time_since=cmd_last_check.last_check)
-
- logger.debug('Update last check time to %s' % now)
- cmd_last_check.last_check = now
- cmd_last_check.save()
- except CommandsLastCheck.DoesNotExist:
- logger.debug('No last check time found, get all unread notices.')
- unseen_notices = UserNotification.objects.get_all_notifications(
- seen=False)
-
- logger.debug('Create new last check time: %s' % now)
- CommandsLastCheck(command_type=self.label, last_check=now).save()
-
- email_ctx = {}
- for notice in unseen_notices:
- if notice.to_user in email_ctx:
- email_ctx[notice.to_user] += 1
+ # save current language
+ cur_language = translation.get_language()
+ for (to_user, interval_val) in user_file_updates_email_intervals:
+ # 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)
+ if not last_emailed_time:
+ last_emailed_time = datetime.datetime.now().replace(hour=0).replace(
+ minute=0).replace(second=0).replace(microsecond=0)
else:
- email_ctx[notice.to_user] = 1
+ if (now - last_emailed_time).total_seconds() < interval_val:
+ continue
- for to_user, count in list(email_ctx.items()):
- # save current language
- cur_language = translation.get_language()
+ # 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:
+ continue
# get and active user language
user_language = self.get_user_language(to_user)
translation.activate(user_language)
- logger.debug('Set language code to %s for user: %s' % (user_language, to_user))
- self.stdout.write('[%s] Set language code to %s' % (
- str(datetime.datetime.now()), user_language))
+ logger.debug('Set language code to %s for user: %s' % (
+ user_language, to_user))
+ self.stdout.write('[%s] Set language code to %s for user: %s' % (
+ str(datetime.datetime.now()), user_language, to_user))
+ # format mail content and send
notices = []
- for notice in unseen_notices:
- logger.info('Processing unseen notice: [%s]' % (notice))
-
+ for notice in user_notices:
d = json.loads(notice.detail)
-
- repo_id = d.get('repo_id', None)
- group_id = d.get('group_id', None)
+ repo_id = d.get('repo_id')
+ group_id = d.get('group_id')
try:
if repo_id and not seafile_api.get_repo(repo_id):
notice.delete()
continue
-
if group_id and not ccnet_api.get_group(group_id):
notice.delete()
continue
except Exception as e:
logger.error(e)
continue
-
if notice.to_user != to_user:
continue
@@ -297,7 +307,6 @@ class Command(BaseCommand):
if not notices:
continue
-
user_name = email2nickname(to_user)
contact_email = Profile.objects.get_contact_email_by_user(to_user)
to_user = contact_email # use contact email if any
@@ -312,7 +321,9 @@ class Command(BaseCommand):
send_html_email(_('New notice on %s') % get_site_name(),
'notifications/notice_email.html', c,
None, [to_user])
-
+ # set new last_emailed_time
+ UserOptions.objects.set_collaborate_last_emailed_time(
+ to_user, now)
logger.info('Successfully sent email to %s' % to_user)
self.stdout.write('[%s] Successfully sent email to %s' % (str(datetime.datetime.now()), to_user))
except Exception as e:
diff --git a/seahub/options/models.py b/seahub/options/models.py
index 2d65feeb82..320bfb96ad 100644
--- a/seahub/options/models.py
+++ b/seahub/options/models.py
@@ -36,6 +36,8 @@ KEY_DEFAULT_REPO = "default_repo"
KEY_WEBDAV_SECRET = "webdav_secret"
KEY_FILE_UPDATES_EMAIL_INTERVAL = "file_updates_email_interval"
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'
class CryptoOptionNotSetError(Exception):
@@ -306,6 +308,42 @@ class UserOptionsManager(models.Manager):
def unset_file_updates_last_emailed_time(self, username):
return self.unset_user_option(username, KEY_FILE_UPDATES_LAST_EMAILED_TIME)
+ def set_collaborate_email_interval(self, username, seconds):
+ return self.set_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL,
+ str(seconds))
+
+ def get_collaborate_email_interval(self, username):
+ val = self.get_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
+ if not val:
+ return None
+ try:
+ return int(val)
+ except ValueError:
+ logger.error('Failed to convert string %s to int' % val)
+ return None
+
+ def unset_collaborate_email_interval(self, username):
+ return self.unset_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
+
+ def set_collaborate_last_emailed_time(self, username, time_dt):
+ return self.set_user_option(
+ username, KEY_COLLABORATE_LAST_EMAILED_TIME,
+ time_dt.strftime("%Y-%m-%d %H:%M:%S"))
+
+ def get_collaborate_last_emailed_time(self, username):
+ val = self.get_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
+ if not val:
+ return None
+
+ try:
+ return datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
+ except Exception:
+ logger.error('Failed to convert string %s to datetime obj' % val)
+ return None
+
+ def unset_collaborate_last_emailed_time(self, username):
+ return self.unset_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
+
class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True)
diff --git a/seahub/profile/templates/profile/set_profile_react.html b/seahub/profile/templates/profile/set_profile_react.html
index 0f576553b6..5a054dfc79 100644
--- a/seahub/profile/templates/profile/set_profile_react.html
+++ b/seahub/profile/templates/profile/set_profile_react.html
@@ -49,7 +49,8 @@ window.app.pageOptions = {
})(),
{% if is_pro %}
- initialEmailNotificationInterval: {{ email_notification_interval }},
+ fileUpdatesEmailInterval: {{ file_updates_email_interval }},
+ collaborateEmailInterval: {{ collaborate_email_interval }},
{% endif %}
twoFactorAuthEnabled: {% if two_factor_auth_enabled %} true {% else %} false {% endif %},
diff --git a/seahub/profile/views.py b/seahub/profile/views.py
index df736d5e04..333b7206cf 100644
--- a/seahub/profile/views.py
+++ b/seahub/profile/views.py
@@ -84,8 +84,10 @@ def edit_profile(request):
else:
webdav_passwd = ''
- email_inverval = UserOptions.objects.get_file_updates_email_interval(username)
- email_inverval = email_inverval if email_inverval is not None else 0
+ 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
if work_weixin_oauth_check():
enable_wechat_work = True
@@ -123,7 +125,8 @@ def edit_profile(request):
'ENABLE_DELETE_ACCOUNT': ENABLE_DELETE_ACCOUNT,
'ENABLE_UPDATE_USER_INFO': ENABLE_UPDATE_USER_INFO,
'webdav_passwd': webdav_passwd,
- 'email_notification_interval': email_inverval,
+ 'file_updates_email_interval': file_updates_email_interval,
+ 'collaborate_email_interval': collaborate_email_interval,
'social_next_page': reverse('edit_profile'),
'enable_wechat_work': enable_wechat_work,
'social_connected': social_connected,
diff --git a/tests/api/views/test_account_info.py b/tests/api/views/test_account_info.py
index 67e7f8f70e..07954ca667 100644
--- a/tests/api/views/test_account_info.py
+++ b/tests/api/views/test_account_info.py
@@ -26,22 +26,23 @@ class AccountInfoTest(BaseTestCase):
def test_update(self, ):
self.login_as(self.user)
- resp = self._do_put('name=foo&email_notification_interval=3000')
+ resp = self._do_put('name=foo&file_updates_email_interval=3000&collaborate_email_interval=3000')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
- assert json_resp['email_notification_interval'] == 3000
+ assert json_resp['file_updates_email_interval'] == 3000
+ assert json_resp['collaborate_email_interval'] == 3000
assert json_resp['name'] == 'foo'
def test_update_email_nofification_interval(self, ):
self.login_as(self.user)
- resp = self._do_put('email_notification_interval=3000')
+ resp = self._do_put('file_updates_email_interval=3000')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
- assert json_resp['email_notification_interval'] == 3000
+ assert json_resp['file_updates_email_interval'] == 3000
- resp = self._do_put('email_notification_interval=0')
+ resp = self._do_put('file_updates_email_interval=0')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
- assert json_resp['email_notification_interval'] == 0
+ assert json_resp['file_updates_email_interval'] == 0
diff --git a/tests/seahub/notifications/management/commands/test_send_notices.py b/tests/seahub/notifications/management/commands/test_send_notices.py
index 1b56a6108f..5317394727 100644
--- a/tests/seahub/notifications/management/commands/test_send_notices.py
+++ b/tests/seahub/notifications/management/commands/test_send_notices.py
@@ -12,6 +12,7 @@ from seahub.profile.models import Profile
from seahub.test_utils import BaseTestCase
from seahub.notifications.management.commands.send_notices import Command
from seahub.share.utils import share_dir_to_user, share_dir_to_group
+from seahub.options.models import UserOptions
try:
from seahub.settings import LOCAL_PRO_DEV_ENV
@@ -21,6 +22,16 @@ except ImportError:
class CommandTest(BaseTestCase):
+ def setUp(self):
+ super(CommandTest, self).setUp()
+ UserOptions.objects.set_file_updates_email_interval(self.user.username, 3600)
+ UserOptions.objects.set_collaborate_email_interval(self.user.username, 3600)
+
+ def tearDown(self):
+ UserOptions.objects.unset_file_updates_last_emailed_time(self.user.username)
+ UserOptions.objects.unset_collaborate_last_emailed_time(self.user.username)
+ super(CommandTest, self).tearDown()
+
def test_can_send_repo_share_msg(self):
self.assertEqual(len(mail.outbox), 0)
UserNotification.objects.add_repo_share_msg(
@@ -95,13 +106,13 @@ class CommandTest(BaseTestCase):
self.assertEqual(len(mail.outbox), 0)
detail = file_comment_msg_to_json(self.repo.id, '/foo',
- self.user.username, 'test comment')
- UserNotification.objects.add_file_comment_msg('a@a.com', detail)
+ 'bar@bar.com', 'test comment')
+ UserNotification.objects.add_file_comment_msg(self.user.username, detail)
call_command('send_notices')
self.assertEqual(len(mail.outbox), 1)
- assert mail.outbox[0].to[0] == 'a@a.com'
- assert 'new comment from user %s' % self.user.username in mail.outbox[0].body
+ assert mail.outbox[0].to[0] == self.user.username
+ assert 'new comment from user %s' % 'bar@bar.com' in mail.outbox[0].body
assert '/foo' in mail.outbox[0].body
def test_send_guest_invitation_notice(self):