diff --git a/frontend/src/components/common/notice-item.js b/frontend/src/components/common/notice-item.js index 119efbab5e..18c4f03505 100644 --- a/frontend/src/components/common/notice-item.js +++ b/frontend/src/components/common/notice-item.js @@ -15,6 +15,7 @@ const MSG_TYPE_REPO_SHARE = 'repo_share'; const MSG_TYPE_REPO_SHARE_TO_GROUP = 'repo_share_to_group'; const MSG_TYPE_REPO_TRANSFER = 'repo_transfer'; const MSG_TYPE_FILE_UPLOADED = 'file_uploaded'; +const MSG_TYPE_FOLDER_UPLOADED = 'folder_uploaded'; // const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'; const MSG_TYPE_REPO_MONITOR = 'repo_monitor'; const MSG_TYPE_DELETED_FILES = 'deleted_files'; @@ -221,6 +222,40 @@ class NoticeItem extends React.Component { return {avatar_url, notice}; } + if (noticeType === MSG_TYPE_FOLDER_UPLOADED) { + let avatar_url = detail.uploaded_user_avatar_url; + let folderName = detail.folder_name; + let folderLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + detail.folder_path; + + let parentDirName = detail.parent_dir_name; + let parentDirLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + detail.parent_dir_path; + let notice = ''; + if (detail.repo_id) { // todo is repo exist ? + // 1. handle translate + notice = gettext('A folder named {upload_folder_link} is uploaded to {uploaded_link}.'); + + // 2. handle xss(cross-site scripting) + notice = notice.replace('{upload_folder_link}', `{tagA}${folderName}{/tagA}`); + notice = notice.replace('{uploaded_link}', `{tagB}${parentDirName}{/tagB}`); + notice = Utils.HTMLescape(notice); + + // 3. add jump link + notice = notice.replace('{tagA}', ``); + notice = notice.replace('{/tagA}', ''); + notice = notice.replace('{tagB}', ``); + notice = notice.replace('{/tagB}', ''); + } else { + // 1. handle translate + notice = gettext('A folder named {upload_folder_link} is uploaded to {uploaded_link}.'); + + // 2. handle xss(cross-site scripting) + notice = notice.replace('{upload_folder_link}', `${folderName}`); + notice = Utils.HTMLescape(notice); + notice = notice.replace('{uploaded_link}', 'Deleted Library'); + } + return {avatar_url, notice}; + } + if (noticeType === MSG_TYPE_REPO_MONITOR) { const { op_user_avatar_url: avatar_url, diff --git a/frontend/src/pages/upload-link/index.js b/frontend/src/pages/upload-link/index.js index 46cee8107a..b9cf75af11 100644 --- a/frontend/src/pages/upload-link/index.js +++ b/frontend/src/pages/upload-link/index.js @@ -25,7 +25,8 @@ class SharedUploadLink extends React.Component { onFileUploadSuccess = (direntObject) => { const { name } = direntObject; - seafileAPI.shareLinksUploadDone(token, Utils.joinPath(path, name)); + const isDir = direntObject.type === 'dir'; + seafileAPI.shareLinksUploadDone(token, Utils.joinPath(path, name), isDir); }; render() { diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py index 7e356b90fb..ef82b326e5 100644 --- a/seahub/api2/endpoints/share_links.py +++ b/seahub/api2/endpoints/share_links.py @@ -51,7 +51,7 @@ from seahub.settings import SHARE_LINK_EXPIRE_DAYS_MAX, \ from seahub.wiki.models import Wiki from seahub.views.file import can_edit_file from seahub.views import check_folder_permission -from seahub.signals import upload_file_successful +from seahub.signals import upload_file_successful, upload_folder_successful from seahub.repo_tags.models import RepoTags logger = logging.getLogger(__name__) @@ -1129,33 +1129,17 @@ class ShareLinkUploadDone(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) parent_dir = share_link.path - if seafile_api.check_permission_by_path(repo_id, parent_dir, share_link.username) != 'rw': + link_owner = share_link.username + if seafile_api.check_permission_by_path(repo_id, + parent_dir, + link_owner) != 'rw': error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - file_path = request.data.get('file_path') - if not file_path: - error_msg = 'file_path invalid.' - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - file_id = seafile_api.get_file_id_by_path(repo_id, file_path) - if not file_id: - error_msg = 'File %s not found.' % file_path - return api_error(status.HTTP_404_NOT_FOUND, error_msg) - - # send singal - upload_file_successful.send(sender=None, - repo_id=repo_id, - file_path=file_path, - owner=share_link.username) - - return Response({'success': True}) - if upload_link: - if upload_link.is_encrypted() and not check_share_link_access(request, - token, - is_upload_link=True): + if upload_link.is_encrypted() and not \ + check_share_link_access(request, token, is_upload_link=True): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) @@ -1170,26 +1154,44 @@ class ShareLinkUploadDone(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) parent_dir = upload_link.path - if seafile_api.check_permission_by_path(repo_id, parent_dir, upload_link.username) != 'rw': + link_owner = upload_link.username + if seafile_api.check_permission_by_path(repo_id, + parent_dir, + link_owner) != 'rw': error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - file_path = request.data.get('file_path') - if not file_path: - error_msg = 'file_path invalid.' - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + # send signal + dirent_path = request.data.get('file_path') + if not dirent_path: + error_msg = 'file_path invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - file_id = seafile_api.get_file_id_by_path(repo_id, file_path) - if not file_id: - error_msg = 'File %s not found.' % file_path + is_dir = request.data.get('is_dir', 'false') + if is_dir.lower() == 'true': + dir_id = seafile_api.get_dir_id_by_path(repo_id, dirent_path) + if not dir_id: + error_msg = 'Folder %s not found.' % dirent_path return api_error(status.HTTP_404_NOT_FOUND, error_msg) + # send singal + upload_folder_successful.send(sender=None, + repo_id=repo_id, + folder_path=dirent_path, + owner=link_owner) + else: + file_id = seafile_api.get_file_id_by_path(repo_id, dirent_path) + if not file_id: + error_msg = 'File %s not found.' % dirent_path + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # send singal upload_file_successful.send(sender=None, repo_id=repo_id, - file_path=file_path, - owner=upload_link.username) + file_path=dirent_path, + owner=link_owner) - return Response({'success': True}) + return Response({'success': True}) class ShareLinkSaveFileToRepo(APIView): diff --git a/seahub/notifications/management/commands/send_notices.py b/seahub/notifications/management/commands/send_notices.py index 6e9ea60f19..6649743245 100644 --- a/seahub/notifications/management/commands/send_notices.py +++ b/seahub/notifications/management/commands/send_notices.py @@ -153,6 +153,32 @@ class Command(BaseCommand): notice.avatar_src = self.get_default_avatar_src() return notice + def format_folder_uploaded_msg(self, notice): + d = json.loads(notice.detail) + + folder_name = d['folder_name'] + repo_id = d['repo_id'] + repo = seafile_api.get_repo(repo_id) + + uploaded_to = d['uploaded_to'] + if uploaded_to != '/': + uploaded_to = d['uploaded_to'].rstrip('/') + folder_path = uploaded_to + '/' + folder_name + parent_dir_link = reverse('lib_view', args=[repo_id, repo.name, uploaded_to.strip('/')]) + parent_dir_name = os.path.basename(uploaded_to) + else: + folder_path = '/' + folder_name + parent_dir_link = reverse('lib_view', args=[repo_id, repo.name, '']) + parent_dir_name = repo.name + + folder_link = reverse('lib_view', args=[repo_id, repo.name, folder_path.strip('/')]) + notice.folder_link = folder_link + notice.folder_name = folder_name + notice.parent_dir_link = parent_dir_link + notice.parent_dir_name = parent_dir_name + notice.avatar_src = self.get_default_avatar_src() + return notice + def format_group_join_request(self, notice): d = json.loads(notice.detail) username = d['username'] @@ -299,13 +325,17 @@ class Command(BaseCommand): results = {} for notice in all_unseen_notices: if notice.to_user not in results: - results[notice.to_user] = {'notices': [notice], 'sdoc_notices': [] , 'interval': DEFAULT_COLLABORATE_EMAIL_INTERVAL} + results[notice.to_user] = {'notices': [notice], + 'sdoc_notices': [], + 'interval': DEFAULT_COLLABORATE_EMAIL_INTERVAL} else: results[notice.to_user]['notices'].append(notice) for sdoc_notice in all_unseen_sdoc_notices: if sdoc_notice.username not in results: - results[sdoc_notice.username] = {'notices': [], 'sdoc_notices': [sdoc_notice], 'interval': DEFAULT_COLLABORATE_EMAIL_INTERVAL} + results[sdoc_notice.username] = {'notices': [], + 'sdoc_notices': [sdoc_notice], + 'interval': DEFAULT_COLLABORATE_EMAIL_INTERVAL} else: results[sdoc_notice.username]['sdoc_notices'].append(sdoc_notice) @@ -323,7 +353,9 @@ class Command(BaseCommand): else: results[email]['interval'] = interval - return [(key, value['interval'], value['notices'], value['sdoc_notices'], sdoc_queryset) for key, value in results.items()] + return [(key, value['interval'], + value['notices'], value['sdoc_notices'], + sdoc_queryset) for key, value in results.items()] def do_action(self): @@ -408,6 +440,9 @@ class Command(BaseCommand): elif notice.is_file_uploaded_msg(): notice = self.format_file_uploaded_msg(notice) + elif notice.is_folder_uploaded_msg(): + notice = self.format_folder_uploaded_msg(notice) + elif notice.is_group_join_request(): notice = self.format_group_join_request(notice) diff --git a/seahub/notifications/models.py b/seahub/notifications/models.py index e505149f08..6c4ed40d12 100644 --- a/seahub/notifications/models.py +++ b/seahub/notifications/models.py @@ -66,6 +66,7 @@ class NotificationForm(ModelForm): MSG_TYPE_GROUP_JOIN_REQUEST = 'group_join_request' MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group' MSG_TYPE_FILE_UPLOADED = 'file_uploaded' +MSG_TYPE_FOLDER_UPLOADED = 'folder_uploaded' MSG_TYPE_REPO_SHARE = 'repo_share' MSG_TYPE_REPO_SHARE_PERM_CHANGE = 'repo_share_perm_change' MSG_TYPE_REPO_SHARE_PERM_DELETE = 'repo_share_perm_delete' @@ -88,6 +89,12 @@ def file_uploaded_msg_to_json(file_name, repo_id, uploaded_to): return json.dumps({'file_name': file_name, 'repo_id': repo_id, 'uploaded_to': uploaded_to}) +def folder_uploaded_msg_to_json(folder_name, repo_id, uploaded_to): + """Encode folder uploaded message to json string. + """ + return json.dumps({'folder_name': folder_name, 'repo_id': repo_id, + 'uploaded_to': uploaded_to}) + def repo_share_msg_to_json(share_from, repo_id, path, org_id): return json.dumps({'share_from': share_from, 'repo_id': repo_id, 'path': path, 'org_id': org_id}) @@ -265,6 +272,18 @@ class UserNotificationManager(models.Manager): return self._add_user_notification(to_user, MSG_TYPE_FILE_UPLOADED, detail) + def add_folder_uploaded_msg(self, to_user, detail): + """ + + Arguments: + - `self`: + - `to_user`: + - `folder_name`: + - `upload_to`: + """ + return self._add_user_notification(to_user, + MSG_TYPE_FOLDER_UPLOADED, detail) + def add_repo_share_msg(self, to_user, detail): """Notify ``to_user`` that others shared a repo to him/her. @@ -378,6 +397,14 @@ class UserNotification(models.Model): """ return self.msg_type == MSG_TYPE_FILE_UPLOADED + def is_folder_uploaded_msg(self): + """ + + Arguments: + - `self`: + """ + return self.msg_type == MSG_TYPE_FOLDER_UPLOADED + def is_repo_share_msg(self): """ @@ -473,6 +500,8 @@ class UserNotification(models.Model): def format_msg(self): if self.is_file_uploaded_msg(): return self.format_file_uploaded_msg() + if self.is_folder_uploaded_msg(): + return self.format_folder_uploaded_msg() elif self.is_repo_share_msg(): return self.format_repo_share_msg() elif self.is_repo_share_to_group_msg(): @@ -535,6 +564,48 @@ class UserNotification(models.Model): return msg + def format_folder_uploaded_msg(self): + """ + + Arguments: + - `self`: + """ + try: + d = json.loads(self.detail) + except Exception as e: + logger.error(e) + return _("Internal Server Error") + + foldername = d['folder_name'] + repo_id = d['repo_id'] + repo = seafile_api.get_repo(repo_id) + if repo: + if d['uploaded_to'] == '/': + # current upload path is '/' + folder_path = '/' + foldername + link = reverse('lib_view', args=[repo_id, repo.name, '']) + name = repo.name + else: + uploaded_to = d['uploaded_to'].rstrip('/') + folder_path = uploaded_to + '/' + foldername + link = reverse('lib_view', args=[repo_id, repo.name, uploaded_to.lstrip('/')]) + name = os.path.basename(uploaded_to) + + folder_link = reverse('lib_view', args=[repo_id, repo.name, folder_path]) + + msg = _("A folder named %(folder_name)s is uploaded to %(name)s") % { + 'folder_link': folder_link, + 'folder_name': escape(foldername), + 'link': link, + 'name': escape(name), + } + else: + msg = _("A folder named %(folder_name)s is uploaded to Deleted Library") % { + 'folder_name': escape(foldername), + } + + return msg + def format_repo_share_msg(self): """ @@ -810,7 +881,8 @@ class UserNotification(models.Model): ########## handle signals from django.dispatch import receiver -from seahub.signals import upload_file_successful, comment_file_successful, repo_transfer +from seahub.signals import upload_file_successful, upload_folder_successful,\ + comment_file_successful, repo_transfer from seahub.group.signals import group_join_request, add_user_to_group from seahub.share.signals import share_repo_to_user_successful, \ share_repo_to_group_successful, change_repo_perm_successful, delete_repo_perm_successful @@ -836,6 +908,23 @@ def add_upload_file_msg_cb(sender, **kwargs): detail = file_uploaded_msg_to_json(filename, repo_id, folder_path) UserNotification.objects.add_file_uploaded_msg(owner, detail) + +@receiver(upload_folder_successful) +def add_upload_folder_msg_cb(sender, **kwargs): + """Notify repo owner when others upload folder to his/her folder from shared link. + """ + repo_id = kwargs.get('repo_id', None) + folder_path = kwargs.get('folder_path', None) + owner = kwargs.get('owner', None) + + assert repo_id and folder_path and owner is not None, 'Arguments error' + + folder_name = os.path.basename(folder_path.rstrip('/')) + parent_dir = os.path.dirname(folder_path.rstrip('/')) + detail = folder_uploaded_msg_to_json(folder_name, repo_id, parent_dir) + UserNotification.objects.add_folder_uploaded_msg(owner, detail) + + @receiver(share_repo_to_user_successful) def add_share_repo_msg_cb(sender, **kwargs): """Notify user when others share repos to him/her. diff --git a/seahub/notifications/templates/notifications/notice_email.html b/seahub/notifications/templates/notifications/notice_email.html index 62907be996..dacbeb3b1e 100644 --- a/seahub/notifications/templates/notifications/notice_email.html +++ b/seahub/notifications/templates/notifications/notice_email.html @@ -50,6 +50,9 @@ You've got {{num}} new notices on {{ site_name }}: {% elif notice.is_file_uploaded_msg %}
{% blocktrans with file_url=notice.file_link file_name=notice.file_name folder_url=notice.folder_link folder_name=notice.folder_name %}A file named {{file_name}} is uploaded to your folder {{folder_name}}.{% endblocktrans %}
+ {% elif notice.is_folder_uploaded_msg %} +{% blocktrans with folder_url=notice.folder_link folder_name=notice.folder_name parent_dir_url=notice.parent_dir_link parent_dir_name=notice.parent_dir_name %}A folder named {{folder_name}} is uploaded to your folder {{parent_dir_name}}.{% endblocktrans %}
+ {% elif notice.is_group_join_request %}{% blocktrans with user_url=notice.grpjoin_user_profile_url user=notice.notice_from grp_url=notice.grpjoin_group_url grp_name=notice.grpjoin_group_name msg=notice.grpjoin_request_msg %}User {{user}} has asked to join group {{grp_name}}, verification message: {{msg}}{% endblocktrans %}
diff --git a/seahub/notifications/utils.py b/seahub/notifications/utils.py index 9c9cf183f8..a18974b3e7 100644 --- a/seahub/notifications/utils.py +++ b/seahub/notifications/utils.py @@ -144,7 +144,6 @@ def update_notice_detail(request, notices): except Exception as e: logger.error(e) - elif notice.is_repo_share_to_group_msg(): try: d = json.loads(notice.detail) @@ -285,6 +284,41 @@ def update_notice_detail(request, notices): except Exception as e: logger.error(e) + elif notice.is_folder_uploaded_msg(): + try: + d = json.loads(notice.detail) + foldername = d['folder_name'] + repo_id = d['repo_id'] + + if repo_id in repo_dict: + repo = repo_dict[repo_id] + else: + repo = seafile_api.get_repo(repo_id) + repo_dict[repo_id] = repo + + if repo: + if d['uploaded_to'] == '/': + # current upload path is '/' + folder_path = '/' + foldername + name = repo.name + else: + uploaded_to = d['uploaded_to'].rstrip('/') + folder_path = uploaded_to + '/' + foldername + name = os.path.basename(uploaded_to) + + d['repo_name'] = repo.name + d['parent_dir_path'] = d.pop('uploaded_to') + d['parent_dir_name'] = name + d['folder_path'] = folder_path + url, is_default, date_uploaded = api_avatar_url('', 32) + d['uploaded_user_avatar_url'] = url + notice.detail = d + else: + notice.detail = None + + except Exception as e: + logger.error(e) + elif notice.is_file_comment_msg(): try: d = json.loads(notice.detail) diff --git a/seahub/signals.py b/seahub/signals.py index 318f831735..cb367b5cbd 100644 --- a/seahub/signals.py +++ b/seahub/signals.py @@ -8,5 +8,6 @@ repo_transfer = Signal() clean_up_repo_trash = Signal() repo_restored = Signal() upload_file_successful = Signal() +upload_folder_successful = Signal() comment_file_successful = Signal() institution_deleted = Signal()