+ >
)}
{context.checkCanDeleteTag() && (
-
+
)}
@@ -77,6 +134,17 @@ const Tag = ({ tags, tag, context, onChangeDisplayTag }) => {
{isShowDeleteDialog && (
)}
+ {editingColumnKey && (
+
+ )}
>
);
};
diff --git a/frontend/src/tag/views/all-tags/main/tag/tag-more-operation.js b/frontend/src/tag/views/all-tags/main/tag/tag-more-operation.js
new file mode 100644
index 0000000000..d47ce38329
--- /dev/null
+++ b/frontend/src/tag/views/all-tags/main/tag/tag-more-operation.js
@@ -0,0 +1,62 @@
+import React, { useCallback, useMemo } from 'react';
+import PropTypes from 'prop-types';
+import ItemDropdownMenu from '../../../../../components/dropdown-menu/item-dropdown-menu';
+import { gettext } from '../../../../../utils/constants';
+import { isMobile } from '../../../../../utils/utils';
+
+const KEY_MORE_OPERATION = {
+ SET_PARENT_TAGS: 'set_parent_tags',
+ SET_SUB_TAGS: 'set_sub_tags',
+};
+
+const TagMoreOperation = ({ freezeItem, unfreezeItem, showParentTagsSetter, showSubTagsSetter }) => {
+
+ const operationMenus = useMemo(() => {
+ let menus = [];
+ menus.push(
+ { key: KEY_MORE_OPERATION.SET_PARENT_TAGS, value: gettext('Set parent tags') },
+ { key: KEY_MORE_OPERATION.SET_SUB_TAGS, value: gettext('Set sub tags') },
+ );
+ return menus;
+ }, []);
+
+ const clickMenu = useCallback((key) => {
+ switch (key) {
+ case KEY_MORE_OPERATION.SET_PARENT_TAGS: {
+ showParentTagsSetter();
+ return;
+ }
+ case KEY_MORE_OPERATION.SET_SUB_TAGS: {
+ showSubTagsSetter();
+ return;
+ }
+ default: {
+ return;
+ }
+ }
+ }, [showParentTagsSetter, showSubTagsSetter]);
+
+ return (
+
+ operationMenus}
+ onMenuItemClick={clickMenu}
+ menuStyle={isMobile ? { zIndex: 1050 } : {}}
+ />
+
+ );
+};
+
+TagMoreOperation.propTypes = {
+ freezeItem: PropTypes.func,
+ unfreezeItem: PropTypes.func,
+ showParentTagsSetter: PropTypes.func,
+ showSubTagsSetter: PropTypes.func,
+};
+
+export default TagMoreOperation;
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py
index 4bed850be3..90a9e0081a 100644
--- a/seahub/repo_metadata/apis.py
+++ b/seahub/repo_metadata/apis.py
@@ -16,7 +16,7 @@ from seahub.views import check_folder_permission
from seahub.repo_metadata.utils import add_init_metadata_task, gen_unique_id, init_metadata, \
get_unmodifiable_columns, can_read_metadata, init_faces, \
extract_file_details, get_someone_similar_faces, remove_faces_table, FACES_SAVE_PATH, \
- init_tags, remove_tags_table, add_init_face_recognition_task, init_ocr, remove_ocr_column
+ init_tags, init_tag_self_link_columns, remove_tags_table, add_init_face_recognition_task, init_ocr, remove_ocr_column
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.utils.repo import is_repo_admin
@@ -1936,6 +1936,225 @@ class MetadataTags(APIView):
return Response({'success': True})
+class MetadataTagsLinks(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def post(self, request, repo_id):
+ link_column_key = request.data.get('link_column_key')
+ row_id_map = request.data.get('row_id_map')
+
+ if not link_column_key:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'link_column_key invalid')
+
+ if not row_id_map:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'row_id_map invalid')
+
+ metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not metadata or not metadata.enabled:
+ error_msg = f'The metadata module is disabled for repo {repo_id}.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ if not can_read_metadata(request, repo_id):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
+
+ try:
+ metadata = metadata_server_api.get_metadata()
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+ tables = metadata.get('tables', [])
+ tags_table_id = [table['id'] for table in tables if table['name'] == TAGS_TABLE.name]
+ tags_table_id = tags_table_id[0] if tags_table_id else None
+ if not tags_table_id:
+ return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
+
+ try:
+ columns_data = metadata_server_api.list_columns(tags_table_id)
+ columns = columns_data.get('columns', [])
+
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ link_column = [column for column in columns if column['key'] == link_column_key and column['type'] == 'link']
+ link_column = link_column[0] if link_column else None
+ if not link_column:
+ # init self link columns
+ if link_column_key == TAGS_TABLE.columns.parent_links.key or link_column_key == TAGS_TABLE.columns.sub_links.key:
+ try:
+ init_tag_self_link_columns(metadata_server_api, tags_table_id)
+ link_id = TAGS_TABLE.self_link_id;
+ is_linked_back = link_column_key == TAGS_TABLE.columns.sub_links.key if True else False
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ else:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'link column %s not found' % link_column_key)
+ else:
+ link_column_data = link_column.get('data', {})
+ link_id = link_column_data.get('link_id', '')
+ is_linked_back = link_column_data.get('is_linked_back', False)
+
+ if not link_id:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'invalid link column')
+
+ try:
+ metadata_server_api.insert_link(repo_id, link_id, tags_table_id, row_id_map, is_linked_back)
+ except Exception as e:
+ logger.exception(e)
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
+
+ return Response({'success': True})
+
+ def put(self, request, repo_id):
+ link_column_key = request.data.get('link_column_key')
+ row_id_map = request.data.get('row_id_map')
+
+ if not row_id_map:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'row_id_map invalid')
+
+ metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not metadata or not metadata.enabled:
+ error_msg = f'The metadata module is disabled for repo {repo_id}.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ if not can_read_metadata(request, repo_id):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
+
+ try:
+ metadata = metadata_server_api.get_metadata()
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+ tables = metadata.get('tables', [])
+ tags_table_id = [table['id'] for table in tables if table['name'] == TAGS_TABLE.name]
+ tags_table_id = tags_table_id[0] if tags_table_id else None
+ if not tags_table_id:
+ return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
+
+ try:
+ columns_data = metadata_server_api.list_columns(tags_table_id)
+ columns = columns_data.get('columns', [])
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ link_column = [column for column in columns if column['key'] == link_column_key and column['type'] == 'link']
+ link_column = link_column[0] if link_column else None
+ if not link_column:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'link column %s not found' % link_column_key)
+
+ link_column_data = link_column.get('data', {})
+ link_id = link_column_data.get('link_id', '')
+ is_linked_back = link_column_data.get('is_linked_back', False)
+
+ if not link_id:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'invalid link column')
+
+ try:
+ metadata_server_api.update_link(repo_id, link_id, tags_table_id, row_id_map, is_linked_back)
+ except Exception as e:
+ logger.exception(e)
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
+
+ return Response({'success': True})
+
+ def delete(self, request, repo_id):
+ link_column_key = request.data.get('link_column_key')
+ row_id_map = request.data.get('row_id_map')
+
+ if not link_column_key:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'link_id invalid')
+
+ if not row_id_map:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'row_id_map invalid')
+
+ metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not metadata or not metadata.enabled:
+ error_msg = f'The metadata module is disabled for repo {repo_id}.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ if not can_read_metadata(request, repo_id):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
+
+ try:
+ metadata = metadata_server_api.get_metadata()
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+ tables = metadata.get('tables', [])
+ tags_table_id = [table['id'] for table in tables if table['name'] == TAGS_TABLE.name]
+ tags_table_id = tags_table_id[0] if tags_table_id else None
+ if not tags_table_id:
+ return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
+
+ try:
+ columns_data = metadata_server_api.list_columns(tags_table_id)
+ columns = columns_data.get('columns', [])
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ link_column = [column for column in columns if column['key'] == link_column_key and column['type'] == 'link']
+ link_column = link_column[0] if link_column else None
+ if not link_column:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'link column %s not found' % link_column_key)
+
+ link_column_data = link_column.get('data', {})
+ link_id = link_column_data.get('link_id', '')
+ is_linked_back = link_column_data.get('is_linked_back', False)
+
+ if not link_id:
+ return api_error(status.HTTP_400_BAD_REQUEST, 'invalid link column')
+
+ try:
+ metadata_server_api.delete_link(repo_id, link_id, tags_table_id, row_id_map, is_linked_back)
+ except Exception as e:
+ logger.exception(e)
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
+
+ return Response({'success': True})
+
+
class MetadataFileTags(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
@@ -2013,9 +2232,9 @@ class MetadataFileTags(APIView):
try:
if not current_tags:
- metadata_server_api.insert_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
+ metadata_server_api.insert_link(repo_id, TAGS_TABLE.file_link_id, METADATA_TABLE.id, { record_id: tags })
else:
- metadata_server_api.update_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
+ metadata_server_api.update_link(repo_id, TAGS_TABLE.file_link_id, METADATA_TABLE.id, { record_id: tags })
success_records.append(record_id)
except Exception as e:
failed_records.append(record_id)
diff --git a/seahub/repo_metadata/metadata_server_api.py b/seahub/repo_metadata/metadata_server_api.py
index b31dc4c9fe..4c4ab39089 100644
--- a/seahub/repo_metadata/metadata_server_api.py
+++ b/seahub/repo_metadata/metadata_server_api.py
@@ -169,6 +169,18 @@ class MetadataServerAPI:
response = requests.post(url, json=data, headers=self.headers, timeout=self.timeout)
return parse_response(response)
+ def add_link_columns(self, link_id, table_id, other_table_id, table_column, other_table_column):
+ url = f'{METADATA_SERVER_URL}/api/v1/base/{self.base_id}/link-columns'
+ data = {
+ 'link_id': link_id,
+ 'table_id': table_id,
+ 'other_table_id': other_table_id,
+ 'table_column': table_column,
+ 'other_table_column': other_table_column,
+ }
+ response = requests.post(url, json=data, headers=self.headers, timeout=self.timeout)
+ return parse_response(response)
+
def delete_column(self, table_id, column_key, permanently=False):
url = f'{METADATA_SERVER_URL}/api/v1/base/{self.base_id}/columns'
data = {
@@ -211,22 +223,35 @@ class MetadataServerAPI:
# link
- def insert_link(self, base_id, link_id, table_id, row_id_map):
+ def insert_link(self, base_id, link_id, table_id, row_id_map, is_linked_back=False):
url = f'{METADATA_SERVER_URL}/api/v1/base/{base_id}/links'
data = {
'link_id': link_id,
'table_id': table_id,
- 'row_id_map': row_id_map
+ 'is_linked_back': is_linked_back,
+ 'row_id_map': row_id_map,
}
response = requests.post(url, json=data, headers=self.headers, timeout=self.timeout)
return parse_response(response)
- def update_link(self, base_id, link_id, table_id, row_id_map):
+ def update_link(self, base_id, link_id, table_id, row_id_map, is_linked_back=False):
url = f'{METADATA_SERVER_URL}/api/v1/base/{base_id}/links'
data = {
'link_id': link_id,
'table_id': table_id,
+ 'is_linked_back': is_linked_back,
'row_id_map': row_id_map
}
response = requests.put(url, json=data, headers=self.headers, timeout=self.timeout)
return parse_response(response)
+
+ def delete_link(self, base_id, link_id, table_id, row_id_map, is_linked_back=False):
+ url = f'{METADATA_SERVER_URL}/api/v1/base/{base_id}/links'
+ data = {
+ 'link_id': link_id,
+ 'table_id': table_id,
+ 'is_linked_back': is_linked_back,
+ 'row_id_map': row_id_map
+ }
+ response = requests.delete(url, json=data, headers=self.headers, timeout=self.timeout)
+ return parse_response(response)
diff --git a/seahub/repo_metadata/urls.py b/seahub/repo_metadata/urls.py
index 64d8b949c7..e96c7d5a06 100644
--- a/seahub/repo_metadata/urls.py
+++ b/seahub/repo_metadata/urls.py
@@ -2,7 +2,7 @@ from django.urls import re_path
from .apis import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
MetadataFolders, MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
FaceRecognitionManage, FacesRecord, MetadataExtractFileDetails, PeoplePhotos, MetadataTagsStatusManage, MetadataTags, \
- MetadataFileTags, MetadataTagFiles, MetadataDetailsSettingsView, MetadataOCRManageView
+ MetadataTagsLinks, MetadataFileTags, MetadataTagFiles, MetadataDetailsSettingsView, MetadataOCRManageView
urlpatterns = [
re_path(r'^$', MetadataManage.as_view(), name='api-v2.1-metadata'),
@@ -34,6 +34,7 @@ urlpatterns = [
# tags api
re_path(r'^tags-status/$', MetadataTagsStatusManage.as_view(), name='api-v2.1-metadata-tags-status'),
re_path(r'^tags/$', MetadataTags.as_view(), name='api-v2.1-metadata-tags'),
+ re_path(r'^tags-links/$', MetadataTagsLinks.as_view(), name='api-v2.1-metadata-tags-links'),
re_path(r'^file-tags/$', MetadataFileTags.as_view(), name='api-v2.1-metadata-file-tags'),
re_path(r'^tag-files/(?P
.+)/$', MetadataTagFiles.as_view(), name='api-v2.1-metadata-tag-files'),
]
diff --git a/seahub/repo_metadata/utils.py b/seahub/repo_metadata/utils.py
index e14f189ddd..2f90f63e4c 100644
--- a/seahub/repo_metadata/utils.py
+++ b/seahub/repo_metadata/utils.py
@@ -177,52 +177,71 @@ def remove_faces_table(metadata_server_api):
# tag
-def get_tag_link_column(table_id):
- from seafevents.repo_metadata.constants import METADATA_TABLE, TAGS_TABLE
- columns = [
- METADATA_TABLE.columns.tags.to_dict({
- 'link_id': TAGS_TABLE.link_id,
- 'table_id': METADATA_TABLE.id,
- 'other_table_id': table_id,
- 'display_column_key': TAGS_TABLE.columns.name.key,
- }),
- ]
-
- return columns
-
-
def get_tag_columns(table_id):
- from seafevents.repo_metadata.constants import METADATA_TABLE, TAGS_TABLE
+ from seafevents.repo_metadata.constants import TAGS_TABLE
columns = [
TAGS_TABLE.columns.name.to_dict(),
TAGS_TABLE.columns.color.to_dict(),
- TAGS_TABLE.columns.file_links.to_dict({
- 'link_id': TAGS_TABLE.link_id,
- 'table_id': METADATA_TABLE.id,
- 'other_table_id': table_id,
- 'display_column_key': METADATA_TABLE.columns.id.key,
- }),
]
return columns
-def init_tags(metadata_server_api):
+def init_tag_file_links_column(metadata_server_api, tag_table_id):
from seafevents.repo_metadata.constants import METADATA_TABLE, TAGS_TABLE
+ file_link_id = TAGS_TABLE.file_link_id
+ table_id = METADATA_TABLE.id
+ other_table_id = tag_table_id
+ table_column = {
+ 'key': METADATA_TABLE.columns.tags.key,
+ 'name': METADATA_TABLE.columns.tags.name,
+ 'display_column_key': TAGS_TABLE.columns.name.name,
+ }
+ other_table_column = {
+ 'key': TAGS_TABLE.columns.file_links.key,
+ 'name': TAGS_TABLE.columns.file_links.name,
+ 'display_column_key': TAGS_TABLE.columns.id.key,
+ }
+ metadata_server_api.add_link_columns(file_link_id, table_id, other_table_id, table_column, other_table_column)
+
+def init_tag_self_link_columns(metadata_server_api, tag_table_id):
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+ link_id = TAGS_TABLE.self_link_id
+ table_id = tag_table_id
+ other_table_id = tag_table_id
+
+ # as parent tags which is_linked_back is false
+ table_column = {
+ 'key': TAGS_TABLE.columns.parent_links.key,
+ 'name': TAGS_TABLE.columns.parent_links.name,
+ 'display_column_key': TAGS_TABLE.columns.id.key,
+ }
+
+ # as sub tags which is_linked_back is true
+ other_table_column = {
+ 'key': TAGS_TABLE.columns.sub_links.key,
+ 'name': TAGS_TABLE.columns.sub_links.name,
+ 'display_column_key': TAGS_TABLE.columns.id.key,
+ }
+ metadata_server_api.add_link_columns(link_id, table_id, other_table_id, table_column, other_table_column)
+
+
+def init_tags(metadata_server_api):
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+
remove_tags_table(metadata_server_api)
resp = metadata_server_api.create_table(TAGS_TABLE.name)
table_id = resp['id']
- # init link column
- link_column = get_tag_link_column(table_id)
- metadata_server_api.add_columns(METADATA_TABLE.id, link_column)
-
# init columns
tag_columns = get_tag_columns(table_id)
metadata_server_api.add_columns(table_id, tag_columns)
+ # init link columns
+ init_tag_file_links_column(metadata_server_api, table_id)
+ init_tag_self_link_columns(metadata_server_api, table_id)
def remove_tags_table(metadata_server_api):
from seafevents.repo_metadata.constants import METADATA_TABLE, TAGS_TABLE