-
+ |
{dirent.encoded_thumbnail_src ?
-  :
+  :

}
{dirent.is_locked &&  }
|
-
+ |
{this.state.isRenameing ?
:
- {dirent.name}
+ {dirent.name}
}
{dirent.size && {dirent.size}}
diff --git a/frontend/src/components/search/search-result-item.js b/frontend/src/components/search/search-result-item.js
index 431a85b17a..a78a91eb3a 100644
--- a/frontend/src/components/search/search-result-item.js
+++ b/frontend/src/components/search/search-result-item.js
@@ -19,6 +19,11 @@ class SearchResultItem extends React.Component {
let className = item.link_content ? 'item-img' : 'lib-item-img';
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
+
+ if (item.thumbnail_url !== '') {
+ fileIconUrl = item.thumbnail_url;
+ }
+
return (
diff --git a/frontend/src/components/search/search.js b/frontend/src/components/search/search.js
index 4d450a42ae..ea0ed6c7e6 100644
--- a/frontend/src/components/search/search.js
+++ b/frontend/src/components/search/search.js
@@ -178,6 +178,7 @@ class Search extends Component {
items[i]['is_dir'] = data[i].is_dir;
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
items[i]['content'] = data[i].content_highlight;
+ items[i]['thumbnail_url'] = data[i].thumbnail_url;
}
return items;
}
diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
index ef58c04371..a3f8f02cbd 100644
--- a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
+++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
-import { Link } from '@reach/router';
+import { Link, navigate } from '@reach/router';
import { Utils } from '../../utils/utils';
import { gettext, siteRoot, isPro, username, folderPermEnabled, isSystemStaff, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
import ModalPortal from '../../components/modal-portal';
@@ -479,14 +479,21 @@ class SharedRepoListItem extends React.Component {
);
}
+ visitRepo = () => {
+ if (!this.state.isRenaming) {
+ navigate(this.repoURL);
+ }
+ }
+
renderMobileUI = () => {
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
let { repo } = this.props;
+ this.repoURL = libPath;
return (
-  |
-
+ |  |
+
{this.state.isRenaming ?
:
{repo.repo_name}
diff --git a/frontend/src/models/sysadmin-admin-user.js b/frontend/src/models/sysadmin-admin-user.js
index 5724d0463b..a7a9442d32 100644
--- a/frontend/src/models/sysadmin-admin-user.js
+++ b/frontend/src/models/sysadmin-admin-user.js
@@ -6,6 +6,7 @@ class SysAdminAdminUser {
this.contact_email = object.contact_email;
this.login_id = object.login_id;
this.last_login = object.last_login;
+ this.last_access_time = object.last_access_time;
this.create_time = object.create_time;
this.is_active = object.is_active;
this.is_staff = object.is_staff;
diff --git a/frontend/src/models/sysadmin-user.js b/frontend/src/models/sysadmin-user.js
index d09f189af4..b59532bc8d 100644
--- a/frontend/src/models/sysadmin-user.js
+++ b/frontend/src/models/sysadmin-user.js
@@ -5,6 +5,7 @@ class SysAdminUser {
this.contact_email = object.contact_email;
this.login_id = object.login_id;
this.last_login = object.last_login;
+ this.last_access_time = object.last_access_time;
this.create_time = object.create_time;
this.is_active = object.is_active;
this.is_staff = object.is_staff;
diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js
index c3f2ff8719..9b2b19355e 100644
--- a/frontend/src/pages/my-libs/mylib-repo-list-item.js
+++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js
@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import moment from 'moment';
-import { Link } from '@reach/router';
+import { Link, navigate } from '@reach/router';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot, storages } from '../../utils/constants';
@@ -121,6 +121,12 @@ class MylibRepoListItem extends React.Component {
}
}
+ visitRepo = () => {
+ if (!this.state.isRenaming && this.props.repo.repo_name) {
+ navigate(this.repoURL);
+ }
+ }
+
onRepoClick = () => {
this.props.onRepoClick(this.props.repo);
}
@@ -317,12 +323,12 @@ class MylibRepoListItem extends React.Component {
let repo = this.props.repo;
let iconUrl = Utils.getLibIconUrl(repo);
let iconTitle = Utils.getLibIconTitle(repo);
- let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
+ let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return (
|
-  |
-
+ |  |
+
{this.state.isRenaming && (
diff --git a/frontend/src/pages/shared-libs/shared-libs.js b/frontend/src/pages/shared-libs/shared-libs.js
index 782e2d6e56..63cc2ad1c6 100644
--- a/frontend/src/pages/shared-libs/shared-libs.js
+++ b/frontend/src/pages/shared-libs/shared-libs.js
@@ -3,7 +3,7 @@ import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import PropTypes from 'prop-types';
import moment from 'moment';
import cookie from 'react-cookies';
-import { Link } from '@reach/router';
+import { Link, navigate } from '@reach/router';
import { gettext, siteRoot, isPro } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@@ -194,6 +194,10 @@ class Item extends Component {
}
}
+ visitRepo = () => {
+ navigate(this.repoURL);
+ }
+
render() {
if (this.state.unshared) {
return null;
@@ -207,7 +211,7 @@ class Item extends Component {
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility;
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility;
- let shareRepoUrl =`${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
+ let shareRepoUrl = this.repoURL = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
const desktopItem = (
@@ -248,8 +252,8 @@ class Item extends Component {
const mobileItem = (
-  |
-
+ |  |
+
{data.repo_name}
{data.owner_name}
{data.size}
diff --git a/frontend/src/pages/starred/starred.js b/frontend/src/pages/starred/starred.js
index efab48e1b6..3cbe54bf24 100644
--- a/frontend/src/pages/starred/starred.js
+++ b/frontend/src/pages/starred/starred.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
-import { Link } from '@reach/router';
+import { Link, navigate } from '@reach/router';
import moment from 'moment';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@@ -170,6 +170,15 @@ class Item extends Component {
});
}
+ visitItem = () => {
+ const data = this.props.data;
+ if (data.is_dir) {
+ navigate(data.dirent_view_url);
+ } else {
+ window.open(data.dirent_view_url);
+ }
+ }
+
render() {
if (this.state.unstarred) {
@@ -206,14 +215,14 @@ class Item extends Component {
const mobileItem = (
|
-
+ |
{
data.thumbnail_url ?
:
}
|
-
+ |
{ data.is_dir ?
{data.obj_name} :
{data.obj_name}
diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js
index 796f238af4..9dbd85bfe8 100644
--- a/frontend/src/pages/sys-admin/users/users-content.js
+++ b/frontend/src/pages/sys-admin/users/users-content.js
@@ -83,7 +83,7 @@ class Content extends Component {
const colSpaceText = {spaceEl}{` / ${gettext('Quota')}`};
const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`;
- const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')}`;
+ const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')} / ${gettext('Last Access')}`;
if (isPro) {
columns.push(
{width: '20%', text: colNameText},
@@ -422,6 +422,8 @@ class Item extends Component {
{`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`}
{`${item.last_login ? moment(item.last_login).fromNow() : '--'}`}
+
+ {`${item.last_access_time ? moment(item.last_access_time).fromNow() : '--'}`}
|
{(item.email != username && isOpIconShown) &&
diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js
index bd10bb2a49..cc0c08999f 100644
--- a/frontend/src/utils/constants.js
+++ b/frontend/src/utils/constants.js
@@ -3,6 +3,7 @@ export const gettext = window.gettext;
export const siteRoot = window.app.config.siteRoot;
export const loginUrl = window.app.config.loginUrl;
+export const logoutUrl = window.app.config.logoutUrl;
export const avatarInfo = window.app.config.avatarInfo;
export const logoPath = window.app.config.logoPath;
export const mediaUrl = window.app.config.mediaUrl;
diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js
index 92acc676f5..01646cbf3a 100644
--- a/frontend/src/utils/utils.js
+++ b/frontend/src/utils/utils.js
@@ -484,7 +484,7 @@ export const Utils = {
return list;
},
- getFileOperationList: function(currentRepoInfo, dirent, isContextmenu) {
+ getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
let list = [];
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK,
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT } = TextTranslation;
@@ -516,7 +516,7 @@ export const Utils = {
if (isPro) {
if (dirent.is_locked) {
- if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice') {
+ if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice' || isRepoOwner || currentRepoInfo.is_admin) {
list.push(UNLOCK);
}
} else {
@@ -551,7 +551,7 @@ export const Utils = {
getDirentOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
return dirent.type == 'dir' ?
Utils.getFolderOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu) :
- Utils.getFileOperationList(currentRepoInfo, dirent, isContextmenu);
+ Utils.getFileOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu);
},
sharePerms: function(permission) {
diff --git a/requirements.txt b/requirements.txt
index c3acf608ba..a66fd304b1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,6 @@ Django==2.2.14
future
captcha
django-statici18n
-django-post_office==3.3.0
django-webpack_loader
gunicorn
mysqlclient
diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py
index 4440d32121..6e2844ab5b 100644
--- a/seahub/api2/endpoints/admin/users.py
+++ b/seahub/api2/endpoints/admin/users.py
@@ -19,6 +19,7 @@ from seaserv import seafile_api, ccnet_api
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, to_python_boolean
+from seahub.api2.models import TokenV2
import seahub.settings as settings
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
@@ -33,9 +34,12 @@ from seahub.profile.settings import CONTACT_CACHE_TIMEOUT, CONTACT_CACHE_PREFIX,
from seahub.utils import is_valid_username2, is_org_context, \
is_pro_version, normalize_cache_key, is_valid_email, \
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
- gen_shared_link, gen_shared_upload_link
+ gen_shared_link, gen_shared_upload_link, \
+ get_file_audit_events, get_file_update_events
+
from seahub.utils.file_size import get_file_size_unit
-from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
+from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
+ datetime_to_isoformat_timestr, utc_to_local
from seahub.utils.user_permissions import get_user_role
from seahub.utils.repo import normalize_repo_status_code
from seahub.constants import DEFAULT_ADMIN
@@ -57,6 +61,43 @@ logger = logging.getLogger(__name__)
json_content_type = 'application/json; charset=utf-8'
+def get_user_last_access_time(email, last_login_time):
+
+ device_last_access = ''
+ audit_last_access = ''
+ update_last_access = ''
+
+ devices = TokenV2.objects.filter(user=email).order_by('-last_accessed')
+ if devices:
+ device_last_access = devices[0].last_accessed
+
+ audit_events = get_file_audit_events(email, 0, None, 0, 1) or []
+ if audit_events:
+ audit_last_access = audit_events[0].timestamp
+
+ update_events = get_file_update_events(email, 0, None, 0, 1) or []
+ if update_events:
+ update_last_access = update_events[0].timestamp
+
+ last_access_time_list = []
+ if last_login_time:
+ last_access_time_list.append(last_login_time)
+
+ if device_last_access:
+ last_access_time_list.append(device_last_access)
+
+ if audit_last_access:
+ last_access_time_list.append(utc_to_local(audit_last_access))
+
+ if update_last_access:
+ last_access_time_list.append(utc_to_local(update_last_access))
+
+ if not last_access_time_list:
+ return ''
+ else:
+ return datetime_to_isoformat_timestr(sorted(last_access_time_list)[-1])
+
+
def get_user_upload_link_info(uls):
data = {}
@@ -204,6 +245,7 @@ def update_user_info(request, user, password, is_active, is_staff, role,
logger.error(e)
seafile_api.set_user_quota(email, -1)
+
def get_user_info(email):
user = User.objects.get(email=email)
@@ -297,8 +339,15 @@ class AdminAdminUsers(APIView):
user_info['quota_total'] = -1
user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
+
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
- user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
+ if last_login_obj:
+ user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
+ user_info['last_access_time'] = get_user_last_access_time(user.email,
+ last_login_obj.last_login)
+ else:
+ user_info['last_login'] = ''
+ user_info['last_access_time'] = get_user_last_access_time(user.email, '')
try:
admin_role = AdminRole.objects.get_admin_role(user.email)
@@ -312,6 +361,7 @@ class AdminAdminUsers(APIView):
}
return Response(result)
+
class AdminUsers(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
@@ -363,7 +413,13 @@ class AdminUsers(APIView):
info['quota_total'] = seafile_api.get_user_quota(user.email)
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
- info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
+ if last_login_obj:
+ info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
+ info['last_access_time'] = get_user_last_access_time(user.email,
+ last_login_obj.last_login)
+ else:
+ info['last_login'] = ''
+ info['last_access_time'] = get_user_last_access_time(user.email, '')
info['role'] = get_user_role(user)
@@ -423,8 +479,10 @@ class AdminUsers(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
- data = self.get_info_of_users_order_by_quota_usage(source, direction,
- page, per_page)
+ data = self.get_info_of_users_order_by_quota_usage(source,
+ direction,
+ page,
+ per_page)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -448,8 +506,10 @@ class AdminUsers(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
- data = self.get_info_of_users_order_by_quota_usage(source, direction,
- page, per_page)
+ data = self.get_info_of_users_order_by_quota_usage(source,
+ direction,
+ page,
+ per_page)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -489,10 +549,19 @@ class AdminUsers(APIView):
info['quota_usage'] = -1
info['quota_total'] = -1
- info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
- last_login_obj = UserLastLogin.objects.get_by_username(user.email)
- info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
info['role'] = get_user_role(user)
+
+ info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
+
+ last_login_obj = UserLastLogin.objects.get_by_username(user.email)
+ if last_login_obj:
+ info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
+ info['last_access_time'] = get_user_last_access_time(user.email,
+ last_login_obj.last_login)
+ else:
+ info['last_login'] = ''
+ info['last_access_time'] = get_user_last_access_time(user.email, '')
+
if getattr(settings, 'MULTI_INSTITUTION', False):
info['institution'] = profile.institution if profile else ''
@@ -569,8 +638,7 @@ class AdminUsers(APIView):
if is_org_context(request):
org_id = request.user.org.org_id
- org_quota_mb = seafile_api.get_org_quota(org_id) / \
- get_file_size_unit('MB')
+ org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
if quota_total_mb > org_quota_mb:
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
@@ -611,6 +679,7 @@ class AdminUsers(APIView):
c,
None,
[email2contact_email(email)])
+
add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email}
except Exception as e:
logger.error(str(e))
@@ -669,8 +738,16 @@ class AdminLDAPUsers(APIView):
info['quota_total'] = seafile_api.get_user_quota(user.email)
info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
+
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
- info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
+ if last_login_obj:
+ info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
+ info['last_access_time'] = get_user_last_access_time(user.email,
+ last_login_obj.last_login)
+ else:
+ info['last_login'] = ''
+ info['last_access_time'] = get_user_last_access_time(user.email, '')
+
data.append(info)
result = {'ldap_user_list': data, 'has_next_page': has_next_page}
@@ -859,8 +936,16 @@ class AdminSearchUser(APIView):
info['quota_total'] = seafile_api.get_user_quota(user.email)
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
+
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
- info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
+ if last_login_obj:
+ info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
+ info['last_access_time'] = get_user_last_access_time(user.email,
+ last_login_obj.last_login)
+ else:
+ info['last_login'] = ''
+ info['last_access_time'] = get_user_last_access_time(user.email, '')
+
info['role'] = get_user_role(user)
if getattr(settings, 'MULTI_INSTITUTION', False):
@@ -984,8 +1069,7 @@ class AdminUser(APIView):
if is_org_context(request):
org_id = request.user.org.org_id
- org_quota_mb = seafile_api.get_org_quota(org_id) / \
- get_file_size_unit('MB')
+ org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
if quota_total_mb > org_quota_mb:
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
@@ -1115,7 +1199,7 @@ class AdminUserResetPassword(APIView):
contact_email = Profile.objects.get_contact_email_by_user(email)
try:
send_html_email(_(u'Password has been reset on %s') % get_site_name(),
- 'sysadmin/user_reset_email.html', c, None, [contact_email])
+ 'sysadmin/user_reset_email.html', c, None, [contact_email])
reset_tip = _('Successfully reset password to %(passwd)s, an email has been sent to %(user)s.') % \
{'passwd': new_password, 'user': contact_email}
except Exception as e:
@@ -1299,7 +1383,7 @@ class AdminUserBeSharedRepos(APIView):
if email not in nickname_dict:
if '@seafile_group' in email:
group_id = get_group_id_by_repo_owner(email)
- group_name= group_id_to_name(group_id)
+ group_name = group_id_to_name(group_id)
nickname_dict[email] = group_name
else:
nickname_dict[email] = email2nickname(email)
diff --git a/seahub/api2/endpoints/admin/users_batch.py b/seahub/api2/endpoints/admin/users_batch.py
index 48c111d0de..75a99f7d0f 100644
--- a/seahub/api2/endpoints/admin/users_batch.py
+++ b/seahub/api2/endpoints/admin/users_batch.py
@@ -15,7 +15,6 @@ from django.utils.translation import ugettext as _
from seaserv import seafile_api
-import seahub.settings as settings
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
@@ -218,7 +217,7 @@ class AdminUsersBatch(APIView):
"email": email,
}
admin_operation.send(sender=None, admin_name=request.user.username,
- operation=USER_DELETE, detail=admin_op_detail)
+ operation=USER_DELETE, detail=admin_op_detail)
if operation == 'set-institution':
institution = request.POST.get('institution', None)
@@ -228,7 +227,7 @@ class AdminUsersBatch(APIView):
if institution != '':
try:
- obj_insti = Institution.objects.get(name=institution)
+ Institution.objects.get(name=institution)
except Institution.DoesNotExist:
error_msg = 'Institution %s does not exist' % institution
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -372,14 +371,14 @@ class AdminImportUsers(APIView):
except Exception as e:
logger.error(e)
- send_html_email_with_dj_template(
- email, dj_template='sysadmin/user_batch_add_email.html',
- subject=_('You are invited to join %s') % get_site_name(),
- context={
- 'user': email2nickname(request.user.username),
- 'email': email,
- 'password': password,
- })
+ send_html_email_with_dj_template(email,
+ subject=_('You are invited to join %s') % get_site_name(),
+ dj_template='sysadmin/user_batch_add_email.html',
+ context={
+ 'user': email2nickname(request.user.username),
+ 'email': email,
+ 'password': password
+ })
user = User.objects.get(email=email)
@@ -407,4 +406,3 @@ class AdminImportUsers(APIView):
operation=USER_ADD, detail=admin_op_detail)
return Response(result)
-
diff --git a/seahub/api2/endpoints/file.py b/seahub/api2/endpoints/file.py
index 2068d87bd9..b223b8c204 100644
--- a/seahub/api2/endpoints/file.py
+++ b/seahub/api2/endpoints/file.py
@@ -24,7 +24,7 @@ from seahub.views import check_folder_permission
from seahub.utils.file_op import check_file_lock, if_locked_by_online_office
from seahub.views.file import can_preview_file, can_edit_file
from seahub.constants import PERMISSION_READ_WRITE
-from seahub.utils.repo import parse_repo_perm
+from seahub.utils.repo import parse_repo_perm, is_repo_admin, is_repo_owner
from seahub.utils.file_types import MARKDOWN, TEXT
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \
@@ -57,7 +57,7 @@ class FileView(APIView):
file_name = file_obj.obj_name
file_size = file_obj.size
can_preview, error_msg = can_preview_file(file_name, file_size, repo)
- can_edit, error_msg = can_edit_file(file_name, file_size, repo)
+ can_edit, error_msg = can_edit_file(file_name, file_size, repo)
else:
can_preview = False
can_edit = False
@@ -295,11 +295,9 @@ class FileView(APIView):
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# rename file
- new_file_name = check_filename_with_rename(repo_id, parent_dir,
- new_file_name)
+ new_file_name = check_filename_with_rename(repo_id, parent_dir, new_file_name)
try:
- seafile_api.rename_file(repo_id, parent_dir, oldname,
- new_file_name, username)
+ seafile_api.rename_file(repo_id, parent_dir, oldname, new_file_name, username)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -394,8 +392,8 @@ class FileView(APIView):
new_file_name = check_filename_with_rename(dst_repo_id, dst_dir, filename)
try:
seafile_api.move_file(src_repo_id, src_dir, filename,
- dst_repo_id, dst_dir, new_file_name, replace=False,
- username=username, need_progress=0, synchronous=1)
+ dst_repo_id, dst_dir, new_file_name, replace=False,
+ username=username, need_progress=0, synchronous=1)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -460,7 +458,7 @@ class FileView(APIView):
new_file_name = check_filename_with_rename(dst_repo_id, dst_dir, filename)
try:
seafile_api.copy_file(src_repo_id, src_dir, filename, dst_repo_id,
- dst_dir, new_file_name, username, 0, synchronous=1)
+ dst_dir, new_file_name, username, 0, synchronous=1)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -586,7 +584,9 @@ class FileView(APIView):
error_msg = _("File is not locked.")
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
- if locked_by_me or locked_by_online_office:
+ if locked_by_me or locked_by_online_office or \
+ is_repo_owner(request, repo_id, username) or \
+ is_repo_admin(username, repo_id):
# unlock file
try:
seafile_api.unlock_file(repo_id, path)
diff --git a/seahub/api2/endpoints/invitation.py b/seahub/api2/endpoints/invitation.py
index 6dd043be4a..5ace67cd82 100644
--- a/seahub/api2/endpoints/invitation.py
+++ b/seahub/api2/endpoints/invitation.py
@@ -15,13 +15,13 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.invitations.models import Invitation
from seahub.base.accounts import User
-from post_office.models import STATUS
-from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
+from seahub.utils.mail import send_html_email_with_dj_template
from seahub.utils import get_site_name
logger = logging.getLogger(__name__)
json_content_type = 'application/json; charset=utf-8'
+
def invitation_owner_check(func):
"""Check whether user is the invitation inviter.
"""
@@ -34,6 +34,7 @@ def invitation_owner_check(func):
return _decorated
+
class InvitationView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, CanInviteGuest)
@@ -108,13 +109,12 @@ class InvitationRevokeView(APIView):
'site_name': site_name,
}
- m = send_html_email_with_dj_template(
- email, dj_template='invitations/invitation_revoke_email.html',
- subject=subject,
- context=context,
- priority=MAIL_PRIORITY.now
- )
- if m.status != STATUS.sent:
+ send_success = send_html_email_with_dj_template(email,
+ subject=subject,
+ dj_template='invitations/invitation_revoke_email.html',
+ context=context)
+
+ if not send_success:
logger.warning('send revoke access email to %s failed')
return Response({'success': True})
diff --git a/seahub/api2/endpoints/invitations.py b/seahub/api2/endpoints/invitations.py
index c4eb0ea51e..4a4afc4279 100644
--- a/seahub/api2/endpoints/invitations.py
+++ b/seahub/api2/endpoints/invitations.py
@@ -6,7 +6,6 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
-from post_office.models import STATUS
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import CanInviteGuest
@@ -70,8 +69,9 @@ class InvitationsView(APIView):
i = Invitation.objects.add(inviter=request.user.username,
accepter=accepter)
- m = i.send_to(email=accepter)
- if m.status == STATUS.sent:
+ send_success = i.send_to(email=accepter)
+
+ if send_success:
return Response(i.to_dict(), status=201)
else:
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -119,11 +119,13 @@ class InvitationsBatchView(APIView):
continue
if Invitation.objects.filter(inviter=request.user.username,
- accepter=accepter).count() > 0:
+ accepter=accepter).count() > 0:
+
result['failed'].append({
'email': accepter,
'error_msg': _('%s is already invited.') % accepter
})
+
continue
try:
@@ -139,13 +141,15 @@ class InvitationsBatchView(APIView):
pass
i = Invitation.objects.add(inviter=request.user.username,
- accepter=accepter)
+ accepter=accepter)
- m = i.send_to(email=accepter)
- if m.status != STATUS.sent:
+ send_success = i.send_to(email=accepter)
+
+ if not send_success:
result['failed'].append({
'email': accepter,
- 'error_msg': _('Failed to send email, email service is not properly configured, please contact administrator.'),
+ 'error_msg': _('Failed to send email, email service is not properly configured, \
+ please contact administrator.'),
})
else:
result['success'].append(i.to_dict())
diff --git a/seahub/api2/endpoints/repo_share_invitation.py b/seahub/api2/endpoints/repo_share_invitation.py
index 178f2d4f23..e6910b1189 100644
--- a/seahub/api2/endpoints/repo_share_invitation.py
+++ b/seahub/api2/endpoints/repo_share_invitation.py
@@ -6,7 +6,6 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
-from django.utils.translation import ugettext as _
from seaserv import seafile_api
@@ -14,8 +13,7 @@ from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import CanInviteGuest
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
-from seahub.invitations.models import Invitation, RepoShareInvitation
-from post_office.models import STATUS
+from seahub.invitations.models import RepoShareInvitation
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
from seahub.share.utils import is_repo_admin
from seahub.utils import is_org_context
@@ -82,7 +80,7 @@ class RepoShareInvitationView(APIView):
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
+
return Response({'success': True})
def delete(self, request, repo_id, format=None):
@@ -124,11 +122,11 @@ class RepoShareInvitationView(APIView):
if not shared_obj:
error_msg = 'repo share invitation not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
+
shared_obj.delete()
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
+
return Response({'success': True})
diff --git a/seahub/api2/endpoints/repo_share_invitations.py b/seahub/api2/endpoints/repo_share_invitations.py
index 90134bbbba..cacf8baf8d 100644
--- a/seahub/api2/endpoints/repo_share_invitations.py
+++ b/seahub/api2/endpoints/repo_share_invitations.py
@@ -8,7 +8,6 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
-from post_office.models import STATUS
from seaserv import seafile_api
@@ -20,7 +19,6 @@ from seahub.base.accounts import User
from seahub.utils import is_valid_email
from seahub.invitations.models import Invitation, RepoShareInvitation
from seahub.invitations.utils import block_accepter
-from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, GUEST_USER
from seahub.share.utils import is_repo_admin
from seahub.utils import is_org_context
@@ -29,6 +27,7 @@ from seahub.base.templatetags.seahub_tags import email2nickname
json_content_type = 'application/json; charset=utf-8'
logger = logging.getLogger(__name__)
+
class RepoShareInvitationsView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, CanInviteGuest)
@@ -47,7 +46,7 @@ class RepoShareInvitationsView(APIView):
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
+
if seafile_api.get_dir_id_by_path(repo.id, path) is None:
return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found.' % path)
@@ -124,7 +123,7 @@ class RepoShareInvitationsBatchView(APIView):
if username != repo_owner and not is_repo_admin(username, repo_id):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
+
# main
result = {}
result['failed'] = []
@@ -173,7 +172,7 @@ class RepoShareInvitationsBatchView(APIView):
continue
except User.DoesNotExist:
pass
-
+
if invitation_queryset.filter(accepter=accepter).exists():
invitation = invitation_queryset.filter(accepter=accepter)[0]
else:
@@ -181,12 +180,12 @@ class RepoShareInvitationsBatchView(APIView):
inviter=request.user.username, accepter=accepter)
if shared_queryset.filter(invitation=invitation).exists():
- result['failed'].append({
- 'email': accepter,
- 'error_msg': _('This item has been shared to %s.') % accepter
- })
- continue
-
+ result['failed'].append({
+ 'email': accepter,
+ 'error_msg': _('This item has been shared to %s.') % accepter
+ })
+ continue
+
try:
RepoShareInvitation.objects.add(
invitation=invitation, repo_id=repo_id, path=path, permission=permission)
@@ -203,11 +202,13 @@ class RepoShareInvitationsBatchView(APIView):
result['success'].append(data)
- m = invitation.send_to(email=accepter)
- if m.status != STATUS.sent:
+ send_sucess = invitation.send_to(email=accepter)
+
+ if not send_sucess:
result['failed'].append({
'email': accepter,
- 'error_msg': _('Failed to send email, email service is not properly configured, please contact administrator.'),
+ 'error_msg': _('Failed to send email, email service is not properly configured, \
+ please contact administrator.'),
})
return Response(result)
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index 77d047d85c..7455867d34 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -72,6 +72,7 @@ from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \
gen_shared_upload_link, convert_cmmt_desc_link, is_valid_dirent_name, \
normalize_file_path, get_no_duplicate_obj_name, normalize_dir_path
+from seahub.utils.file_types import IMAGE
from seahub.utils.file_revisions import get_file_revisions_after_renamed
from seahub.utils.devices import do_unlink_device
from seahub.utils.repo import get_repo_owner, get_library_storages, \
@@ -424,6 +425,7 @@ class Search(APIView):
throttle_classes = (UserRateThrottle, )
def get(self, request, format=None):
+
if not HAS_FILE_SEARCH:
error_msg = 'Search not supported.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
@@ -601,6 +603,16 @@ class Search(APIView):
else:
e['repo_type'] = ''
+ e['thumbnail_url'] = ''
+ filetype, fileext = get_file_type_and_ext(e.get('name', ''))
+
+ if filetype == IMAGE:
+ thumbnail_url = reverse('api2-thumbnail',
+ args=[e.get('repo_id', '')],
+ request=request)
+ params = '?p={}&size={}'.format(quote(e.get('fullpath', '').encode('utf-8')), 72)
+ e['thumbnail_url'] = thumbnail_url + params
+
has_more = True if total > current_page * per_page else False
return Response({"total":total, "results":results, "has_more":has_more})
@@ -5054,7 +5066,7 @@ class OfficeGenerateView(APIView):
return HttpResponse(json.dumps(ret_dict), status=200, content_type=json_content_type)
class ThumbnailView(APIView):
- authentication_classes = (TokenAuthentication,)
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py
index b94dcf9012..3c2c464e39 100644
--- a/seahub/base/accounts.py
+++ b/seahub/base/accounts.py
@@ -10,7 +10,6 @@ from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
-import seaserv
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, \
seafile_api, ccnet_api
from constance import config
@@ -24,7 +23,7 @@ from seahub.role_permissions.utils import get_enabled_role_permissions_by_role,
get_enabled_admin_role_permissions_by_role
from seahub.utils import is_user_password_strong, get_site_name, \
clear_token, get_system_admins, is_pro_version, IS_EMAIL_CONFIGURED
-from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
+from seahub.utils.mail import send_html_email_with_dj_template
from seahub.utils.licenseparse import user_number_over_limit
from seahub.share.models import ExtraSharePermission
@@ -41,7 +40,8 @@ logger = logging.getLogger(__name__)
ANONYMOUS_EMAIL = 'Anonymous'
-UNUSABLE_PASSWORD = '!' # This will never be a valid hash
+UNUSABLE_PASSWORD = '!' # This will never be a valid hash
+
class UserManager(object):
@@ -122,6 +122,7 @@ class UserManager(object):
return user
+
class UserPermissions(object):
def __init__(self, user):
self.user = user
@@ -222,6 +223,7 @@ class UserPermissions(object):
return self._get_perm_by_roles('can_publish_repo')
+
class AdminPermissions(object):
def __init__(self, user):
self.user = user
@@ -322,8 +324,8 @@ class User(object):
return True
def save(self):
- emailuser = ccnet_threaded_rpc.get_emailuser(self.username)
- if emailuser:
+ emailuser = ccnet_api.get_emailuser(self.username)
+ if emailuser and emailuser.source.lower() in ("db", "ldapimport"):
if not hasattr(self, 'password'):
self.set_unusable_password()
@@ -385,12 +387,10 @@ class User(object):
if orgs:
for org in orgs:
org_id = org.org_id
- shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id,
- username, -1, -1)
+ shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id, username, -1, -1)
for r in shared_in_repos:
- seafile_api.org_remove_share(org_id,
- r.repo_id, r.user, username)
+ seafile_api.org_remove_share(org_id, r.repo_id, r.user, username)
else:
shared_in_repos = seafile_api.get_share_in_repo_list(username, -1, -1)
for r in shared_in_repos:
@@ -478,15 +478,12 @@ class User(object):
user_language = Profile.objects.get_user_language(u.email)
translation.activate(user_language)
- send_html_email_with_dj_template(
- u.email, dj_template='sysadmin/user_freeze_email.html',
- subject=_('Account %(account)s froze on %(site)s.') % {
- "account": self.email,
- "site": get_site_name(),
- },
- context={'user': self.email},
- priority=MAIL_PRIORITY.now
- )
+ send_html_email_with_dj_template(u.email,
+ subject=_('Account %(account)s froze on %(site)s.') % {
+ "account": self.email,
+ "site": get_site_name()},
+ dj_template='sysadmin/user_freeze_email.html',
+ context={'user': self.email})
# restore current language
translation.activate(cur_language)
@@ -535,10 +532,11 @@ class User(object):
for r in passwd_setted_repos:
unset_repo_passwd(r.id, self.email)
+
class AuthBackend(object):
def get_user_with_import(self, username):
- emailuser = seaserv.get_emailuser_with_import(username)
+ emailuser = ccnet_api.get_emailuser_with_import(username)
if not emailuser:
raise User.DoesNotExist('User matching query does not exits.')
@@ -580,7 +578,8 @@ class AuthBackend(object):
if user.check_password(password):
return user
-########## Register related
+
+# Register related
class RegistrationBackend(object):
"""
A registration backend which follows a simple workflow:
@@ -653,10 +652,10 @@ class RegistrationBackend(object):
# since user will be activated after registration,
# so we will not use email sending, just create acitvated user
new_user = RegistrationProfile.objects.create_active_user(username, email,
- password, site,
- send_email=False)
+ password, site,
+ send_email=False)
# login the user
- new_user.backend=settings.AUTHENTICATION_BACKENDS[0]
+ new_user.backend = settings.AUTHENTICATION_BACKENDS[0]
login(request, new_user)
else:
@@ -702,7 +701,7 @@ class RegistrationBackend(object):
user=activated,
request=request)
# login the user
- activated.backend=settings.AUTHENTICATION_BACKENDS[0]
+ activated.backend = settings.AUTHENTICATION_BACKENDS[0]
login(request, activated)
return activated
@@ -753,17 +752,17 @@ class RegistrationForm(forms.Form):
Validates that the requested email is not already in use, and
requires the password to be entered twice to catch typos.
"""
- attrs_dict = { 'class': 'input' }
+ attrs_dict = {'class': 'input'}
+
+ email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)),
+ label=_("Email address"))
- email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=75)),
- label=_("Email address"))
userid = forms.RegexField(regex=r'^\w+$',
max_length=40,
required=False,
widget=forms.TextInput(),
label=_("Username"),
- error_messages={ 'invalid': _("This value must be of length 40") })
+ error_messages={'invalid': _("This value must be of length 40")})
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Password"))
@@ -825,12 +824,13 @@ class RegistrationForm(forms.Form):
raise forms.ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
+
class DetailedRegistrationForm(RegistrationForm):
- attrs_dict = { 'class': 'input' }
+ attrs_dict = {'class': 'input'}
try:
from seahub.settings import REGISTRATION_DETAILS_MAP
- except:
+ except ImportError:
REGISTRATION_DETAILS_MAP = None
if REGISTRATION_DETAILS_MAP:
diff --git a/seahub/base/context_processors.py b/seahub/base/context_processors.py
index c79d589bb5..6b7ca9b252 100644
--- a/seahub/base/context_processors.py
+++ b/seahub/base/context_processors.py
@@ -133,6 +133,7 @@ def base(request):
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
'FILE_SERVER_ROOT': file_server_root,
'LOGIN_URL': dj_settings.LOGIN_URL,
+ 'LOGOUT_URL': dj_settings.LOGOUT_URL,
'enable_thumbnail': ENABLE_THUMBNAIL,
'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL,
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
diff --git a/seahub/invitations/models.py b/seahub/invitations/models.py
index a04e5b380f..2db61cfb99 100644
--- a/seahub/invitations/models.py
+++ b/seahub/invitations/models.py
@@ -2,7 +2,6 @@
from datetime import timedelta
from django.db import models
-from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import ugettext as _
@@ -10,11 +9,12 @@ from seahub.base.fields import LowerCaseCharField
from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
from seahub.utils import gen_token, get_site_name
from seahub.utils.timeutils import datetime_to_isoformat_timestr
-from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
+from seahub.utils.mail import send_html_email_with_dj_template
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
GUEST = 'Guest'
+
class InvitationManager(models.Manager):
def add(self, inviter, accepter, invite_type=GUEST):
token = gen_token(max_length=32)
@@ -26,8 +26,7 @@ class InvitationManager(models.Manager):
return i
def get_by_inviter(self, inviter):
- return super(InvitationManager,
- self).filter(inviter=inviter).order_by('-invite_time')
+ return super(InvitationManager, self).filter(inviter=inviter).order_by('-invite_time')
def delete_all_expire_invitation(self):
super(InvitationManager, self).filter(expire_time__lte=timezone.now(), accept_time__isnull=True).delete()
@@ -93,16 +92,13 @@ class Invitation(models.Model):
context = self.to_dict()
context['site_name'] = get_site_name()
- # subject = render_to_string('invitations/invitation_email_subject.txt',
- # context).rstrip()
subject = _('%(user)s invited you to join %(site_name)s.') % {
'user': self.inviter, 'site_name': get_site_name()}
- return send_html_email_with_dj_template(
- email, dj_template='invitations/invitation_email.html',
- context=context,
- subject=subject,
- priority=MAIL_PRIORITY.now
- )
+
+ return send_html_email_with_dj_template(email,
+ subject=subject,
+ dj_template='invitations/invitation_email.html',
+ context=context)
class RepoShareInvitationManager(models.Manager):
@@ -115,7 +111,7 @@ class RepoShareInvitationManager(models.Manager):
)
obj.save()
return obj
-
+
def list_by_repo_id_and_path(self, repo_id, path):
return self.select_related('invitation').filter(
invitation__expire_time__gte=timezone.now(),
@@ -132,11 +128,13 @@ class RepoShareInvitationManager(models.Manager):
return qs[0]
else:
return None
-
+
def list_by_invitation(self, invitation):
return self.select_related('invitation').filter(invitation=invitation)
+
class RepoShareInvitation(models.Model):
+
PERMISSION_CHOICES = (
(PERMISSION_READ, 'read only'),
(PERMISSION_READ_WRITE, 'read and write')
diff --git a/seahub/notifications/management/commands/notify_admins_on_virus.py b/seahub/notifications/management/commands/notify_admins_on_virus.py
index c32867edf3..545a71c3bf 100644
--- a/seahub/notifications/management/commands/notify_admins_on_virus.py
+++ b/seahub/notifications/management/commands/notify_admins_on_virus.py
@@ -12,13 +12,15 @@ import seaserv
from seaserv import seafile_api
from seahub.profile.models import Profile
-from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
+from seahub.utils.mail import send_html_email_with_dj_template
from seahub.utils import get_site_name
# Get an instance of a logger
logger = logging.getLogger(__name__)
+
class Command(BaseCommand):
+
help = 'Send Email notifications to admins if there are virus files detected .'
label = "notifications_notify_admins_on_virus"
@@ -52,11 +54,9 @@ class Command(BaseCommand):
user_language = self.get_user_language(u.email)
translation.activate(user_language)
- send_html_email_with_dj_template(
- u.email, dj_template='notifications/notify_virus.html',
- subject=_('Virus detected on %s') % get_site_name(),
- priority=MAIL_PRIORITY.now
- )
+ send_html_email_with_dj_template(u.email,
+ subject=_('Virus detected on %s') % get_site_name(),
+ dj_template='notifications/notify_virus.html')
# restore current language
translation.activate(cur_language)
@@ -68,11 +68,9 @@ class Command(BaseCommand):
return
for mail in notify_list:
- send_html_email_with_dj_template(
- mail, dj_template='notifications/notify_virus.html',
- subject=_('Virus detected on %s') % get_site_name(),
- priority=MAIL_PRIORITY.now
- )
+ send_html_email_with_dj_template(mail,
+ subject=_('Virus detected on %s') % get_site_name(),
+ dj_template='notifications/notify_virus.html')
def email_repo_owner(self, repo_file):
repo_id, file_path = repo_file.split(':', 1)
@@ -88,16 +86,12 @@ class Command(BaseCommand):
translation.activate(user_language)
contact_email = Profile.objects.get_contact_email_by_user(owner)
- send_html_email_with_dj_template(
- contact_email, dj_template='notifications/notify_virus.html',
- context={'owner': owner,
- 'file_url': reverse('view_lib_file',
- args=[repo_id, file_path]),
- 'file_name': os.path.basename(file_path),
- },
- subject=_('Virus detected on %s') % get_site_name(),
- priority=MAIL_PRIORITY.now
- )
+ send_html_email_with_dj_template(contact_email,
+ subject=_('Virus detected on %s') % get_site_name(),
+ dj_template='notifications/notify_virus.html',
+ context={'owner': owner,
+ 'file_url': reverse('view_lib_file', args=[repo_id, file_path]),
+ 'file_name': os.path.basename(file_path)})
# restore current language
translation.activate(cur_language)
diff --git a/seahub/onlyoffice/settings.py b/seahub/onlyoffice/settings.py
index 645b4adfe6..23d401be47 100644
--- a/seahub/onlyoffice/settings.py
+++ b/seahub/onlyoffice/settings.py
@@ -11,3 +11,5 @@ ONLYOFFICE_JWT_SECRET = getattr(settings, 'ONLYOFFICE_JWT_SECRET', '')
# if True, file will be saved when user click save btn on file editing page
ONLYOFFICE_FORCE_SAVE = getattr(settings, 'ONLYOFFICE_FORCE_SAVE', False)
+
+ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN = getattr(settings, 'ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN', False)
diff --git a/seahub/settings.py b/seahub/settings.py
index 1db61efdbb..f259595aef 100644
--- a/seahub/settings.py
+++ b/seahub/settings.py
@@ -218,7 +218,6 @@ INSTALLED_APPS = [
'statici18n',
'constance',
'constance.backends.database',
- 'post_office',
'termsandconditions',
'webpack_loader',
diff --git a/seahub/share/views.py b/seahub/share/views.py
index 87e8ea4214..61ec42fd14 100644
--- a/seahub/share/views.py
+++ b/seahub/share/views.py
@@ -3,34 +3,30 @@
import os
import logging
import json
-from dateutil.relativedelta import relativedelta
-from constance import config
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseRedirect, Http404, \
HttpResponseBadRequest
from django.utils.translation import ugettext as _, activate
from django.contrib import messages
-from django.utils import timezone
from django.utils.html import escape
+
import seaserv
from seaserv import seafile_api
-from seaserv import ccnet_threaded_rpc
from pysearpc import SearpcError
from seahub.share.forms import FileLinkShareForm, \
UploadLinkShareForm
-from seahub.share.models import FileShare, UploadLinkShare, OrgFileShare
+from seahub.share.models import FileShare, UploadLinkShare
from seahub.share.signals import share_repo_to_user_successful
from seahub.auth.decorators import login_required, login_required_ajax
from seahub.base.decorators import require_POST
from seahub.contacts.signals import mail_sended
from seahub.views import is_registered_user, check_folder_permission
-from seahub.utils import string2list, gen_shared_link, \
- gen_shared_upload_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
+from seahub.utils import string2list, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
is_valid_username, is_valid_email, send_html_email, is_org_context, \
gen_token, normalize_cache_key, get_site_name
-from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
+from seahub.utils.mail import send_html_email_with_dj_template
from seahub.settings import SITE_ROOT, REPLACE_FROM_EMAIL, \
ADD_REPLY_TO_HEADER, SHARE_LINK_EMAIL_LANGUAGE, \
SHARE_LINK_AUDIT_CODE_TIMEOUT
@@ -39,21 +35,25 @@ from seahub.profile.models import Profile
# Get an instance of a logger
logger = logging.getLogger(__name__)
-########## rpc wrapper
+
+# rpc wrapper
def is_org_repo_owner(username, repo_id):
owner = seaserv.seafserv_threaded_rpc.get_org_repo_owner(repo_id)
return True if owner == username else False
+
def org_share_repo(org_id, repo_id, from_user, to_user, permission):
return seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id,
from_user, to_user,
permission)
+
def org_remove_share(org_id, repo_id, from_user, to_user):
return seaserv.seafserv_threaded_rpc.org_remove_share(org_id, repo_id,
from_user, to_user)
-########## functions
+
+# functions
def share_to_group(request, repo, group, permission):
"""Share repo to group with given permission.
"""
@@ -83,6 +83,7 @@ def share_to_group(request, repo, group, permission):
logger.error(e)
return False
+
def share_to_user(request, repo, to_user, permission):
"""Share repo to a user with given permission.
"""
@@ -109,8 +110,8 @@ def share_to_user(request, repo, to_user, permission):
else:
seafile_api.share_repo(repo_id, from_user, to_user, permission)
except SearpcError as e:
- return False
- logger.error(e)
+ logger.error(e)
+ return False
else:
# send a signal when sharing repo successful
share_repo_to_user_successful.send(sender=None,
@@ -119,7 +120,8 @@ def share_to_user(request, repo, to_user, permission):
path='/', org_id=org_id)
return True
-########## share link
+
+# share link
@login_required_ajax
def send_shared_link(request):
"""
@@ -131,7 +133,8 @@ def send_shared_link(request):
content_type = 'application/json; charset=utf-8'
if not IS_EMAIL_CONFIGURED:
- data = json.dumps({'error':_('Sending shared link failed. Email service is not properly configured, please contact administrator.')})
+ data = json.dumps({'error': _('Sending shared link failed. \
+ Email service is not properly configured, please contact administrator.')})
return HttpResponse(data, status=500, content_type=content_type)
form = FileLinkShareForm(request.POST)
@@ -207,6 +210,7 @@ def send_shared_link(request):
return HttpResponseBadRequest(json.dumps(form.errors),
content_type=content_type)
+
@login_required
def save_shared_link(request):
"""Save public share link to one's library.
@@ -246,6 +250,7 @@ def save_shared_link(request):
messages.success(request, _('Successfully saved.'))
return HttpResponseRedirect(next_page)
+
@login_required_ajax
def send_shared_upload_link(request):
"""
@@ -257,7 +262,8 @@ def send_shared_upload_link(request):
content_type = 'application/json; charset=utf-8'
if not IS_EMAIL_CONFIGURED:
- data = json.dumps({'error':_('Sending shared upload link failed. Email service is not properly configured, please contact administrator.')})
+ data = json.dumps({'error': _('Sending shared upload link failed. \
+ Email service is not properly configured, please contact administrator.')})
return HttpResponse(data, status=500, content_type=content_type)
form = UploadLinkShareForm(request.POST)
@@ -317,6 +323,7 @@ def send_shared_upload_link(request):
return HttpResponseBadRequest(json.dumps(form.errors),
content_type=content_type)
+
@login_required_ajax
@require_POST
def ajax_private_share_dir(request):
@@ -359,8 +366,7 @@ def ajax_private_share_dir(request):
sub_repo_id = seaserv.seafserv_threaded_rpc.create_org_virtual_repo(
org_id, repo_id, path, name, name, username)
else:
- sub_repo_id = seafile_api.create_virtual_repo(repo_id, path,
- name, name, username)
+ sub_repo_id = seafile_api.create_virtual_repo(repo_id, path, name, name, username)
sub_repo = seafile_api.get_repo(sub_repo_id)
except SearpcError as e:
result['error'] = e.msg
@@ -423,6 +429,7 @@ def ajax_private_share_dir(request):
data = json.dumps({"error": _("Please check the email(s) you entered")})
return HttpResponse(data, status=400, content_type=content_type)
+
def ajax_get_link_audit_code(request):
"""
Generate a token, and record that token with email in cache, expires in
@@ -455,19 +462,18 @@ def ajax_get_link_audit_code(request):
# send code to user via email
subject = _("Verification code for visiting share links")
- c = {
- 'code': code,
- }
- try:
- send_html_email_with_dj_template(
- email, dj_template='share/audit_code_email.html',
- context=c, subject=subject, priority=MAIL_PRIORITY.now
- )
- return HttpResponse(json.dumps({'success': True}), status=200,
- content_type=content_type)
- except Exception as e:
+ c = {'code': code}
+
+ send_success = send_html_email_with_dj_template(email,
+ subject=subject,
+ dj_template='share/audit_code_email.html',
+ context=c)
+
+ if not send_success:
logger.error('Failed to send audit code via email to %s')
- logger.error(e)
return HttpResponse(json.dumps({
"error": _("Failed to send a verification code, please try again later.")
}), status=500, content_type=content_type)
+
+ return HttpResponse(json.dumps({'success': True}), status=200,
+ content_type=content_type)
diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html
index 08590d10a2..23871dcdfa 100644
--- a/seahub/templates/base_for_react.html
+++ b/seahub/templates/base_for_react.html
@@ -40,6 +40,7 @@
siteName: '{{ site_name|escapejs }}',
siteRoot: '{{ SITE_ROOT }}',
loginUrl: '{{ LOGIN_URL }}',
+ logoutUrl: '{{ LOGOUT_URL }}',
isPro: '{{ is_pro }}',
isDocs: '{{ is_docs }}',
lang: '{{ LANGUAGE_CODE }}',
diff --git a/seahub/templates/react_app.html b/seahub/templates/react_app.html
index 6ed66e672b..8f5f6168af 100644
--- a/seahub/templates/react_app.html
+++ b/seahub/templates/react_app.html
@@ -1,5 +1,6 @@
{% extends "base_for_react.html" %}
{% load render_bundle from webpack_loader %}
+{% load seahub_tags %}
{% block extra_style %}
{% render_bundle 'app' 'css' %}
@@ -16,6 +17,15 @@
uploadLinkExpireDaysMin: {{ upload_link_expire_days_min }},
uploadLinkExpireDaysMax: {{ upload_link_expire_days_max }}
});
+ {% if onlyoffice_desktop_editors_portal_login %}
+ let params = {
+ "displayName": "{{request.user.username|email2nickname|escapejs}}",
+ "email": "{{request.user.username|escapejs}}",
+ "domain": "{{service_url}}",
+ "provider": "{{site_name}}"
+ }
+ window.AscDesktopEditor.execCommand('portal:login', JSON.stringify(params));
+ {% endif %}
{% render_bundle 'app' 'js' %}
{% endblock %}
diff --git a/seahub/utils/mail.py b/seahub/utils/mail.py
index a70ad68048..245880e730 100644
--- a/seahub/utils/mail.py
+++ b/seahub/utils/mail.py
@@ -1,27 +1,23 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import os
-from django.template import Context, loader
-from post_office import mail
-from post_office.models import PRIORITY
-from constance import config
+import logging
+from django.template import loader
+from django.core.mail import EmailMessage
from seahub.utils import get_site_scheme_and_netloc, get_site_name
from seahub.settings import MEDIA_URL, LOGO_PATH, \
MEDIA_ROOT, CUSTOM_LOGO_PATH
-MAIL_PRIORITY = PRIORITY # 'low medium high now'
+logger = logging.getLogger(__name__)
-def send_html_email_with_dj_template(recipients, subject, dj_template,
- context={}, sender=None, template=None,
- message='', headers=None,
- priority=None, backend=''):
+
+def send_html_email_with_dj_template(recipients, subject, dj_template, context={}):
"""
Arguments:
- `recipients`:
- `subject`:
- - `sender`:
- - `template`:
+ - `dj_template`:
- `context`:
"""
@@ -42,7 +38,12 @@ def send_html_email_with_dj_template(recipients, subject, dj_template,
t = loader.get_template(dj_template)
html_message = t.render(context)
- return mail.send(recipients, sender=sender, template=template, context=context,
- subject=subject, message=message,
- html_message=html_message, headers=headers, priority=priority,
- backend=backend)
+ mail = EmailMessage(subject=subject, body=html_message, to=[recipients])
+ mail.content_subtype = "html"
+
+ try:
+ mail.send()
+ return True
+ except Exception as e:
+ logger.error(e)
+ return False
diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py
index dab3b2804a..06d9e30f84 100644
--- a/seahub/views/__init__.py
+++ b/seahub/views/__init__.py
@@ -60,7 +60,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, \
DTABLE_WEB_SERVER
from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP
-from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE
+from seahub.onlyoffice.settings import ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
from seahub.constants import HASH_URLS, PERMISSION_READ
@@ -1180,6 +1180,7 @@ def react_fake_view(request, **kwargs):
max_upload_file_size = -1
return render(request, "react_app.html", {
+ "onlyoffice_desktop_editors_portal_login": ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN,
"guide_enabled": guide_enabled,
'trash_repos_expire_days': expire_days if expire_days > 0 else 30,
'dtable_web_server': DTABLE_WEB_SERVER,
diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py
index cf58430e2e..9c3876f384 100644
--- a/seahub/views/sysadmin.py
+++ b/seahub/views/sysadmin.py
@@ -57,7 +57,6 @@ from seahub.utils.ldap import get_ldap_info
from seahub.utils.licenseparse import parse_license, user_number_over_limit
from seahub.utils.rpc import mute_seafile_api
from seahub.utils.sysinfo import get_platform_name
-from seahub.utils.mail import send_html_email_with_dj_template
from seahub.utils.ms_excel import write_xls
from seahub.utils.user_permissions import get_basic_user_roles, \
get_user_role, get_basic_admin_roles
| | |