1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-19 18:29:23 +00:00

[api & notification] Add send_file_updates_email command and update account_info API

This commit is contained in:
zhengxie
2018-11-05 11:56:33 +08:00
parent 313bb3c977
commit e9778fccd4
14 changed files with 741 additions and 35 deletions

View File

@@ -289,7 +289,7 @@ class AccountInfo(APIView):
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, format=None):
def _get_account_info(self, request):
info = {}
email = request.user.username
p = Profile.objects.get_profile_by_user(email)
@@ -321,7 +321,54 @@ class AccountInfo(APIView):
info['institution'] = p.institution if p and p.institution else ""
info['is_staff'] = request.user.is_staff
return Response(info)
interval = UserOptions.objects.get_file_updates_email_interval(email)
info['email_notification_interval'] = 0 if interval is None else interval
return info
def get(self, request, format=None):
return Response(self._get_account_info(request))
def put(self, request, format=None):
"""Update account info.
"""
username = request.user.username
name = request.data.get("name", None)
if name is not None:
if len(name) > 64:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u'Name is too long (maximum is 64 characters)'))
if "/" in name:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u"Name should not include '/'."))
email_interval = request.data.get("email_notification_interval", None)
if email_interval is not None:
try:
interval = int(email_interval)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'email_interval invalid')
# update user info
if name is not None:
profile = Profile.objects.get_profile_by_user(username)
if profile is None:
profile = Profile(user=username)
profile.nickname = name
profile.save()
if interval is not None:
if interval <= 0:
UserOptions.objects.unset_file_updates_email_interval(username)
else:
UserOptions.objects.set_file_updates_email_interval(
username, email_interval)
return Response(self._get_account_info(request))
class RegDevice(APIView):
"""Reg device for iOS push notification.

View File

@@ -116,9 +116,9 @@ else:
repo_owner = kwargs['repo_owner']
if org_id > 0:
related_users = [r.user for r in seafile_api.org_get_shared_users_by_repo(org_id, repo_id)]
related_users = seafile_api.org_get_shared_users_by_repo(org_id, repo_id)
else:
related_users = [r.user for r in seafile_api.get_shared_users_by_repo(repo_id)]
related_users = seafile_api.get_shared_users_by_repo(repo_id)
org_id = -1
related_users.append(repo_owner)

View File

@@ -0,0 +1,272 @@
# Copyright (c) 2012-2016 Seafile Ltd.
# encoding: utf-8
from datetime import datetime
import logging
import os
import re
from django.core.management.base import BaseCommand
from django.core.urlresolvers import reverse
from django.utils.html import escape as e
from django.utils import translation
from django.utils.translation import ugettext as _
from seahub.avatar.templatetags.avatar_tags import avatar
from seahub.avatar.util import get_default_avatar_url
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.constants import HASH_URLS
from seahub.options.models import (
UserOptions, KEY_FILE_UPDATES_EMAIL_INTERVAL,
KEY_FILE_UPDATES_LAST_EMAILED_TIME
)
from seahub.profile.models import Profile
from seahub.utils import (get_site_name, seafevents_api,
send_html_email, get_site_scheme_and_netloc)
from seahub.utils.timeutils import utc_to_local
# Get an instance of a logger
logger = logging.getLogger(__name__)
########## Utility Functions ##########
def td(con):
return con
# return '<td>%s</td>' % con
def a_tag(con, href='#'):
return '<a href="%s">%s</a>' % (href, e(con))
def repo_url(repo_id):
p = HASH_URLS["VIEW_COMMON_LIB_DIR"] % {'repo_id': repo_id, 'path': ''}
return get_site_scheme_and_netloc() + p
def file_url(repo_id, file_path):
p = reverse('view_lib_file', args=[repo_id, file_path])
return get_site_scheme_and_netloc() + p
def dir_url(repo_id, dir_path):
p = HASH_URLS["VIEW_COMMON_LIB_DIR"] % {
'repo_id': repo_id, 'path': dir_path.strip('/')
}
return get_site_scheme_and_netloc() + p
def user_info_url(username):
p = reverse('user_profile', args=[username])
return get_site_scheme_and_netloc() + p
#######################################
class Command(BaseCommand):
help = 'Send Email notifications to user if he/she has '
'file updates notices every period of seconds .'
label = "notifications_send_file_updates"
def handle(self, *args, **options):
logger.debug('Start sending file updates emails...')
self.do_action()
logger.debug('Finish sending file updates emails.\n')
def get_avatar(self, username, default_size=32):
img_tag = avatar(username, default_size)
pattern = r'src="(.*)"'
repl = r'src="%s\1"' % get_site_scheme_and_netloc()
return re.sub(pattern, repl, img_tag)
def get_avatar_src(self, username, default_size=32):
avatar_img = self.get_avatar(username, default_size)
m = re.search('<img src="(.*?)".*', avatar_img)
if m:
return m.group(1)
else:
return ''
def get_default_avatar(self, default_size=32):
# user default avatar
img_tag = """<img src="%s" width="%s" height="%s" class="avatar" alt="" />""" % \
(get_default_avatar_url(), default_size, default_size)
pattern = r'src="(.*)"'
repl = r'src="%s\1"' % get_site_scheme_and_netloc()
return re.sub(pattern, repl, img_tag)
def get_default_avatar_src(self, default_size=32):
avatar_img = self.get_default_avatar(default_size)
m = re.search('<img src="(.*?)".*', avatar_img)
if m:
return m.group(1)
else:
return ''
def get_user_language(self, username):
return Profile.objects.get_user_language(username)
def format_file_operation(self, ev):
lib_link = a_tag(ev.repo_name, repo_url(ev.repo_id))
if ev.obj_type == 'repo':
if ev.op_type == 'create':
op = _('Created library')
details = td(lib_link)
elif ev.op_type == 'rename':
op = _('Renamed library')
details = td('%s => %s' % (e(ev.old_repo_name), lib_link))
elif ev.op_type == 'delete':
op = _('Deleted library')
details = td(e(ev.repo_name))
elif ev.op_type == 'recover':
op = _('Restored library')
details = td(lib_link)
else: # ev.op_type == 'clean-up-trash':
if ev.days == 0:
op = _('Removed all items from trash.')
else:
op = _('Removed items older than %s days from trash.' %
ev.days)
details = td(lib_link)
elif ev.obj_type == 'file':
file_name = os.path.basename(ev.path)
file_link = a_tag(file_name, file_url(ev.repo_id, ev.path))
if ev.op_type == 'create':
op = _('Created file')
details = td("%s<br />%s" % (file_link, lib_link))
elif ev.op_type == 'delete':
op = _('Deleted file')
details = td("%s<br />%s" % (e(file_name), lib_link))
elif ev.op_type == 'recover':
op = _('Restored file')
details = td("%s<br />%s" % (file_link, lib_link))
elif ev.op_type == 'rename':
op = _('Renamed file')
old_name = os.path.basename(ev.old_path)
details = td("%s => %s<br />%s" % (
e(old_name), file_link, lib_link)
)
elif ev.op_type == 'move':
op = _('Moved file')
file_path_link = a_tag(ev.path, file_url(ev.repo_id, ev.path))
details = td('%s => %s<br />%s' % (
e(ev.old_path), file_path_link, lib_link)
)
else: # ev.op_type == 'edit':
op = _('Updated file')
details = td("%s<br />%s" % (file_link, lib_link))
else: # dir
dir_name = os.path.basename(ev.path)
dir_link = a_tag(dir_name, dir_url(ev.repo_id, ev.path))
if ev.op_type == 'create':
op = _('Created folder')
details = td('%s<br />%s' % (dir_link, lib_link))
elif ev.op_type == 'delete':
op = _('Deleted folder')
details = td('%s<br />%s' % (e(dir_name), lib_link))
elif ev.op_type == 'recover':
op = _('Restored folder')
details = td('%s<br />%s' % (dir_link, lib_link))
elif ev.op_type == 'rename':
op = _('Renamed folder')
old_name = os.path.basename(ev.old_path)
details = td('%s => %s<br />%s' % (e(old_name), dir_link,
lib_link))
else: # ev.op_type == 'move':
op = _('Moved folder')
details = td('%s => %s<br />%s' % (e(ev.old_path), dir_link,
lib_link))
return (op, details)
def do_action(self):
today = datetime.utcnow().replace(hour=0).replace(minute=0).replace(
second=0).replace(microsecond=0)
emails = []
user_file_updates_email_intervals = []
for ele in UserOptions.objects.filter(
option_key=KEY_FILE_UPDATES_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)
continue
user_last_emailed_time_dict = {}
for ele in UserOptions.objects.filter(
option_key=KEY_FILE_UPDATES_LAST_EMAILED_TIME).filter(
email__in=emails):
try:
user_last_emailed_time_dict[ele.email] = datetime.strptime(
ele.option_val, "%Y-%m-%d %H:%M:%S")
except Exception as e:
logger.error(e)
continue
for (username, interval_val) in user_file_updates_email_intervals:
# save current language
cur_language = translation.get_language()
# get and active user language
user_language = self.get_user_language(username)
translation.activate(user_language)
logger.debug('Set language code to %s for user: %s' % (
user_language, username))
self.stdout.write('[%s] Set language code to %s' % (
str(datetime.now()), user_language))
# get last_emailed_time if any, defaults to today
last_emailed_time = user_last_emailed_time_dict.get(username, today)
now = datetime.utcnow().replace(microsecond=0)
if (now - last_emailed_time).seconds < interval_val:
continue
# get file updates(from: last_emailed_time, to: now) for repos
# user can access
res = seafevents_api.get_user_activities_by_timestamp(
username, last_emailed_time, now)
if not res:
continue
# remove my activities
res = filter(lambda x: x.op_user != username, res)
if not res:
continue
# format mail content & send file updates email to user
try:
for ele in res:
ele.user_avatar = self.get_avatar_src(ele.op_user)
ele.local_timestamp = utc_to_local(ele.timestamp)
ele.op_user_link = a_tag(email2nickname(ele.op_user),
user_info_url(ele.op_user))
ele.operation, ele.op_details = self.format_file_operation(ele)
except Exception as e:
logger.error('Failed to format mail content for user: %s' %
username)
logger.error(e, exc_info=True)
continue
nickname = email2nickname(username)
contact_email = Profile.objects.get_contact_email_by_user(username)
c = {
'name': nickname,
'updates_count': len(res),
'updates': res,
}
try:
send_html_email(_('New file updates on %s') % get_site_name(),
'notifications/file_updates_email.html', c,
None, [contact_email])
# set new last_emailed_time
UserOptions.objects.set_file_updates_last_emailed_time(
username, now)
except Exception as e:
logger.error('Failed to send email to %s, error detail: %s' %
(contact_email, e))
self.stderr.write('[%s] Failed to send email to %s, error '
'detail: %s' % (str(now), contact_email, e))
finally:
# reset lang
translation.activate(cur_language)

View File

@@ -0,0 +1,49 @@
{% extends 'email_base.html' %}
{% load i18n seahub_tags %}
{% block email_con %}
<p style="font-size:14px; line-height: 1.5; color:#121214; margin:.2em 0 12px;">{% trans "Hi," %} {{ name }}</p>
<p style="font-size:14px; line-height: 1.5; color:#434144; margin:.2em 0;">
{% blocktrans count num=updates_count %}
You've got 1 new file updates on {{ site_name }}:
{% plural %}
You've got {{num}} file updates on {{ site_name }}:
{% endblocktrans %}
</p>
<table style="width:100%; margin:12px 0 20px; table-layout:fixed; border-spacing: 0; border-collapse: collapse;">
<tr>
<th width="9%" style="padding: 5px 3px; border-bottom: 1px solid #eee;"></th>
<th width="19%" style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size:13px; text-align: left; font-weight: normal; color: #9c9c9c;">{% trans "User" %}</th>
<th width="20%" style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size:13px; text-align: left; font-weight: normal; color: #9c9c9c;">{% trans "Operation" %}</th>
<th width="30%" style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size:13px; text-align: left; font-weight: normal; color: #9c9c9c;">{% trans "File" %}</th>
<th width="22%" style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size:13px; text-align: left; font-weight: normal; color: #9c9c9c;">{% trans "Time" %}</th>
</tr>
{% autoescape off %}
{% for ele in updates %}
<tr>
<td style="padding:8px 3px 5px; border-bottom: 1px solid #eee; text-align:center; vertical-align:top;"><img src="{{ele.user_avatar}}" width="32" height="32" alt="" style="border-radius:1000px;" /></td>
<td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">
{{ele.op_user_link}}
</td>
<td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">
{{ele.operation}}
</td>
<td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">
{{ele.op_details}}
</td>
<td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">{{ ele.local_timestamp|date:"Y-m-d G:i:s"}}</td>
</tr>
{% endfor %}
{% endautoescape %}
</table>
{% endblock %}

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-11-07 08:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('options', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='useroptions',
name='option_key',
field=models.CharField(db_index=True, max_length=50),
),
]

View File

@@ -1,11 +1,16 @@
# Copyright (c) 2012-2016 Seafile Ltd.
# -*- coding: utf-8 -*-
from datetime import datetime
import logging
from django.db import models
from seahub.base.fields import LowerCaseCharField
from seahub.utils import is_pro_version
# Get an instance of a logger
logger = logging.getLogger(__name__)
KEY_SERVER_CRYPTO = "server_crypto"
VAL_SERVER_CRYPTO_ENABLED = "1"
VAL_SERVER_CRYPTO_DISABLED = "0"
@@ -29,6 +34,9 @@ VAL_USER_LOGGED_IN = "1"
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"
class CryptoOptionNotSetError(Exception):
pass
@@ -53,6 +61,20 @@ class UserOptionsManager(models.Manager):
return user_option
def get_user_option(self, username, k):
user_options = super(UserOptionsManager, self).filter(
email=username, option_key=k)
if len(user_options) == 0:
return None
elif len(user_options) == 1:
return user_options[0].option_val
else:
for o in user_options[1: len(user_options)]:
o.delete()
return user_options[0].option_val
def unset_user_option(self, username, k):
"""Remove user's option.
"""
@@ -177,18 +199,7 @@ class UserOptionsManager(models.Manager):
- `self`:
- `username`:
"""
user_options = super(UserOptionsManager, self).filter(
email=username, option_key=KEY_DEFAULT_REPO)
if len(user_options) == 0:
return None
elif len(user_options) == 1:
return user_options[0].option_val
else:
for o in user_options[1: len(user_options)]:
o.delete()
return user_options[0].option_val
return self.get_user_option(username, KEY_DEFAULT_REPO)
def passwd_change_required(self, username):
"""Check whether user need to change password.
@@ -259,9 +270,46 @@ class UserOptionsManager(models.Manager):
decoded = None
return decoded
def set_file_updates_email_interval(self, username, seconds):
return self.set_user_option(username, KEY_FILE_UPDATES_EMAIL_INTERVAL,
str(seconds))
def get_file_updates_email_interval(self, username):
val = self.get_user_option(username, KEY_FILE_UPDATES_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_file_updates_email_interval(self, username):
return self.unset_user_option(username, KEY_FILE_UPDATES_EMAIL_INTERVAL)
def set_file_updates_last_emailed_time(self, username, time_dt):
return self.set_user_option(
username, KEY_FILE_UPDATES_LAST_EMAILED_TIME,
time_dt.strftime("%Y-%m-%d %H:%M:%S"))
def get_file_updates_last_emailed_time(self, username):
val = self.get_user_option(username, KEY_FILE_UPDATES_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_file_updates_last_emailed_time(self, username):
return self.unset_user_option(username, KEY_FILE_UPDATES_LAST_EMAILED_TIME)
class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True)
option_key = models.CharField(max_length=50)
option_key = models.CharField(max_length=50, db_index=True)
option_val = models.CharField(max_length=50)
objects = UserOptionsManager()

View File

@@ -81,6 +81,9 @@ 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
resp_dict = {
'form': form,
'server_crypto': server_crypto,
@@ -94,6 +97,7 @@ def edit_profile(request):
'ENABLE_CHANGE_PASSWORD': settings.ENABLE_CHANGE_PASSWORD,
'ENABLE_WEBDAV_SECRET': settings.ENABLE_WEBDAV_SECRET,
'webdav_passwd': webdav_passwd,
'email_notification_interval': email_inverval,
}
if has_two_factor_auth():

View File

@@ -3,7 +3,7 @@ import unittest
from tests.common.utils import apiurl, urljoin, randstring
from tests.api.apitestbase import ApiTestBase
from tests.api.urls import ACCOUNTS_URL, ACCOUNT_INFO_URL, PING_URL, \
from tests.api.urls import ACCOUNTS_URL, PING_URL, \
AUTH_PING_URL
test_account_username = 'test_%s@test.com' % randstring(10)
@@ -12,17 +12,6 @@ test_account_password2 = randstring(20)
test_account_url = urljoin(ACCOUNTS_URL, test_account_username)
class AccountsApiTest(ApiTestBase):
def test_check_account_info(self):
info = self.get(ACCOUNT_INFO_URL).json()
self.assertIsNotNone(info)
self.assertEqual(info['email'], self.username)
self.assertIsNotNone(info['total'])
self.assertIsNotNone(info['usage'])
self.assertIsNotNone(info['login_id'])
self.assertIsNotNone(info['department'])
self.assertIsNotNone(info['contact_email'])
self.assertIsNotNone(info['institution'])
def test_list_accounts(self):
# Normal user can not list accounts
self.get(ACCOUNTS_URL, expected=403)

View File

@@ -5,7 +5,6 @@ TOKEN_URL = apiurl('/api2/auth-token/')
AUTH_PING_URL = apiurl('/api2/auth/ping/')
ACCOUNTS_URL = apiurl('/api2/accounts/')
ACCOUNT_INFO_URL = apiurl('/api2/account/info/')
AVATAR_BASE_URL = apiurl(u'/api2/avatars/')
REPOS_URL = apiurl('/api2/repos/')

View File

@@ -0,0 +1,47 @@
import json
from django.core.urlresolvers import reverse
import seaserv
from seaserv import seafile_api
from seahub.base.accounts import User
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.profile.models import Profile
from seahub.test_utils import BaseTestCase
from tests.common.utils import randstring
class AccountInfoTest(BaseTestCase):
def test_get(self, ):
self.login_as(self.user)
resp = self.client.get('/api2/account/info/')
self.assertEqual(200, resp.status_code)
def _do_put(self, val):
return self.client.put('/api2/account/info/',
val, 'application/x-www-form-urlencoded',
)
def test_update(self, ):
self.login_as(self.user)
resp = self._do_put('name=foo&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['name'] == 'foo'
def test_update_email_nofification_interval(self, ):
self.login_as(self.user)
resp = self._do_put('email_interval=3000')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['email_notification_interval'] == 3000
resp = self._do_put('email_interval=0')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['email_notification_interval'] is None

View File

@@ -0,0 +1,194 @@
# encoding: utf-8
import time
import datetime
from mock import patch
from django.core import mail
from django.core.management import call_command
from django.utils import timezone
from django.test import override_settings
from seahub.test_utils import BaseTestCase
from seahub.options.models import UserOptions
class Record(object):
def __init__(self, **entries):
self.__dict__.update(entries)
class CommandTest(BaseTestCase):
def _repo_evs(self, ):
l = [
{'username': self.user.username, 'commit_id': None, 'obj_type': u'repo', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', 'timestamp': datetime.datetime(2018, 11, 5, 6, 46, 2), 'op_type': u'create', 'path': u'/', 'id': 254L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\'},
{'username': self.user.username, 'commit_id': None, 'obj_type': u'repo', 'repo_id': u'f8dc0bc8-eae0-4063-9beb-790071168794', 'timestamp': datetime.datetime(2018, 11, 6, 9, 52, 6), 'op_type': u'delete', 'path': u'/', 'id': 289L, 'op_user': u'foo@foo.com', u'repo_name': u'123'},
{'username': self.user.username, 'commit_id': u'93fb5d8f07e03e5c947599cd7c948965426aafec', 'obj_type': u'repo', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', 'timestamp': datetime.datetime(2018, 11, 7, 2, 35, 34), u'old_repo_name': u'tests\\', 'op_type': u'rename', 'path': u'/', 'id': 306L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\123'},
{'username': self.user.username, 'commit_id': None, 'obj_type': u'repo', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', 'timestamp': datetime.datetime(2018, 11, 7, 3, 13, 2), u'days': 0, 'op_type': u'clean-up-trash', 'path': u'/', 'id': 308L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\123'},
{'username': self.user.username, 'commit_id': None, 'obj_type': u'repo', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', 'timestamp': datetime.datetime(2018, 11, 7, 3, 12, 43), u'days': 3, 'op_type': u'clean-up-trash', 'path': u'/', 'id': 307L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\123'},
]
return [Record(**x) for x in l]
def _dir_evs(self, ):
l = [
{'username': self.user.username, 'commit_id': u'8ff6473e9ef5229a632e1481a1b28d52673220ec', 'obj_type': u'dir', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'0000000000000000000000000000000000000000', 'timestamp': datetime.datetime(2018, 11, 6, 9, 10, 45), 'op_type': u'create', 'path': u'/xx', 'id': 260L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\'},
{'username': self.user.username, 'commit_id': u'bb3ef321899d2f75ecf56098cb89e6b13c48cff9', 'obj_type': u'dir', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'0000000000000000000000000000000000000000', 'timestamp': datetime.datetime(2018, 11, 6, 9, 27, 3), 'op_type': u'delete', 'path': u'/aa', 'id': 268L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\'},
{'username': self.user.username, 'commit_id': u'016435e95ace96902ea1bfa1e7688f45804d5aa4', 'obj_type': u'dir', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'95421aa563cf474dce02b7fadc532c17c11cd97a', 'timestamp': datetime.datetime(2018, 11, 6, 9, 38, 32), u'old_path': u'/11', 'op_type': u'move', 'path': u'/new/11', u'repo_name': u'tests\\', 'id': 283L, 'op_user': u'foo@foo.com', u'size': -1},
{'username': self.user.username, 'commit_id': u'712504f1cfd94b0813763a106eb4140a5dba156a', 'obj_type': u'dir', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'4d83a9b62084fef33ec99787425f91df356ae307', 'timestamp': datetime.datetime(2018, 11, 6, 9, 39, 10), u'old_path': u'/new', 'op_type': u'rename', 'path': u'/new2', u'repo_name': u'tests\\', 'id': 284L, 'op_user': u'foo@foo.com', u'size': -1},
{'username': self.user.username, 'commit_id': u'2f7021e0804187b8b09ec82142e0f8b53771cc69', 'obj_type': u'dir', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'0000000000000000000000000000000000000000', 'timestamp': datetime.datetime(2018, 11, 6, 9, 27, 6), 'op_type': u'recover', 'path': u'/aa', 'id': 269L, 'op_user': u'foo@foo.com', u'repo_name': u'tests\\'},
]
return [Record(**x) for x in l]
def _file_evs(self, ):
l = [
{'username': self.user.username, 'commit_id': u'658d8487b7e8916ee25703fbdf978b98ab76e3d4', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'0000000000000000000000000000000000000000', 'timestamp': datetime.datetime(2018, 11, 6, 9, 38, 23), 'op_type': u'create', 'path': u'/11/new/aa/new/yy/xx/bb/1.txt', u'repo_name': u'tests\\', 'id': 282L, 'op_user': u'foo@foo.com', u'size': 0},
{'username': self.user.username, 'commit_id': u'04df2a831ba485bb6f216f62c1b47883c3e3433c', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'd16369af225687671348897a0ad918261866af5d', 'timestamp': datetime.datetime(2018, 11, 6, 9, 0, 14), 'op_type': u'delete', 'path': u'/aa1.txt', u'repo_name': u'tests\\', 'id': 257L, 'op_user': u'foo@foo.com', u'size': 2},
{'username': self.user.username, 'commit_id': u'612f605faa112e4e8928dc08e91c669cea92ef59', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'd16369af225687671348897a0ad918261866af5d', 'timestamp': datetime.datetime(2018, 11, 6, 9, 0, 22), 'op_type': u'recover', 'path': u'/aa1.txt', u'repo_name': u'tests\\', 'id': 258L, 'op_user': u'foo@foo.com', u'size': 2},
{'username': self.user.username, 'commit_id': u'106e6e12138bf0e12fbd558da73ff24502807f3e', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'28054f8015aada8b5232943d072526541f5227f9', 'timestamp': datetime.datetime(2018, 11, 6, 9, 0, 30), 'op_type': u'edit', 'path': u'/aa1.txt', u'repo_name': u'tests\\', 'id': 259L, 'op_user': u'foo@foo.com', u'size': 4},
{'username': self.user.username, 'commit_id': u'1c9a12a2d8cca79f261eb7c65c118a3ea4f7b850', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'28054f8015aada8b5232943d072526541f5227f9', 'timestamp': datetime.datetime(2018, 11, 6, 9, 36, 45), u'old_path': u'/11/new/aa/new/yy/xx/aa4.txt', 'op_type': u'move', 'path': u'/aa4.txt', u'repo_name': u'tests\\', 'id': 279L, 'op_user': u'foo@foo.com', u'size': 4},
{'username': self.user.username, 'commit_id': u'19cab0f3c53ee00cffe6eaa65f256ccc35a77a72', 'obj_type': u'file', 'repo_id': u'7d6b3f36-3ce1-45f1-8c82-b9532e3162c7', u'obj_id': u'28054f8015aada8b5232943d072526541f5227f9', 'timestamp': datetime.datetime(2018, 11, 6, 9, 36, 59), u'old_path': u'/aa4.txt', 'op_type': u'rename', 'path': u'/aa5.txt', u'repo_name': u'tests\\', 'id': 280L, 'op_user': u'foo@foo.com', u'size': 4},
]
return [Record(**x) for x in l]
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_dir_evs(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._dir_evs()
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 30)
self.assertEqual(len(mail.outbox), 0)
call_command('send_file_updates')
mock_seafevents_api.get_user_activities_by_timestamp.assert_called_once()
self.assertEqual(len(mail.outbox), 1)
assert mail.outbox[0].to[0] == self.user.username
for op in ['Created', 'Deleted', 'Moved', 'Restored', 'Renamed', ]:
assert op in mail.outbox[0].body
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_file_evs(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._file_evs()
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 30)
self.assertEqual(len(mail.outbox), 0)
call_command('send_file_updates')
mock_seafevents_api.get_user_activities_by_timestamp.assert_called_once()
self.assertEqual(len(mail.outbox), 1)
assert mail.outbox[0].to[0] == self.user.username
for op in ['Created', 'Deleted', 'Restored', 'Updated', 'Moved',
'Renamed', ]:
assert op in mail.outbox[0].body
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_repo_evs(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._repo_evs()
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 30)
self.assertEqual(len(mail.outbox), 0)
call_command('send_file_updates')
mock_seafevents_api.get_user_activities_by_timestamp.assert_called_once()
self.assertEqual(len(mail.outbox), 1)
assert mail.outbox[0].to[0] == self.user.username
for op in ['Created', 'Deleted', 'Renamed', 'Removed']:
assert op in mail.outbox[0].body
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_seafevents_api(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._repo_evs()
username = self.user.username
UserOptions.objects.set_file_updates_email_interval(username, 30)
assert UserOptions.objects.get_file_updates_last_emailed_time(username) is None
today = datetime.datetime.utcnow().replace(hour=0).replace(
minute=0).replace(second=0).replace(microsecond=0)
before_dt = datetime.datetime.utcnow().replace(microsecond=0)
call_command('send_file_updates')
after_dt = datetime.datetime.utcnow().replace(microsecond=0)
mock_seafevents_api.get_user_activities_by_timestamp.assert_called_once()
args = mock_seafevents_api.get_user_activities_by_timestamp.call_args[0]
assert args[0] == username
assert args[1] == today
last_emailed_dt = UserOptions.objects.get_file_updates_last_emailed_time(username)
assert before_dt <= last_emailed_dt
assert last_emailed_dt <= after_dt
assert last_emailed_dt == args[2]
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_email_interval(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._repo_evs()
username = self.user.username
assert UserOptions.objects.get_file_updates_last_emailed_time(username) is None
# assume this command will be finished in 5 seconds
UserOptions.objects.set_file_updates_email_interval(username, 5)
assert mock_seafevents_api.get_user_activities_by_timestamp.called is False
call_command('send_file_updates')
assert mock_seafevents_api.get_user_activities_by_timestamp.called is True
# still within 5 seconds ...
mock_seafevents_api.get_user_activities_by_timestamp.reset_mock()
assert mock_seafevents_api.get_user_activities_by_timestamp.called is False
call_command('send_file_updates')
assert mock_seafevents_api.get_user_activities_by_timestamp.called is False
time.sleep(5) # 5 seconds passed
mock_seafevents_api.get_user_activities_by_timestamp.reset_mock()
assert mock_seafevents_api.get_user_activities_by_timestamp.called is False
call_command('send_file_updates')
assert mock_seafevents_api.get_user_activities_by_timestamp.called is True
@override_settings(TIME_ZONE='Asia/Shanghai')
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_timezone_in_email_body(self, mock_seafevents_api):
assert timezone.get_default_timezone_name() == 'Asia/Shanghai'
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._repo_evs()
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 30)
self.assertEqual(len(mail.outbox), 0)
call_command('send_file_updates')
self.assertEqual(len(mail.outbox), 1)
assert '2018-11-05 14:46:02' in mail.outbox[0].body
@patch('seahub.notifications.management.commands.send_file_updates.seafevents_api')
def test_invalid_option_vals(self, mock_seafevents_api):
mock_seafevents_api.get_user_activities_by_timestamp.return_value = self._repo_evs()
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 'a')
try:
call_command('send_file_updates')
assert True
except Exception:
assert False

View File

@@ -1,9 +1,13 @@
from django.utils import timezone
from seahub.test_utils import BaseTestCase
from seahub.options.models import (UserOptions, KEY_USER_GUIDE,
VAL_USER_GUIDE_ON, VAL_USER_GUIDE_OFF,
KEY_DEFAULT_REPO,
KEY_FORCE_2FA, VAL_FORCE_2FA,
KEY_WEBDAV_SECRET)
from seahub.options.models import (
UserOptions, KEY_USER_GUIDE,
VAL_USER_GUIDE_ON, VAL_USER_GUIDE_OFF,
KEY_DEFAULT_REPO, KEY_FORCE_2FA, VAL_FORCE_2FA,
KEY_FILE_UPDATES_EMAIL_INTERVAL, KEY_FILE_UPDATES_LAST_EMAILED_TIME,
KEY_WEBDAV_SECRET)
class UserOptionsManagerTest(BaseTestCase):
def test_is_user_guide_enabled(self):
@@ -85,3 +89,36 @@ class UserOptionsManagerTest(BaseTestCase):
assert len(UserOptions.objects.filter(email=self.user.email,
option_key=KEY_WEBDAV_SECRET)) == 0
def test_file_udpates_email_interval(self, ):
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_EMAIL_INTERVAL)) == 0
UserOptions.objects.set_file_updates_email_interval(
self.user.email, 300)
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_EMAIL_INTERVAL)) == 1
interv = UserOptions.objects.get_file_updates_email_interval(self.user.email)
assert interv == 300
UserOptions.objects.unset_file_updates_email_interval(self.user.email)
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_EMAIL_INTERVAL)) == 0
def test_file_updates_last_emailed_time(self, ):
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_LAST_EMAILED_TIME)) == 0
t = timezone.now().replace(microsecond=0)
UserOptions.objects.set_file_updates_last_emailed_time(self.user.email, t)
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_LAST_EMAILED_TIME)) == 1
val = UserOptions.objects.get_file_updates_last_emailed_time(self.user.email)
assert t == val
UserOptions.objects.unset_file_updates_last_emailed_time(self.user.email)
assert len(UserOptions.objects.filter(
email=self.user.email, option_key=KEY_FILE_UPDATES_LAST_EMAILED_TIME)) == 0