- {enableMetadata ?
+ {enableMetadata && !tagsMigrated ?
(
<>
{gettext('Tips: There are tags of old version. Please migrate tags to new version.')}
diff --git a/frontend/src/metadata/components/dialog/metadata-tags-status-dialog/index.js b/frontend/src/metadata/components/dialog/metadata-tags-status-dialog/index.js
index 1c058fc9b3..af02775952 100644
--- a/frontend/src/metadata/components/dialog/metadata-tags-status-dialog/index.js
+++ b/frontend/src/metadata/components/dialog/metadata-tags-status-dialog/index.js
@@ -19,10 +19,11 @@ const langOptions = [
}
];
-const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, toggleDialog: toggle, submit, enableMetadata, showMigrateTip }) => {
+const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, toggleDialog: toggle, submit, enableMetadata, showMigrateTip, usedRepoTags, onMigrateSuccess }) => {
const [value, setValue] = useState(oldValue);
const [lang, setLang] = useState(oldLang);
const [submitting, setSubmitting] = useState(false);
+ const [migrated, setMigrated] = useState(false);
const [showTurnOffConfirmDialog, setShowTurnOffConfirmDialog] = useState(false);
const onToggle = useCallback(() => {
@@ -46,8 +47,16 @@ const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, togg
}, [lang, repoID, submit, toggle, value]);
const migrateTag = useCallback(() => {
- // TODO backend migrate old tags
- }, []);
+ tagsAPI.migrateTags(repoID, usedRepoTags).then(res => {
+ setMigrated(true);
+ toaster.success(gettext('Tags migrated successfully'));
+ onMigrateSuccess && onMigrateSuccess();
+ }).catch(error => {
+ const errorMsg = Utils.getErrorMsg(error);
+ toaster.danger(errorMsg);
+ setMigrated(false);
+ });
+ }, [repoID, usedRepoTags, onMigrateSuccess]);
const turnOffConfirmToggle = useCallback(() => {
setShowTurnOffConfirmDialog(!showTurnOffConfirmDialog);
@@ -110,7 +119,13 @@ const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, togg
{showMigrateTip &&
{gettext('This library contains tags of old version. Do you like to migrate the tags to new version?')}
-
+
}
@@ -136,6 +151,8 @@ MetadataTagsStatusDialog.propTypes = {
submit: PropTypes.func.isRequired,
enableMetadata: PropTypes.bool.isRequired,
showMigrateTip: PropTypes.bool,
+ repoTags: PropTypes.array,
+ onMigrateSuccess: PropTypes.func,
};
export default MetadataTagsStatusDialog;
diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js
index 39087421b2..3d0c133498 100644
--- a/frontend/src/pages/lib-content-view/lib-content-view.js
+++ b/frontend/src/pages/lib-content-view/lib-content-view.js
@@ -2350,6 +2350,7 @@ class LibContentView extends React.Component {
if (!currentDirent && currentMode !== METADATA_MODE && currentMode !== TAGS_MODE) {
detailPath = Utils.getDirName(this.state.path);
}
+
const detailDirent = currentDirent || currentNode?.object || null;
return (
diff --git a/frontend/src/tag/api.js b/frontend/src/tag/api.js
index 1652c05d39..8e29f701cb 100644
--- a/frontend/src/tag/api.js
+++ b/frontend/src/tag/api.js
@@ -138,6 +138,14 @@ class TagsManagerAPI {
return this.req.post(url, params);
};
+ migrateTags = (repoID, usedRepoTags) => {
+ const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/migrate-tags/';
+ const params = {
+ tag_ids: usedRepoTags.map(tag => tag.id),
+ };
+ return this.req.post(url, params);
+ };
+
}
const tagsAPI = new TagsManagerAPI();
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py
index b14fdcb40a..18a73fa77a 100644
--- a/seahub/repo_metadata/apis.py
+++ b/seahub/repo_metadata/apis.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import posixpath
from datetime import datetime
from rest_framework.authentication import SessionAuthentication
@@ -17,11 +18,12 @@ from seahub.repo_metadata.utils import add_init_metadata_task, recognize_faces,
get_unmodifiable_columns, can_read_metadata, init_faces, \
extract_file_details, get_table_by_name, remove_faces_table, FACES_SAVE_PATH, \
init_tags, init_tag_self_link_columns, remove_tags_table, add_init_face_recognition_task, init_ocr, \
- remove_ocr_column, get_update_record, update_people_cover_photo
-from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
+ remove_ocr_column, get_update_record, update_people_cover_photo, gen_unique_tag_name
+from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records, list_eligible_metadata_records
from seahub.utils.repo import is_repo_admin
from seaserv import seafile_api
from seahub.repo_metadata.constants import FACE_RECOGNITION_VIEW_ID, METADATA_RECORD_UPDATE_LIMIT
+from seahub.file_tags.models import FileTags
logger = logging.getLogger(__name__)
@@ -2780,3 +2782,174 @@ class PeopleCoverPhoto(APIView):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
+
+
+class MetadataMigrateTags(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def post(self, request, repo_id):
+ tag_ids = request.data.get('tag_ids')
+ if not tag_ids:
+ error_msg = 'tag_ids invalid'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+ metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not metadata or not metadata.enabled or not metadata.tags_enabled:
+ error_msg = f'The tags is disabled for repo {repo_id}.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # resource check
+ 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 is_repo_admin(request.user.username, repo_id):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ # get all records
+ from seafevents.repo_metadata.constants import METADATA_TABLE
+ from seafevents.repo_metadata.constants import TAGS_TABLE
+
+ try:
+ basic_filters = [{'column_key': METADATA_TABLE.columns.is_dir.name, 'filter_predicate': 'is', 'filter_term': 'file'}]
+ view = {
+ 'filters': [],
+ 'basic_filters': basic_filters
+ }
+ filter_columns = [METADATA_TABLE.columns.id.name, METADATA_TABLE.columns.file_name.name, METADATA_TABLE.columns.parent_dir.name]
+ results = list_eligible_metadata_records(repo_id, request.user.username, view, filter_columns)
+ records = results.get('results')
+ except Exception as e:
+ logger.exception(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ # 每个record对应的文件路径
+ records_dict = {} # {file_path: record_id}
+ for record in records:
+ file_path = posixpath.join(record.get(METADATA_TABLE.columns.parent_dir.name), record.get(METADATA_TABLE.columns.file_name.name))
+ records_dict[file_path] = record.get(METADATA_TABLE.columns.id.name)
+
+ # list old tag info
+ old_repo_tag_info = {} # {repo_tag_id: {name: '', color: '', file_paths: [file_path1, file_path2]}}
+ try:
+ tagged_file_objs = FileTags.objects.filter(
+ repo_tag__id__in=tag_ids).select_related('repo_tag', 'file_uuid')
+ for tagged_file_obj in tagged_file_objs:
+ repo_tag_id = tagged_file_obj.repo_tag.id
+ parent_path = tagged_file_obj.file_uuid.parent_path
+ filename = tagged_file_obj.file_uuid.filename
+ file_path = posixpath.join(parent_path, filename)
+ if repo_tag_id not in old_repo_tag_info:
+ old_repo_tag_info[repo_tag_id] = {
+ 'name': tagged_file_obj.repo_tag.name,
+ 'color': tagged_file_obj.repo_tag.color,
+ 'file_paths': []
+ }
+ old_repo_tag_info[repo_tag_id]['file_paths'].append(file_path)
+ except Exception as err:
+ logger.error(err)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ print(old_repo_tag_info, '---old_repo_tag_info')
+
+ # file_path to record_id
+ repo_tag_id_map = {} # {repo_tag_id: [record_id1, record_id2]}
+ tags_data = [] # [{name: '', color: ''}]
+ for repo_tag_id, tag_data in old_repo_tag_info.items():
+ repo_tag_id_map[repo_tag_id] = []
+ for file_path in tag_data['file_paths']:
+ record_id = records_dict.get(file_path, '')
+ if record_id:
+ repo_tag_id_map[repo_tag_id].append(record_id)
+ tags_data.append({
+ TAGS_TABLE.columns.name.name: tag_data['name'],
+ TAGS_TABLE.columns.color.name: tag_data['color'],
+ })
+
+ # {"tags_data":[{"_tag_color":"#46A1FD","_tag_name":"va"}]}
+ metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
+ try:
+ tags_table = get_table_by_name(metadata_server_api, TAGS_TABLE.name)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ if not tags_table:
+ return api_error(status.HTTP_404_NOT_FOUND, 'tags table not found')
+
+ tags_table_id = tags_table['id']
+ exist_tags = []
+ new_tags = []
+ sql = f'SELECT `{TAGS_TABLE.columns.name.name}` FROM {TAGS_TABLE.name}'
+
+ try:
+ exist_rows = metadata_server_api.query_rows(sql)
+ exist_tags = exist_rows.get('results', [])
+ if exist_tags:
+ exist_tags = [tag_data.get(TAGS_TABLE.columns.name.name, '') for tag_data in exist_tags]
+ except Exception as e:
+ logger.exception(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ # handle exist tags
+ if exist_tags:
+ for index, tag_data in enumerate(tags_data):
+ tag_name = tag_data.get(TAGS_TABLE.columns.name.name, '')
+ if tag_name not in exist_tags:
+ new_tags.append(tag_data)
+ else:
+ unique_tag_name = gen_unique_tag_name(tag_name, exist_tags)
+ new_tags.append({
+ TAGS_TABLE.columns.color.name: tag_data.get(TAGS_TABLE.columns.color.name, ''),
+ TAGS_TABLE.columns.name.name: unique_tag_name
+ })
+ else:
+ new_tags = tags_data
+
+ try:
+ resp = metadata_server_api.insert_rows(tags_table_id, new_tags)
+ row_ids = resp.get('row_ids', [])
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ # record_id : tags
+ # handle file_tags_data
+ new_tags_info_array = []
+ for index, old_tag_id in enumerate(repo_tag_id_map):
+ new_tag_id = row_ids[index]
+ record_ids = repo_tag_id_map[old_tag_id]
+ new_tags_info_array.append({
+ 'record_ids': record_ids,
+ 'tag_id': new_tag_id
+ })
+ record_id_tag_id_map = {}
+ for tag_info in new_tags_info_array:
+ record_ids = tag_info.get('record_ids', [])
+ tag_id = tag_info.get('tag_id', '')
+ for record_id in record_ids:
+ if record_id not in record_id_tag_id_map:
+ record_id_tag_id_map[record_id] = []
+ record_id_tag_id_map[record_id].append(tag_id)
+
+ try:
+ metadata_server_api.insert_link(TAGS_TABLE.file_link_id, METADATA_TABLE.id, record_id_tag_id_map)
+ record = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not record.details_settings:
+ details_settings = {}
+ else:
+ details_settings = json.loads(record.details_settings)
+ details_settings['tags_migrated'] = True
+ record.details_settings = json.dumps(details_settings)
+
+ record.save()
+ 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/repo_metadata/metadata_server_api.py b/seahub/repo_metadata/metadata_server_api.py
index 12738dd6dd..0cc5be911b 100644
--- a/seahub/repo_metadata/metadata_server_api.py
+++ b/seahub/repo_metadata/metadata_server_api.py
@@ -78,6 +78,26 @@ def list_metadata_view_records(repo_id, user, view, tags_enabled, start=0, limit
return response_results
+def list_eligible_metadata_records(repo_id, user, view, filter_columns):
+ from seafevents.repo_metadata.constants import METADATA_TABLE
+ from seafevents.repo_metadata.utils import gen_view_data_sql
+ metadata_server_api = MetadataServerAPI(repo_id, user)
+ columns = metadata_server_api.list_columns(METADATA_TABLE.id).get('columns')
+ tags_data = {'metadata': [], 'results': []}
+
+ sql = gen_view_data_sql(METADATA_TABLE, columns, view, 0, 0, {'tags_data': tags_data, 'username': user})
+ query_fields_str = ''
+ for column in columns:
+ column_name = column.get('name')
+ if column_name in filter_columns:
+ column_name_str = '`%s`, ' % column_name
+ query_fields_str += column_name_str
+ query_fields_str = query_fields_str.strip(', ')
+ sql = sql.replace('*', query_fields_str)
+ response_results = metadata_server_api.query_rows(sql, [])
+ return response_results
+
+
def parse_response(response):
if response.status_code >= 300 or response.status_code < 200:
raise ConnectionError(response.status_code, response.text)
diff --git a/seahub/repo_metadata/urls.py b/seahub/repo_metadata/urls.py
index 3c59b414ee..6ffde33f25 100644
--- a/seahub/repo_metadata/urls.py
+++ b/seahub/repo_metadata/urls.py
@@ -3,7 +3,7 @@ from .apis import MetadataRecognizeFaces, MetadataRecords, MetadataManage, Metad
MetadataFolders, MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
FaceRecognitionManage, FacesRecord, MetadataExtractFileDetails, PeoplePhotos, MetadataTagsStatusManage, MetadataTags, \
MetadataTagsLinks, MetadataFileTags, MetadataTagFiles, MetadataMergeTags, MetadataTagsFiles, MetadataDetailsSettingsView, \
- MetadataOCRManageView, PeopleCoverPhoto
+ MetadataOCRManageView, PeopleCoverPhoto, MetadataMigrateTags
urlpatterns = [
re_path(r'^$', MetadataManage.as_view(), name='api-v2.1-metadata'),
@@ -43,4 +43,5 @@ urlpatterns = [
re_path(r'^tag-files/(?P.+)/$', MetadataTagFiles.as_view(), name='api-v2.1-metadata-tag-files'),
re_path(r'^merge-tags/$', MetadataMergeTags.as_view(), name='api-v2.1-metadata-merge-tags'),
re_path(r'^tags-files/$', MetadataTagsFiles.as_view(), name='api-v2.1-metadata-tags-files'),
+ re_path(r'^migrate-tags/$', MetadataMigrateTags.as_view(), name='api-v2.1-metadata-migrate-tags'),
]
diff --git a/seahub/repo_metadata/utils.py b/seahub/repo_metadata/utils.py
index 845548c40e..6156e25d20 100644
--- a/seahub/repo_metadata/utils.py
+++ b/seahub/repo_metadata/utils.py
@@ -73,6 +73,11 @@ def gen_unique_id(id_set, length=4):
return _id
_id = generator_base64_code(length)
+def gen_unique_tag_name(tag_name, exist_tags, counter=1):
+ new_name = f'{tag_name}({counter})'
+ if new_name not in exist_tags:
+ return new_name
+ return gen_unique_tag_name(tag_name, exist_tags, counter + 1)
def get_face_columns():
from seafevents.repo_metadata.constants import FACES_TABLE