From 8e78326cfa01fa86544f69d2dc8284da1c298597 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 25 Apr 2025 21:45:30 +0900 Subject: [PATCH 001/259] fix: raise error for missing packaging W-OA-IT-147 --- modules/weko-swordserver/weko_swordserver/decorators.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/weko-swordserver/weko_swordserver/decorators.py b/modules/weko-swordserver/weko_swordserver/decorators.py index 8e0c1e461e..a33796cb69 100644 --- a/modules/weko-swordserver/weko_swordserver/decorators.py +++ b/modules/weko-swordserver/weko_swordserver/decorators.py @@ -161,6 +161,14 @@ def decorated(*args, **kwargs): f"Not accept packaging: {packaging}", ErrorType.PackagingFormatNotAcceptable ) + elif packaging is None: + current_app.logger.error( + "Packaging is required, but not contained in request headers." + ) + raise WekoSwordserverException( + "Packaging is required.", + ErrorType.PackagingFormatNotAcceptable + ) return f(*args, **kwargs) return decorated From 2ee0a98595335af41f49ef3a13e3baf84d352658 Mon Sep 17 00:00:00 2001 From: ivis-okano <140027365+ivis-okano@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:11:43 +0900 Subject: [PATCH 002/259] Update views.py --- modules/weko-workspace/weko_workspace/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/weko-workspace/weko_workspace/views.py b/modules/weko-workspace/weko_workspace/views.py index 3d7cd854bc..df21380bc0 100644 --- a/modules/weko-workspace/weko_workspace/views.py +++ b/modules/weko-workspace/weko_workspace/views.py @@ -183,10 +183,12 @@ def get_workspace_itemlist(): # "doi": None, # DOIリンク identifiers = source.get("identifier", []) + workspaceItem["doi"] = "" if identifiers: - workspaceItem["doi"] = identifiers[0].get("value", "") - else: - workspaceItem["doi"] = "" + for value in identifiers: + if value.get("value"): + workspaceItem["doi"] = current_app.config.get("OAIHARVESTER_DOI_PREFIX", "") + "/" + identifiers[0].get("value", "") + breack # "resourceType": None, # リソースタイプ resourceType = source.get("type", []) From d85f4da7a1d25b8d73c9b0f48111b46feb4addef Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Mon, 28 Apr 2025 10:15:48 +0900 Subject: [PATCH 003/259] fix: raise error when On-Behalf-Of user not found. W-OA-IT-145 --- .../weko-swordserver/weko_swordserver/utils.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index 4ef5123c1e..80e3a65c84 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -14,6 +14,7 @@ from flask import current_app, request from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm.exc import NoResultFound from invenio_accounts.models import User from invenio_oauth2server.models import Token @@ -148,16 +149,22 @@ def get_shared_id_from_on_behalf_of(on_behalf_of): # get weko user id from shibboleth user eppn shib_user = ( ShibbolethUser.query - .filter_by(shib_eppn=on_behalf_of).one_or_none() + .filter_by(shib_eppn=on_behalf_of).one() ) shared_id = shib_user.weko_uid if shib_user is not None else None + except NoResultFound as ex: + msg = "No user found by On-Behalf-Of." + current_app.logger.error(msg) + traceback.print_exc() + raise WekoSwordserverException( + msg, errorType=ErrorType.BadRequest + ) from ex except SQLAlchemyError as ex: - current_app.logger.error( - "Somthing went wrong while searching user by On-Behalf-Of.") + msg = "DB error occurred while searching user by On-Behalf-Of." + current_app.logger.error(msg) traceback.print_exc() raise WekoSwordserverException( - "An error occurred while searching user by On-Behalf-Of.", - errorType=ErrorType.ServerError + msg, errorType=ErrorType.ServerError ) from ex return shared_id From abe1af6cccb6e85a9d46ecfa098d7fba6c55a997 Mon Sep 17 00:00:00 2001 From: ivis-ashino Date: Mon, 28 Apr 2025 11:13:01 +0900 Subject: [PATCH 004/259] fix comadmin handling in index api --- .../weko-index-tree/weko_index_tree/rest.py | 20 +++++--- .../weko-index-tree/weko_index_tree/utils.py | 48 +++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/modules/weko-index-tree/weko_index_tree/rest.py b/modules/weko-index-tree/weko_index_tree/rest.py index dc4648b0e7..645235f26f 100644 --- a/modules/weko-index-tree/weko_index_tree/rest.py +++ b/modules/weko-index-tree/weko_index_tree/rest.py @@ -49,7 +49,8 @@ from weko_accounts.utils import limiter, roles_required from weko_admin.config import ( WEKO_ADMIN_PERMISSION_ROLE_SYSTEM, - WEKO_ADMIN_PERMISSION_ROLE_REPO + WEKO_ADMIN_PERMISSION_ROLE_REPO, + WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY, ) from weko_admin.models import AdminLangSettings @@ -64,7 +65,7 @@ create_index_scope, read_index_scope, update_index_scope, delete_index_scope ) from .utils import ( - check_doi_in_index, check_index_permissions, can_user_access_index, + check_doi_in_index, check_index_permissions, can_admin_access_index, is_index_locked, perform_delete_index, save_index_trees_to_redis, reset_tree ) from .schema import IndexCreateRequestSchema, IndexUpdateRequestSchema @@ -888,7 +889,7 @@ def get(self, **kwargs): raise VersionNotFoundRESTError() @require_api_auth(allow_anonymous=False) - @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO]) + @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO,WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY]) @require_oauth_scopes(create_index_scope.id) @limiter.limit('') def post(self, **kwargs): @@ -901,7 +902,7 @@ def post(self, **kwargs): raise VersionNotFoundRESTError() @require_api_auth(allow_anonymous=False) - @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO]) + @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO,WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY]) @require_oauth_scopes(update_index_scope.id) @limiter.limit('') def put(self, **kwargs): @@ -915,7 +916,7 @@ def put(self, **kwargs): raise VersionNotFoundRESTError() @require_api_auth(allow_anonymous=False) - @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO]) + @roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,WEKO_ADMIN_PERMISSION_ROLE_REPO,WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY]) @require_oauth_scopes(delete_index_scope.id) @limiter.limit('') def delete(self, **kwargs): @@ -1271,7 +1272,7 @@ def delete_v1(self, index_id, **kwargs): return make_response(jsonify({'status': 404, 'error': 'Index not found'}), 404) else: lst = {column.name: getattr(index_obj, column.name) for column in index_obj.__table__.columns} - if not can_user_access_index(lst): + if not can_admin_access_index(lst): current_app.logger.error(f"Permission denied for index: {index_id}") return make_response(jsonify({'status': 403, 'error': f'Permission denied: You do not have access to index {index_id}.'}), 403) @@ -1357,6 +1358,11 @@ def check_index_accessible(self, id): """ if not id: + if id == 0 and any(role.name == current_app.config.get("WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY") + for role in getattr(current_user, 'roles', [])): + raise PermissionError( + description=f"Permission denied: Community administrators cannot access root index" + ) return None index = self.record_class.get_index(id, with_deleted=True) @@ -1373,7 +1379,7 @@ def check_index_accessible(self, id): column.name: getattr(index, column.name) for column in index.__table__.columns } - if not can_user_access_index(lst): + if not can_admin_access_index(lst): current_app.logger.error( f"User does not have access to index: {id}" ) diff --git a/modules/weko-index-tree/weko_index_tree/utils.py b/modules/weko-index-tree/weko_index_tree/utils.py index 445a844f70..14025417ba 100644 --- a/modules/weko-index-tree/weko_index_tree/utils.py +++ b/modules/weko-index-tree/weko_index_tree/utils.py @@ -36,6 +36,7 @@ from flask_limiter.util import get_remote_address from flask_login import current_user from invenio_cache import current_cache +from invenio_communities.models import Community from invenio_db import db from invenio_i18n.ext import current_i18n from invenio_pidstore.models import PersistentIdentifier @@ -152,6 +153,37 @@ def can_user_access_index(lst): return result +def can_admin_access_index(lst): + """Check if the specified user with admin role has access to the index item. + + This function determines access permissions based on the user's admin roles. + It checks whether the user has administrative privileges directly on the index item, + or indirectly through one of its parent indexes. + + Args: + lst (dict): Dictionary representing the index item. + + Returns: + bool: True if the user has admin access to the index, False otherwise. + """ + from .api import Indexes + + result, roles = get_user_roles(is_super_role=False) + + if not result: + if check_comadmin(roles, lst.get('id')): + result = True + else: + parent_id = lst.get('parent', 0) + while parent_id and parent_id != '0': + parent = Indexes.get_index(parent_id) + if parent and check_comadmin(roles, parent.id): + result = True + break + parent_id = parent.parent if parent else None + + return result + def get_tree_json(index_list, root_id): """Get Tree Json. @@ -344,6 +376,10 @@ def reduce_index_by_role(tree, roles, groups, browsing_role=True, plst=None): lst = tree[i] if isinstance(lst, dict): + if check_comadmin(roles[1], lst.get('id')): + i += 1 + continue + contribute_role = lst.pop('contribute_role') public_state = lst.pop('public_state') public_date = lst.pop('public_date') @@ -1188,3 +1224,15 @@ def get_all_records_in_index(index_id): def create_limiter(): from .config import WEKO_INDEX_TREE_API_LIMIT_RATE_DEFAULT return Limiter(app=Flask(__name__), key_func=get_remote_address, default_limits=WEKO_INDEX_TREE_API_LIMIT_RATE_DEFAULT) + + +def check_comadmin(roles, index_id): + """Check if the user is a community admin based on roles and group_id.""" + if roles is not None and any( + role.name == current_app.config.get("WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY") + for role in current_user.roles + ): + com_list = Community.get_by_root_node_id(index_id) + if any(com.group_id and com.group_id in roles for com in com_list): + return True + return False From 8d3e482cab5325cddf0e2b48efd0e8866f497324 Mon Sep 17 00:00:00 2001 From: ivis-akagawa Date: Mon, 28 Apr 2025 17:05:06 +0900 Subject: [PATCH 005/259] =?UTF-8?q?W-OA-IT-183=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-admin/weko_admin/admin.py | 6 +++--- .../admin/sword_api_jsonld_settings.html | 6 +++--- .../weko_admin/admin/sword_api_settings.html | 6 +++--- .../translations/en/LC_MESSAGES/messages.mo | Bin 22747 -> 22793 bytes .../translations/en/LC_MESSAGES/messages.po | 12 +++++------- .../translations/ja/LC_MESSAGES/messages.mo | Bin 26024 -> 26205 bytes .../translations/ja/LC_MESSAGES/messages.po | 15 ++++++--------- .../weko_admin/translations/messages.pot | 6 +++--- 8 files changed, 23 insertions(+), 28 deletions(-) diff --git a/modules/weko-admin/weko_admin/admin.py b/modules/weko-admin/weko_admin/admin.py index 1d1b53c63f..9218fa46c3 100644 --- a/modules/weko-admin/weko_admin/admin.py +++ b/modules/weko-admin/weko_admin/admin.py @@ -1582,7 +1582,7 @@ class SwordAPIJsonldSettingsView(ModelView): "active", "creator", "registration_type", - "input_support", + "metadata_collection", "duplicate_check" ) column_searchable_list = ("registration_type_id", "client_id", "workflow_id") @@ -1609,7 +1609,7 @@ def _format_registration_type(view, context, model, name): else: return "Workflow" - def _format_input_support(view, context, model, name): + def _format_metadata_collection(view, context, model, name): if len(model.meta_data_api) > 0: return "ON" else: @@ -1626,7 +1626,7 @@ def _format_duplicate_check(view, context, model, name): "active": _format_active, "creator": _format_creator, "registration_type": _format_registration_type, - "input_support": _format_input_support, + "metadata_collection": _format_metadata_collection, "duplicate_check": _format_duplicate_check, } diff --git a/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_jsonld_settings.html b/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_jsonld_settings.html index dcbae93785..5befe4cda4 100644 --- a/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_jsonld_settings.html +++ b/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_jsonld_settings.html @@ -114,9 +114,9 @@
+ {{_("Direct_Registration")}} + {{_("WorkFlow_Registration")}}
@@ -168,7 +168,7 @@
- +
diff --git a/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_settings.html b/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_settings.html index bd50249eb9..308ba2bf51 100644 --- a/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_settings.html +++ b/modules/weko-admin/weko_admin/templates/weko_admin/admin/sword_api_settings.html @@ -71,16 +71,16 @@
+ {{_("Direct_Registration")}} + {{_("WorkFlow_Registration")}}
- +
+
From d606d8584ed3735e20421dd9625746bf49342457 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 30 Apr 2025 12:59:05 +0900 Subject: [PATCH 011/259] fix: update metadata when save bagit as is. W-OA-IT-110 --- modules/weko-search-ui/weko_search_ui/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py index 2a2bda7476..67c96814b0 100644 --- a/modules/weko-search-ui/weko_search_ui/utils.py +++ b/modules/weko-search-ui/weko_search_ui/utils.py @@ -1071,7 +1071,7 @@ def handle_save_bagit(list_record, file, data_path, filename): { "value": str(os.path.getsize( os.path.join(data_path, filename)) - ), + ) + " B", } ], "filename": filename, @@ -1082,6 +1082,7 @@ def handle_save_bagit(list_record, file, data_path, filename): }, } metadata[key] = [dataset_info] + files_info[0]["items"] = [dataset_info] def get_priority(link_data): From acef2b1b21f6457f70dfef866893d551bce5b7b5 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 30 Apr 2025 15:04:14 +0900 Subject: [PATCH 012/259] update: check type of grant info entities. W-OA-IT-113 --- modules/weko-search-ui/weko_search_ui/mapper.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 663903740b..5c936f61f7 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -1728,11 +1728,20 @@ def _resolve_link(parent, key, value): if "uri" in extracted else {} ) + list_grant = extracted.get("wk:grant", []) + if not isinstance(list_grant, list): + raise ValueError( + "Invalid json-ld format: wk:grant is not a list." + ) for grant in extracted.get("wk:grant", []): + if not isinstance(grant, dict): + continue if grant.get("jpcoar:identifier") == "HDL": system_info["cnri"] = grant.get("@id") break for grant in extracted.get("wk:grant", []): + if not isinstance(grant, dict): + continue if grant.get("jpcoar:identifier") == "DOI": system_info["doi"] = grant.get("@id") system_info["doi_ra"] = grant.get("jpcoar:identifierRegistration") From d5c033696262c099dbc3c884c31224dbfbcec103 Mon Sep 17 00:00:00 2001 From: ivis-akagawa Date: Wed, 30 Apr 2025 15:10:04 +0900 Subject: [PATCH 013/259] =?UTF-8?q?W-OA-IT-186=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/weko_workflow/workflow_list.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/weko-workflow/weko_workflow/templates/weko_workflow/workflow_list.html b/modules/weko-workflow/weko_workflow/templates/weko_workflow/workflow_list.html index f871afe290..71300ec40d 100644 --- a/modules/weko-workflow/weko_workflow/templates/weko_workflow/workflow_list.html +++ b/modules/weko-workflow/weko_workflow/templates/weko_workflow/workflow_list.html @@ -77,6 +77,7 @@ {{_('WorkFlow')}} {{_('Item Type')}} {{_('Flow')}} + {{_('Deletion Flow')}} {{_('Next Flow')}} @@ -88,6 +89,13 @@ {{workflow.flows_name}} {{_(workflow.itemtype.item_type_name.name)}} {{_(workflow.flow_define.flow_name)}} + + {% if workflow.delete_flow_define %} + {{ workflow.delete_flow_define.flow_name }} + {% else %} + None + {% endif %} + {%-if community%} Date: Wed, 30 Apr 2025 15:47:41 +0900 Subject: [PATCH 014/259] refactor: unpack system info --- .../weko-search-ui/weko_search_ui/mapper.py | 5 ++++- .../weko-search-ui/weko_search_ui/utils.py | 20 +++---------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 5c936f61f7..2170f5fc75 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -1405,8 +1405,11 @@ def _map_to_item(self, metadata, system_info): mapped_metadata = {} system_info = { **system_info, + # if new item, must not exist "id" and "uri" **({"id": str(system_info["id"])} - if isinstance(system_info.get("id"), int) else {}), + if isinstance(system_info.get("id"), (int, str)) else {}), + **({"uri": system_info["uri"]} + if isinstance(system_info.get("uri"), str) else {}), "list_file": [ filename[5:] for filename in system_info["list_file"] if filename.startswith("data/") diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py index 67c96814b0..49cb9131d2 100644 --- a/modules/weko-search-ui/weko_search_ui/utils.py +++ b/modules/weko-search-ui/weko_search_ui/utils.py @@ -911,11 +911,9 @@ def check_jsonld_import_items( mapping = json_mapping.mapping mapper = JsonLdMapper(item_type.id, mapping) if not mapper.is_valid: - current_app.logger.info( - f"Mapping is invalid for item type {item_type.item_type_name.name}." - ) raise Exception( - f"Mapping is invalid for item type {item_type.item_type_name.name}." + "Mapping is invalid for item type {}." + .format(item_type.item_type_name.name) ) with open(f"{data_path}/{json_name}", "r") as f: @@ -924,25 +922,13 @@ def check_jsonld_import_items( list_record = [ { "$schema": f"/items/jsonschema/{item_type.id}", - # if new item, must not exist "id" and "uri" - **({"id": system_info.get("id")} if "id" in system_info else {}), - **({"uri": system_info.get("uri")} if "uri" in system_info else {}), - "_id": system_info.get("_id"), "metadata": item_metadata, "item_type_name": item_type.item_type_name.name, "item_type_id": item_type.id, "publish_status": item_metadata.get("publish_status"), **({"edit_mode": item_metadata.get("edit_mode")} if "edit_mode" in item_metadata else {}), - "link_data": system_info.get("link_data", []), - "file_path": system_info.get("list_file", []), - "non_extract": system_info.get("non_extract", []), - "save_as_is": system_info.get("save_as_is", False), - "metadata_replace": system_info.get("metadata_replace", False), - "cnri": system_info.get("cnri"), - "doi_ra": system_info.get("doi_ra", ""), - "doi": system_info.get("doi", ""), - "amend_doi": system_info.get("amend_doi"), + **system_info } for item_metadata, system_info in item_metadatas ] data_path = os.path.join(data_path, "data") From 1d540da7dc215ebc52bfddd1646af17e148fa73f Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 30 Apr 2025 17:11:21 +0900 Subject: [PATCH 015/259] refactor: unify processing in sword api --- .../weko_swordserver/utils.py | 26 +-- .../weko_swordserver/views.py | 162 ++++++++---------- 2 files changed, 90 insertions(+), 98 deletions(-) diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index 80e3a65c84..073a7582e6 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -328,18 +328,20 @@ def check_import_items( ) ) - if register_type == "Workflow": - item_type_id = check_result.get("item_type_id") - # Check if workflow and item type match - if workflow.itemtype_id != item_type_id: - current_app.logger.error( - "Item type and workflow do not match. " - f"ItemType ID must be {item_type_id}, " - f"but the workflow's ItemType ID was {workflow.itemtype_id}.") - raise WekoSwordserverException( - "Item type and workflow do not match.", - errorType=ErrorType.BadRequest - ) + item_type_id = check_result.get("item_type_id") + # Check if workflow and item type match + if ( + register_type == "Workflow" + and workflow.itemtype_id != item_type_id + ): + current_app.logger.error( + "Item type and workflow do not match. " + f"ItemType ID must be {item_type_id}, " + f"but the workflow's ItemType ID was {workflow.itemtype_id}.") + raise WekoSwordserverException( + "Item type and workflow do not match.", + errorType=ErrorType.BadRequest + ) else: raise WekoSwordserverException( f"Unsupported file format: {file_format}", diff --git a/modules/weko-swordserver/weko_swordserver/views.py b/modules/weko-swordserver/weko_swordserver/views.py index adf865e671..1a13040762 100644 --- a/modules/weko-swordserver/weko_swordserver/views.py +++ b/modules/weko-swordserver/weko_swordserver/views.py @@ -256,33 +256,30 @@ def post_service_document(): on_behalf_of = request.headers.get("On-Behalf-Of") shared_id = get_shared_id_from_on_behalf_of(on_behalf_of) + client_id = request.oauth.client.client_id - if file_format == "JSON": - digest = request.headers.get("Digest") - if current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"]: - if ( - digest is None - or not digest.startswith("SHA-256=") - or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) - ): - current_app.logger.error( - "Request body and digest verification failed." - ) - raise WekoSwordserverException( - "Request body and digest verification failed.", - ErrorType.DigestMismatch - ) - - client_id = request.oauth.client.client_id - check_result = check_import_items( - file, file_format, False, shared_id, - packaging=packaging, client_id=client_id - ) + digest = request.headers.get("Digest") + if ( + file_format == "JSON" + and current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] + ): + if ( + digest is None + or not digest.startswith("SHA-256=") + or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) + ): + current_app.logger.error( + "Request body and digest verification failed." + ) + raise WekoSwordserverException( + "Request body and digest verification failed.", + ErrorType.DigestMismatch + ) - else: - check_result = check_import_items( - file, file_format, False, shared_id - ) + check_result = check_import_items( + file, file_format, shared_id=shared_id, + packaging=packaging, client_id=client_id + ) data_path = check_result.get("data_path","") expire = datetime.now() + timedelta(days=1) @@ -299,19 +296,9 @@ def post_service_document(): if not required_scopes.issubset(token_scopes): abort(403) - - # Determine registration type - if register_type is None: - if os.path.exists(data_path): - shutil.rmtree(data_path) - TempDirInfo().delete(data_path) - raise WekoSwordserverException( - "Invalid register type in admin settings", ErrorType.ServerError - ) - if check_result.get("error"): current_app.logger.error( - f"Error in check_import_items: {check_result.get('error')}" + f"Error in item to import: {check_result.get('error')}" ) raise WekoSwordserverException( f"Item check error: {check_result.get('error')}", @@ -319,12 +306,11 @@ def post_service_document(): ) # Validate items in the check result - for item in check_result["list_record"]: + for item in check_result.get("list_record", [{}]): if not item or item.get("errors"): error_msg = ( ", ".join(item.get("errors")) - if item and item.get("errors") - else "item_missing" + if item and item.get("errors") else "item_missing" ) current_app.logger.error(f"Error in check_import_items: {error_msg}") raise WekoSwordserverException( @@ -432,7 +418,7 @@ def process_item(item, request_info): TempDirInfo().delete(data_path) current_app.logger.info( - f"Items imported by sword from {request.oauth.client.name}; item: {recid}" + f"Items imported via SWORD api by {request.oauth.client.name} (recid={recid})" ) if len(warns) > 0: if register_type == "Direct": @@ -557,7 +543,6 @@ def put_object(recid): for _, value in request.files.items(): if value.filename == filename: file = value - if file is None: current_app.logger.error(f"Not found {filename} in request body.") raise WekoSwordserverException( @@ -568,32 +553,32 @@ def put_object(recid): packaging = request.headers.get("Packaging") file_format = check_import_file_format(file, packaging) - if file_format == "JSON": - digest = request.headers.get("Digest") - if current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"]: - if ( - digest is None - or not digest.startswith("SHA-256=") - or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) - ): - current_app.logger.error( - "Request body and digest verification failed." - ) - raise WekoSwordserverException( - "Request body and digest verification failed.", - ErrorType.DigestMismatch - ) + on_behalf_of = request.headers.get("On-Behalf-Of") + shared_id = get_shared_id_from_on_behalf_of(on_behalf_of) + client_id = request.oauth.client.client_id - client_id = request.oauth.client.client_id - on_behalf_of = request.headers.get("On-Behalf-Of") - shared_id = get_shared_id_from_on_behalf_of(on_behalf_of) - check_result = check_import_items( - file, file_format, packaging=packaging, - shared_id=shared_id, client_id=client_id - ) + digest = request.headers.get("Digest") + if ( + file_format == "JSON" + and current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] + ): + if ( + digest is None + or not digest.startswith("SHA-256=") + or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) + ): + current_app.logger.error( + "Request body and digest verification failed." + ) + raise WekoSwordserverException( + "Request body and digest verification failed.", + ErrorType.DigestMismatch + ) - else: - check_result = check_import_items(file, file_format, False) + check_result = check_import_items( + file, file_format, shared_id=shared_id, + packaging=packaging, client_id=client_id + ) data_path = check_result.get("data_path","") expire = datetime.now() + timedelta(days=1) @@ -601,23 +586,30 @@ def put_object(recid): data_path, {"expire": expire.strftime("%Y-%m-%d %H:%M:%S")} ) + register_type = check_result.get("register_type") - # Determine registration type - if register_type is None: - if os.path.exists(data_path): - shutil.rmtree(data_path) - TempDirInfo().delete(data_path) + if register_type == "Workflow": + # activity scope check + required_scopes = set([activity_scope.id]) + token_scopes = set(request.oauth.access_token.scopes) + if not required_scopes.issubset(token_scopes): + abort(403) + + if check_result.get("error"): + current_app.logger.error( + f"Error in check_import_items: {check_result.get('error')}" + ) raise WekoSwordserverException( - "Invalid register type in admin settings", ErrorType.ServerError + f"Item check error: {check_result.get('error')}", + ErrorType.ContentMalformed ) # only first item - item = check_result.get("list_record")[0] + item = check_result.get("list_record", [{}])[0] if not item or item.get("errors"): error_msg = ( ", ".join(item.get("errors")) - if item and item.get("errors") - else "item_missing" + if item and item.get("errors") else "item_missing" ) current_app.logger.error(f"Error in check_import_items: {error_msg}") raise WekoSwordserverException( @@ -661,34 +653,32 @@ def put_object(recid): import_result = import_items_to_system(item, request_info=request_info) if not import_result.get("success"): current_app.logger.error( - f"Item import error: {item.get('error_id')}" + f"Error in import_items_to_system: {item.get('error_id')}" ) raise WekoSwordserverException( - f"Error in import_items_to_system: {import_result.get('error_id')}", + f"Item import error:: {import_result.get('error_id')}", ErrorType.ServerError ) from weko_items_ui.utils import send_mail_direct_registered send_mail_direct_registered(recid, current_user.id) response = jsonify(_get_status_document(recid)) - elif register_type == "Workflow": - required_scopes = set([activity_scope.id]) - token_scopes = set(request.oauth.access_token.scopes) - if not required_scopes.issubset(token_scopes): - abort(403) - url, _, _, error = import_items_to_activity(item, request_info=request_info) + elif register_type == "Workflow": + url, _, _, error = import_items_to_activity( + item, request_info=request_info + ) activity_id = url.split("/")[-1] + if error and url: raise WekoSwordserverException( "Error: {error}. Please open the following URL to continue " "with the remaining operations.{url}: Item id: {recid}." .format(error=error, url=url, recid=recid), - ErrorType.ServerError + ErrorType.BadRequest ) if error: raise WekoSwordserverException( - f"Error: {error}. Please contact the administrator.", - ErrorType.ServerError + f"Unexpected error: {error}.", ErrorType.ServerError ) response = jsonify(_get_status_workflow_document(activity_id, recid)) else: @@ -705,7 +695,7 @@ def put_object(recid): TempDirInfo().delete(data_path) current_app.logger.info( - f"item imported by sword from {request.oauth.client.name} (recid={recid})" + f"Item updated via SWORD api by {request.oauth.client.name} (recid={recid})" ) return response From 31cc9e090daa96632bcf0f0dd85f235d4fb2695b Mon Sep 17 00:00:00 2001 From: ivis-ashino Date: Wed, 30 Apr 2025 17:57:23 +0900 Subject: [PATCH 016/259] fix: Change idType to string --- modules/weko-authors/weko_authors/rest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/weko-authors/weko_authors/rest.py b/modules/weko-authors/weko_authors/rest.py index bddfa0e554..f731e5e32a 100644 --- a/modules/weko-authors/weko_authors/rest.py +++ b/modules/weko-authors/weko_authors/rest.py @@ -307,13 +307,13 @@ def process_authors_data_before(self, author_data): for auth_id in author_data.get("authorIdInfo", []): prefix_obj = get_author_prefix_obj(auth_id.get("idType")) if prefix_obj: - auth_id["idType"] = prefix_obj.id + auth_id["idType"] = str(prefix_obj.id) # `affiliationIdType` (scheme -> id) for affiliation in author_data.get("affiliationInfo", []): for identifier in affiliation.get("identifierInfo", []): aff_obj = get_author_affiliation_obj(identifier.get("affiliationIdType")) if aff_obj: - identifier["affiliationIdType"] = aff_obj.id + identifier["affiliationIdType"] = str(aff_obj.id) return author_data def process_authors_data_after(self, author_data): From 9b85168e83316e28fc3bc7d76c2365904ff3aed5 Mon Sep 17 00:00:00 2001 From: Atsushi Suzuki Date: Wed, 30 Apr 2025 19:30:08 +0900 Subject: [PATCH 017/259] fix file count --- modules/weko-workspace/weko_workspace/views.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/weko-workspace/weko_workspace/views.py b/modules/weko-workspace/weko_workspace/views.py index df21380bc0..5abb6fb2cd 100644 --- a/modules/weko-workspace/weko_workspace/views.py +++ b/modules/weko-workspace/weko_workspace/views.py @@ -188,7 +188,7 @@ def get_workspace_itemlist(): for value in identifiers: if value.get("value"): workspaceItem["doi"] = current_app.config.get("OAIHARVESTER_DOI_PREFIX", "") + "/" + identifiers[0].get("value", "") - breack + break # "resourceType": None, # リソースタイプ resourceType = source.get("type", []) @@ -335,18 +335,24 @@ def get_workspace_itemlist(): accessrole_date_list = [ { "accessrole": item["accessrole"], - "dateValue": item["date"][0]["dateValue"], + "dateValue": item["date"][0]["dateValue"] if ( + "date" in item and + item["date"] and + "dateValue" in item["date"][0] + ) else None, } - for item in fileList - if "accessrole" in item and "date" in item + for item in fileList if "accessrole" in item ] for accessrole_date in accessrole_date_list: access_role = accessrole_date["accessrole"] date_val = accessrole_date["dateValue"] - if access_role == "open_access" or \ - (access_role == "open_date" and date_val <= datetime.now(timezone.utc).strftime("%Y-%m-%d")): + if access_role == "open_access" or ( + access_role == "open_date" and + date_val and + date_val <= datetime.now(timezone.utc).strftime("%Y-%m-%d") + ): # public publicCnt += 1 elif access_role == "open_restricted": From 88b7b0d1fd4c3e48ac052bd33527dab15bd2f368 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 1 May 2025 09:19:15 +0900 Subject: [PATCH 018/259] fix: check workflow deleted or not when initializing headless activity. --- .../weko_workflow/headless/activity.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/weko-workflow/weko_workflow/headless/activity.py b/modules/weko-workflow/weko_workflow/headless/activity.py index c5f3e75037..5c4a9ef186 100644 --- a/modules/weko-workflow/weko_workflow/headless/activity.py +++ b/modules/weko-workflow/weko_workflow/headless/activity.py @@ -238,7 +238,7 @@ def init_activity(self, user_id, **kwargs): # create activity for new item self.workflow = workflow = WorkFlow().get_workflow_by_id(workflow_id) - if workflow is None: + if workflow is None or workflow.is_deleted: current_app.logger.error(f"workflow(id={workflow_id}) is not found.") raise WekoWorkflowException(f"workflow(id={workflow_id}) is not found.") @@ -359,6 +359,17 @@ def _input_metadata(self, metadata, files=None, non_extract=None, workspace_regi locked_value = self._activity_lock() try: + itemtype_id = metadata.get("$schema", "").split("/")[-1] + if itemtype_id != "" and int(itemtype_id) != self.item_type.id: + msg = ( + "Itemtype of importing item;(id={}) is not matched with " + "workflow itemtype;(id={})." + .format(itemtype_id, self.item_type.id) + ) + current_app.logger.error(msg) + raise WekoWorkflowException(msg) + metadata.update({"$schema": f"/items/jsonschema/{self.item_type.id}"}) + metadata.setdefault("pubdate", datetime.now().strftime("%Y-%m-%d")) feedback_maillist = metadata.pop("feedback_mail_list", []) self.create_or_update_action_feedbackmail( @@ -440,7 +451,6 @@ def _input_metadata(self, metadata, files=None, non_extract=None, workspace_regi # update old metadata partially metadata = {**_old_metadata, **metadata} # if metadata_replace is True, replace all metadata - metadata.update({"$schema": f"/items/jsonschema/{self.item_type.id}"}) data = { "metainfo": metadata, From 80841890179ebece7b3236bb7ad3e3029c5573be Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 1 May 2025 09:32:52 +0900 Subject: [PATCH 019/259] fix: exclude deleted workflows in tsv registration. --- modules/weko-swordserver/weko_swordserver/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index 073a7582e6..0c486b14a8 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -20,8 +20,6 @@ from invenio_oauth2server.models import Token from weko_accounts.models import ShibbolethUser from weko_admin.models import AdminSettings -from weko_items_ui.utils import get_workflow_by_item_type_id -from weko_records.api import ItemTypes from weko_search_ui.config import SWORD_METADATA_FILE, ROCRATE_METADATA_FILE from weko_search_ui.utils import ( check_tsv_import_items, @@ -244,11 +242,9 @@ def check_import_items( if register_type == "Workflow": item_type_id = check_result["list_record"][0].get("item_type_id") item_type_name = check_result["list_record"][0].get("item_type_name") - item_type = ItemTypes.get_by_id(item_type_id) - workflow = get_workflow_by_item_type_id(item_type.name_id, - item_type_id) - if workflow is None: + list_workflow = WorkFlows().get_workflow_by_itemtype_id(item_type_id) + if not list_workflow: current_app.logger.error( f"Workflow not found for item type ID: {item_type_name}" ) @@ -256,6 +252,7 @@ def check_import_items( "Workflow not found for registration your item.", errorType=ErrorType.BadRequest ) + workflow = list_workflow[0] workflow_id = workflow.id check_result.update({"workflow_id": workflow_id}) From 81a96dff57a88d674af8b77affa84eeda49182e2 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 1 May 2025 09:54:33 +0900 Subject: [PATCH 020/259] fix: include schema in metadata. --- modules/weko-search-ui/weko_search_ui/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py index 49cb9131d2..1e85e732fe 100644 --- a/modules/weko-search-ui/weko_search_ui/utils.py +++ b/modules/weko-search-ui/weko_search_ui/utils.py @@ -2240,6 +2240,7 @@ def import_items_to_activity(item, request_info): workflow_id = request_info.get("workflow_id") item_id = item.get("id") metadata = item.get("metadata") + metadata["$schema"] = item.get("$schema") index = metadata.get("path") files_info = metadata.pop("files_info", [{}]) files = [ From 05575f1cfb9f7c967c5784447b8aefa2c3dc04f1 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 1 May 2025 14:17:14 +0900 Subject: [PATCH 021/259] fix: wait until the service worker is active. --- .../weko_notifications/bundles.py | 1 + .../weko_notifications/forms.py | 13 ++++++++++++- .../weko_notifications/notifications.py | 2 +- .../notifications.settings.js | 17 +++++++++++++---- .../static/js/weko_notifications/sw.js | 5 ----- .../weko_notifications/views.py | 5 +++++ 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/modules/weko-notifications/weko_notifications/bundles.py b/modules/weko-notifications/weko_notifications/bundles.py index 8acf7f8ea1..fd123b68b2 100644 --- a/modules/weko-notifications/weko_notifications/bundles.py +++ b/modules/weko-notifications/weko_notifications/bundles.py @@ -20,6 +20,7 @@ output="gen/notifications_settings.%(version)s.js" ) +# note: do not use %(version)s here, becouse the file name MUST not be changed. sw_js = Bundle( "js/weko_notifications/sw.js", output="gen/sw.js" diff --git a/modules/weko-notifications/weko_notifications/forms.py b/modules/weko-notifications/weko_notifications/forms.py index c67565e872..cd4bb41b9e 100644 --- a/modules/weko-notifications/weko_notifications/forms.py +++ b/modules/weko-notifications/weko_notifications/forms.py @@ -25,6 +25,11 @@ def handle_notifications_form(form): Args: form (flask_wtf.FlaskForm): The notifications form. """ + if not isinstance(form, NotificationsForm): + raise TypeError( + "form must be an instance of NotificationsForm, " + "not {}".format(type(form)) + ) form.process(formdata=request.form) if form.validate_on_submit(): @@ -34,6 +39,12 @@ def handle_notifications_form(form): ) form.subscribe_email.data = False return + if form.subscribe_webpush.data and not form.webpush_endpoint.data: + form.subscribe_webpush.errors.append( + _("Failed to get subscription information. Please try again.") + ) + form.subscribe_webpush.data = False + return NotificationsUserSettings.create_or_update( user_id=current_user.id, subscribe_webpush=False, # NOTE: This is not used. @@ -42,7 +53,7 @@ def handle_notifications_form(form): flash(_("Notifications settings updated."), category="success") else: flash( - _("There was an error updating your notifications settings."), + _("Failed to update your notifications settings."), category="error" ) diff --git a/modules/weko-notifications/weko_notifications/notifications.py b/modules/weko-notifications/weko_notifications/notifications.py index 78c1ad6374..9a7164114b 100644 --- a/modules/weko-notifications/weko_notifications/notifications.py +++ b/modules/weko-notifications/weko_notifications/notifications.py @@ -68,7 +68,7 @@ def __str__(self): def __eq__(self, other): """Return True if the value is equal to the current id.""" - return self.id == other.id + return isinstance(other, self.__class__) and self.id == other.id @property def id(self): diff --git a/modules/weko-notifications/weko_notifications/static/js/weko_notifications/notifications.settings.js b/modules/weko-notifications/weko_notifications/static/js/weko_notifications/notifications.settings.js index e45645c977..5c5f21712a 100644 --- a/modules/weko-notifications/weko_notifications/static/js/weko_notifications/notifications.settings.js +++ b/modules/weko-notifications/weko_notifications/static/js/weko_notifications/notifications.settings.js @@ -1,3 +1,5 @@ +const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); + function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) @@ -32,8 +34,6 @@ async function subscribePush(reg) { userVisibleOnly: true, applicationServerKey: applicationServerKey }); - console.log('Push subscription:', subscription); - console.log('Push subscription:', subscription.toJSON()); return subscription.toJSON(); } catch (e) { console.error(e); @@ -54,10 +54,8 @@ async function unsubscribePush() { const subscription = await reg.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); - console.log('Push unsubscription:', subscription); return subscription.toJSON(); } else { - console.log('No subscription found.'); return null; } } catch (e) { @@ -80,6 +78,17 @@ async function registerServiceWorker() { navigator.serviceWorker.addEventListener('controllerchange', () => { window.location.reload(); }); + + const timeout = 3000; + const startTime = Date.now(); + while (!reg || !reg.active) { + if (Date.now() - startTime > timeout) { + console.error('Service worker activation timed out.'); + return; + } + await sleep(100); + } + return reg; } catch (e) { console.error('Service worker registration failed:', e); diff --git a/modules/weko-notifications/weko_notifications/static/js/weko_notifications/sw.js b/modules/weko-notifications/weko_notifications/static/js/weko_notifications/sw.js index 4d8e398955..efcdd149a5 100644 --- a/modules/weko-notifications/weko_notifications/static/js/weko_notifications/sw.js +++ b/modules/weko-notifications/weko_notifications/static/js/weko_notifications/sw.js @@ -1,19 +1,16 @@ // Handle push event received from the backend self.addEventListener('push', function (event) { - console.log('Push event received:', event); const data = event.data.json(); // Parse the message from the server as JSON // Display the push notification using the showNotification method event.waitUntil( self.registration.showNotification(data.title, data.options) - .then(() => console.log('Notification displayed:', data.title, data.options)) .catch(err => console.error('Notification display error:', err)) ); }); // Handle notification click event self.addEventListener('notificationclick', function (event) { - console.log('Notification click event:', event); event.notification.close(); // Close the notification event.waitUntil( clients.matchAll({ type: 'window' }).then(windowClients => { @@ -31,13 +28,11 @@ self.addEventListener('notificationclick', function (event) { // Execute when Service Worker is installed self.addEventListener('install', (event) => { - console.log('Service worker install ...'); self.skipWaiting(); // Force the active service worker to switch }); // Execute when Service Worker is activated self.addEventListener('activate', (event) => { - console.log('Service worker activate ...'); event.waitUntil(self.clients.claim()); // Immediately start controlling the clients }); diff --git a/modules/weko-notifications/weko_notifications/views.py b/modules/weko-notifications/weko_notifications/views.py index 6d9b0a1f43..ece98ebec1 100644 --- a/modules/weko-notifications/weko_notifications/views.py +++ b/modules/weko-notifications/weko_notifications/views.py @@ -113,6 +113,11 @@ def notifications(): requests.post(inbox_url("/subscribe"), json=subscripsion) requests.post(inbox_url("/userprofile"), json=userprofile) else: + current_app.logger.info( + "Unsubscribing push subscription for user %s: %s", + current_user.id, + endpoint[:24] + "..." + endpoint[-8:] + ) requests.post(inbox_url("/unsubscribe"), json={"endpoint": endpoint}) except Exception as ex: traceback.print_exc() From c3796374d55f49cb2cdc59dd825f9c246411ede1 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 1 May 2025 14:18:18 +0900 Subject: [PATCH 022/259] update: translations --- .../translations/en/LC_MESSAGES/messages.mo | Bin 1819 -> 1929 bytes .../translations/en/LC_MESSAGES/messages.po | 37 +++++++------- .../translations/ja/LC_MESSAGES/messages.mo | Bin 1908 -> 2137 bytes .../translations/ja/LC_MESSAGES/messages.po | 45 +++++++++--------- .../translations/messages.pot | 34 +++++++------ 5 files changed, 60 insertions(+), 56 deletions(-) diff --git a/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.mo b/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.mo index f197947ce1c708c526c92b155cef4a77bd0f07aa..65952f2542bb522c652ed1afddfdd820da0537ae 100644 GIT binary patch delta 543 zcmb`>y-LGS7=Yo^pVSKW$D&nA(F3AR4J~RxClM`(gW{?dqD@PnrnOB6aVX+c92|m^ z7vLbcbWp)-aC7bADh|4f?}v*QVBq9A2a@l+H}PJgJ4l7E70(ze%X*f>pJzXyRGRYv zM$kh8&#;K+xQvfDgP)kgbWo`nuH!WBqQ(}=cRn(=s~*_M;1`VIE6R@p9L0B>#BWUC zL`bO&&f^`It)T>dgn#E4)Jfn7F9}Sc1a6{Se1Q^pi!oK^LN-$PhFch-#=&je!zYx$ z@uARhG%J>8`zOIE-!zJTCKC6P!IFO!{2E@$%9*lLo21Cq%SkR=rgpHY!B{!&d28kH#AOeTlyF$J zM<))#MH@LdyC~)60 z6Y4l=;FX&eyF)4Vh*Ins<-=VZO70j+u_>IxRV=ARnzLBpz^9Hhi&6gcl!p|%LnKUK61}%w49lf> i-88)+^JIT_2BX2a&gUnC#=TRuB%;ysW~_g_TmA(P|42Xp diff --git a/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.po b/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.po index 3470e98d2c..f69c136ffd 100644 --- a/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.po +++ b/modules/weko-notifications/weko_notifications/translations/en/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-notifications 1.0.0.dev20250000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-03-31 22:34+0900\n" +"POT-Creation-Date: 2025-05-01 14:04+0900\n" "PO-Revision-Date: 2025-03-16 16:28+0900\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -19,67 +19,71 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" -#: weko_notifications/forms.py:35 +#: weko_notifications/forms.py:38 msgid "Email notifications require a confirmed email address." msgstr "" #: weko_notifications/forms.py:44 +msgid "Failed to get subscription information. Please try again." +msgstr "" + +#: weko_notifications/forms.py:53 msgid "Notifications settings updated." msgstr "" -#: weko_notifications/forms.py:47 -msgid "There was an error updating your notifications settings." +#: weko_notifications/forms.py:56 +msgid "Failed to update your notifications settings." msgstr "" #. NOTE: Form field label -#: weko_notifications/forms.py:57 +#: weko_notifications/forms.py:66 msgid "Web push" msgstr "" #. NOTE: Form field help text -#: weko_notifications/forms.py:59 +#: weko_notifications/forms.py:68 msgid "Receive notifications via web push." msgstr "" "Receive web push notifications in this browser. Please refrain from using" " this feature on shared devices." -#: weko_notifications/forms.py:63 +#: weko_notifications/forms.py:72 msgid "Web push endpoint" msgstr "" -#: weko_notifications/forms.py:66 +#: weko_notifications/forms.py:75 msgid "Web push expiration time" msgstr "" -#: weko_notifications/forms.py:69 +#: weko_notifications/forms.py:78 msgid "Web push p256dh" msgstr "" -#: weko_notifications/forms.py:72 +#: weko_notifications/forms.py:81 msgid "Web push auth" msgstr "" -#: weko_notifications/forms.py:75 +#: weko_notifications/forms.py:84 msgid "Email" msgstr "" -#: weko_notifications/forms.py:76 +#: weko_notifications/forms.py:85 msgid "Receive notifications via email." msgstr "" "Receive email notifications via the verified email address in your " "Profile." -#: weko_notifications/views.py:48 +#: weko_notifications/views.py:72 #, python-format msgid "%(icon)s Notifications" msgstr "" #: weko_notifications/templates/weko_notifications/settings/notifications.html:25 -#: weko_notifications/views.py:52 +#: weko_notifications/views.py:76 msgid "Notifications" msgstr "" -#: weko_notifications/views.py:98 +#: weko_notifications/views.py:127 msgid "Failed to update push subscription." msgstr "" @@ -102,6 +106,3 @@ msgstr "" msgid "Update" msgstr "" -#~ msgid "WEKO-Notifications" -#~ msgstr "" - diff --git a/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.mo index 3d313be459e98ac8e362585b21dc5f13eba0e9b8..19072fe42d041d1ca61252090da1e9e4f6f8a105 100644 GIT binary patch delta 593 zcmZ|J%PT}t90%}o?~ECgXN*VUl%m8Kj}aCYl7&*p4vWiN=GK@y&D>c?9_Jbg_THI$oF7ja)OC`_!6eVYbYrWs*AuT*4rYDP#8P|Kw8NIW zTYO5b%^0SUcq~XG_5P+nkngJ-Y5p2t9j$9@^&NLR5=w`?oLpK>Ce{So6>L$kgkT%O z=HDe2H^ruK*Wd9;3fq+V@p+zmdH6jJzYfMPBgr$t%CINc9o_s{jv>Sty^CqA zV+t3sf=f7s$2fpjSiozP_=NiY9rc`oDMS@3$h=sD6gCI@s6)h<#Z%1VC3g6Hi$nC= z$sZ4YZR0xU=jh@m>YxrOOXy*UYj}kBc!CQ&stQMl9)j_j#^2 fE6e3MuX~rcG1POa)GenkjDP3!tfG1|zvp}bt2RW; diff --git a/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.po b/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.po index 2eb12417d1..d6518c87ff 100644 --- a/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-notifications/weko_notifications/translations/ja/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-notifications 1.0.0.dev20250000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-03-31 22:34+0900\n" +"POT-Creation-Date: 2025-05-01 14:04+0900\n" "PO-Revision-Date: 2025-03-16 16:27+0900\n" "Last-Translator: FULL NAME \n" "Language: ja\n" @@ -19,65 +19,67 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" -#: weko_notifications/forms.py:35 +#: weko_notifications/forms.py:38 msgid "Email notifications require a confirmed email address." msgstr "メール通知機能をご利用になるには、メールアドレスの確認が必要です。" #: weko_notifications/forms.py:44 +msgid "Failed to get subscription information. Please try again." +msgstr "購読情報を取得できませんでした。もう一度お試しください。" + +#: weko_notifications/forms.py:53 msgid "Notifications settings updated." msgstr "通知設定が更新されました。" -#: weko_notifications/forms.py:47 -msgid "There was an error updating your notifications settings." -msgstr "" +#: weko_notifications/forms.py:56 +msgid "Failed to update your notifications settings." +msgstr "通知設定の更新に失敗しました。" #. NOTE: Form field label -#: weko_notifications/forms.py:57 +#: weko_notifications/forms.py:66 msgid "Web push" msgstr "Webプッシュ通知" #. NOTE: Form field help text -#: weko_notifications/forms.py:59 +#: weko_notifications/forms.py:68 msgid "Receive notifications via web push." msgstr "このブラウザでプッシュ通知を受け取ります。共用のデバイスでのご利用はお控えください。" -#: weko_notifications/forms.py:63 +#: weko_notifications/forms.py:72 msgid "Web push endpoint" msgstr "" -#: weko_notifications/forms.py:66 +#: weko_notifications/forms.py:75 msgid "Web push expiration time" msgstr "" -#: weko_notifications/forms.py:69 -#, fuzzy +#: weko_notifications/forms.py:78 msgid "Web push p256dh" -msgstr "Webプッシュ通知" +msgstr "" -#: weko_notifications/forms.py:72 -#, fuzzy +#: weko_notifications/forms.py:81 msgid "Web push auth" -msgstr "Webプッシュ通知" +msgstr "" -#: weko_notifications/forms.py:75 +#: weko_notifications/forms.py:84 msgid "Email" msgstr "メール通知" -#: weko_notifications/forms.py:76 +#: weko_notifications/forms.py:85 msgid "Receive notifications via email." msgstr "プロフィールに設定された確認済みメールアドレスでメール通知を受け取ります。" -#: weko_notifications/views.py:48 +#: weko_notifications/views.py:72 #, python-format msgid "%(icon)s Notifications" msgstr "%(icon)s 通知" #: weko_notifications/templates/weko_notifications/settings/notifications.html:25 -#: weko_notifications/views.py:52 +#: weko_notifications/views.py:76 msgid "Notifications" msgstr "通知" -#: weko_notifications/views.py:98 +#: weko_notifications/views.py:127 msgid "Failed to update push subscription." msgstr "購読情報の更新に失敗しました。" @@ -98,6 +100,3 @@ msgstr "キャンセル" msgid "Update" msgstr "更新" -#~ msgid "WEKO-Notifications" -#~ msgstr "" - diff --git a/modules/weko-notifications/weko_notifications/translations/messages.pot b/modules/weko-notifications/weko_notifications/translations/messages.pot index b2293b82d6..8b3d36f2cb 100644 --- a/modules/weko-notifications/weko_notifications/translations/messages.pot +++ b/modules/weko-notifications/weko_notifications/translations/messages.pot @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-notifications 1.0.0.dev20250000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-03-31 22:34+0900\n" +"POT-Creation-Date: 2025-05-01 14:04+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,63 +18,67 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" -#: weko_notifications/forms.py:35 +#: weko_notifications/forms.py:38 msgid "Email notifications require a confirmed email address." msgstr "" #: weko_notifications/forms.py:44 +msgid "Failed to get subscription information. Please try again." +msgstr "" + +#: weko_notifications/forms.py:53 msgid "Notifications settings updated." msgstr "" -#: weko_notifications/forms.py:47 -msgid "There was an error updating your notifications settings." +#: weko_notifications/forms.py:56 +msgid "Failed to update your notifications settings." msgstr "" #. NOTE: Form field label -#: weko_notifications/forms.py:57 +#: weko_notifications/forms.py:66 msgid "Web push" msgstr "" #. NOTE: Form field help text -#: weko_notifications/forms.py:59 +#: weko_notifications/forms.py:68 msgid "Receive notifications via web push." msgstr "" -#: weko_notifications/forms.py:63 +#: weko_notifications/forms.py:72 msgid "Web push endpoint" msgstr "" -#: weko_notifications/forms.py:66 +#: weko_notifications/forms.py:75 msgid "Web push expiration time" msgstr "" -#: weko_notifications/forms.py:69 +#: weko_notifications/forms.py:78 msgid "Web push p256dh" msgstr "" -#: weko_notifications/forms.py:72 +#: weko_notifications/forms.py:81 msgid "Web push auth" msgstr "" -#: weko_notifications/forms.py:75 +#: weko_notifications/forms.py:84 msgid "Email" msgstr "" -#: weko_notifications/forms.py:76 +#: weko_notifications/forms.py:85 msgid "Receive notifications via email." msgstr "" -#: weko_notifications/views.py:48 +#: weko_notifications/views.py:72 #, python-format msgid "%(icon)s Notifications" msgstr "" #: weko_notifications/templates/weko_notifications/settings/notifications.html:25 -#: weko_notifications/views.py:52 +#: weko_notifications/views.py:76 msgid "Notifications" msgstr "" -#: weko_notifications/views.py:98 +#: weko_notifications/views.py:127 msgid "Failed to update push subscription." msgstr "" From b8084a6a4895908de79950c010e8f275b6fe8692 Mon Sep 17 00:00:00 2001 From: ivis-akagawa Date: Thu, 1 May 2025 14:36:09 +0900 Subject: [PATCH 023/259] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E8=A6=8F=E7=B4=84=E3=81=AB=E6=B2=BF=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E7=AE=87?= =?UTF-8?q?=E6=89=80=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-workflow/weko_workflow/api.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/weko-workflow/weko_workflow/api.py b/modules/weko-workflow/weko_workflow/api.py index 2a0ce04efb..b82132a8cb 100644 --- a/modules/weko-workflow/weko_workflow/api.py +++ b/modules/weko-workflow/weko_workflow/api.py @@ -679,12 +679,15 @@ def reduce_workflows_for_registration(self, workflows): :return: wfs. """ wfs = [] - item_registration_id = current_app.config.get("WEKO_WORKFLOW_ITEM_REGISTRATION_ACTION_ID") + item_registration_id = \ + current_app.config.get("WEKO_WORKFLOW_ITEM_REGISTRATION_ACTION_ID") if isinstance(workflows, list): for workflow in workflows: - actions = workflow.flow_define.flow_actions - if item_registration_id in [action.action_id for action in actions]: - wfs.append(workflow) + if hasattr(workflow, 'flow_define'): + actions = workflow.flow_define.flow_actions + if item_registration_id in \ + [action.action_id for action in actions]: + wfs.append(workflow) return wfs class Action(object): From a3c2ca09a062ac991c1325b0a42d67b58b567691 Mon Sep 17 00:00:00 2001 From: ivis-akagawa Date: Thu, 1 May 2025 17:12:26 +0900 Subject: [PATCH 024/259] =?UTF-8?q?W-OA-IT-178=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-admin/weko_admin/admin.py | 12 +++- .../admin/jsonld_mapping_settings.html | 4 +- .../translations/en/LC_MESSAGES/messages.mo | Bin 22793 -> 22843 bytes .../translations/en/LC_MESSAGES/messages.po | 4 +- .../translations/ja/LC_MESSAGES/messages.mo | Bin 26205 -> 26272 bytes .../translations/ja/LC_MESSAGES/messages.po | 3 + .../weko_admin/translations/messages.pot | 2 + .../weko_itemtypes_ui/admin.py | 57 ++++++++++++++---- .../translations/en/LC_MESSAGES/messages.mo | Bin 5420 -> 5550 bytes .../translations/en/LC_MESSAGES/messages.po | 3 + .../translations/ja/LC_MESSAGES/messages.mo | Bin 4987 -> 5121 bytes .../translations/ja/LC_MESSAGES/messages.po | 3 + .../translations/messages.pot | 3 + .../weko_swordserver/models.py | 7 +++ 14 files changed, 82 insertions(+), 16 deletions(-) diff --git a/modules/weko-admin/weko_admin/admin.py b/modules/weko-admin/weko_admin/admin.py index 0fa3069ac8..e7e6180a9a 100644 --- a/modules/weko-admin/weko_admin/admin.py +++ b/modules/weko-admin/weko_admin/admin.py @@ -1994,8 +1994,18 @@ class JsonldMappingView(ModelView): ) def _item_type_name(view, context, model, name): + result_name = None result = ItemTypeNames.get_record(id_=model.item_type_id) - return result.name + if result is not None: + result_name = result.name + else: + result = ItemTypeNames.get_record(id_=model.item_type_id,\ + with_deleted=True) + if result is not None: + result_name = _('Deleted ItemType') + current_app.logger.info('ItemType:'/ + + str(model.item_type_id) + " is deleted itemtype") + return result_name def _formatting_mapping_json(view, context, model, name): format_json =json.dumps(model.mapping, indent=4, ensure_ascii=False) diff --git a/modules/weko-admin/weko_admin/templates/weko_admin/admin/jsonld_mapping_settings.html b/modules/weko-admin/weko_admin/templates/weko_admin/admin/jsonld_mapping_settings.html index c053793356..cf48483d8a 100644 --- a/modules/weko-admin/weko_admin/templates/weko_admin/admin/jsonld_mapping_settings.html +++ b/modules/weko-admin/weko_admin/templates/weko_admin/admin/jsonld_mapping_settings.html @@ -145,7 +145,7 @@
+
+

{{_('File Contents')}}

+
+
+ + +
+
+
diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 69ef7b581d..bf3c83879f 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -1908,8 +1908,8 @@ def get_at_id(key, index): list_at_id = [] for item_link_info in list_item_link_info: dict_item_link = { - "identifier": item_link_info.item_links, - "value": item_link_info.value + "identifier": item_link_info["item_links"], + "value": item_link_info["value"] } list_entity.append(dict_item_link) list_at_id.append(gen_id("itemLinks")) From bde83ed4e3c4dea4d1f59fc80a430d0276d7aea7 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 2 May 2025 18:17:22 +0900 Subject: [PATCH 034/259] fix: file path handling for encode file name. W-OA-IT-226 --- modules/weko-search-ui/weko_search_ui/utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py index cc3b96ea67..acd1538afb 100644 --- a/modules/weko-search-ui/weko_search_ui/utils.py +++ b/modules/weko-search-ui/weko_search_ui/utils.py @@ -5214,14 +5214,22 @@ def handle_flatten_data_encode_filename(list_record, data_path): new_file_list = [] for file in file_info["items"]: - filename = file.get("filename") + filename = ( + file.get("url", {}).get("label") + or file.get("filename") + ) + if not filename: + continue # encode filename encoded_filename = urllib.parse.quote(filename, safe='') file["filename"] = encoded_filename # copy file in directory to root under data_path - if not os.path.exists(os.path.join(data_path, encoded_filename)): + if ( + encoded_filename != filename + and os.path.exists(os.path.join(data_path, filename)) + ): shutil.copy( os.path.join(data_path, filename), os.path.join(data_path, encoded_filename) @@ -5238,6 +5246,10 @@ def handle_flatten_data_encode_filename(list_record, data_path): if new_file_list: metadata[key] = new_file_list # replace metadata + item["non_extract"] = [ + urllib.parse.quote(filename, safe='') + for filename in item.get("non_extract") + ] def check_replace_file_import_items(list_record, data_path=None, is_gakuninrdm=False, all_index_permission=True, can_edit_indexes=[]): From 6ab965d2dc4bd9a9fe5afd2a162433a48b43e984 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 2 May 2025 19:30:05 +0900 Subject: [PATCH 035/259] update: align the process to edit or delete item. --- modules/weko-items-ui/weko_items_ui/views.py | 47 +++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/modules/weko-items-ui/weko_items_ui/views.py b/modules/weko-items-ui/weko_items_ui/views.py index 67bdbc4940..5dbd1d4ce5 100644 --- a/modules/weko-items-ui/weko_items_ui/views.py +++ b/modules/weko-items-ui/weko_items_ui/views.py @@ -948,10 +948,7 @@ def prepare_edit_item(id=None, community=None): if not id and request: if request.headers['Content-Type'] != 'application/json': """Check header of request""" - return jsonify( - code=err_code, - msg=_('Header Error') - ) + return jsonify(code=err_code, msg=_('Header Error')) post_activity = request.get_json() or {} getargs = request.args if request else {} @@ -985,7 +982,7 @@ def prepare_edit_item(id=None, community=None): str(deposit.get('weko_shared_id')) ] user_id = str(get_current_user()) - activity = WorkActivity() + work_activity = WorkActivity() latest_pid = PIDVersioning(child=recid).last_child # ! Check User's Permissions @@ -1020,16 +1017,15 @@ def prepare_edit_item(id=None, community=None): if check_an_item_is_locked(pid_value): return jsonify( code=err_code, - msg=_('Item cannot be edited because ' - 'the import is in progress.') + msg=_('Item cannot be edited because the import is in progress.') ) # ! Check Record is being edit item_uuid = latest_pid.object_uuid - post_workflow = activity.get_workflow_activity_by_item_id(item_uuid) + latest_activity = work_activity.get_workflow_activity_by_item_id(item_uuid) - if post_workflow: - is_begin_edit = check_item_is_being_edit(recid, post_workflow, activity) + if latest_activity: + is_begin_edit = check_item_is_being_edit(recid, latest_activity, work_activity) if is_begin_edit: return jsonify( code=err_code, @@ -1037,15 +1033,16 @@ def prepare_edit_item(id=None, community=None): activity_id=is_begin_edit ) - if post_workflow: - post_activity['workflow_id'] = post_workflow.workflow_id - post_activity['flow_id'] = post_workflow.flow_id + if latest_activity: + post_activity['workflow_id'] = latest_activity.workflow_id + post_activity['flow_id'] = latest_activity.flow_id else: - post_workflow = activity.get_workflow_activity_by_item_id( + latest_activity = work_activity.get_workflow_activity_by_item_id( recid.object_uuid ) - workflow = get_workflow_by_item_type_id(item_type.name_id, - item_type_id) + workflow = get_workflow_by_item_type_id( + item_type.name_id, item_type_id + ) if not workflow: return jsonify( code=err_code, @@ -1053,9 +1050,10 @@ def prepare_edit_item(id=None, community=None): ) post_activity['workflow_id'] = workflow.id post_activity['flow_id'] = workflow.flow_id + post_activity['itemtype_id'] = item_type_id post_activity['community'] = community - post_activity['post_workflow'] = post_workflow + post_activity['post_workflow'] = latest_activity try: rtn = prepare_edit_workflow(post_activity, recid, deposit) @@ -1068,9 +1066,8 @@ def prepare_edit_item(id=None, community=None): msg=_('An error has occurred.') ) except BaseException as ex: - import traceback - current_app.logger.error(traceback.format_exc()) current_app.logger.error('Unexpected error: {}'.format(ex)) + traceback.format_exc() db.session.rollback() return jsonify( code=err_code, @@ -1232,22 +1229,22 @@ def prepare_delete_item(id=None, community=None): msg=_('Workflow setting does not exist.') ) post_activity['workflow_id'] = workflow.id + post_activity['itemtype_id'] = item_type_id post_activity['community'] = community post_activity['post_workflow'] = latest_activity - workflows = WorkFlows() - workflow_detail = workflows.get_workflow_by_id(post_activity['workflow_id']) + workflow_detail = WorkFlows().get_workflow_by_id( + post_activity['workflow_id'] + ) from weko_records_ui.views import soft_delete from .utils import send_mail_item_deleted, send_mail_delete_request + if workflow_detail.delete_flow_id is None: soft_delete(pid_value) send_mail_item_deleted(pid_value, deposit, user_id) - return jsonify( - code=0, - msg="success", - ) + return jsonify(code=0, msg="success") post_activity['flow_id'] = workflow_detail.delete_flow_id From 9e2bfecc00658715138b02eaf73a64dbded7c141 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 2 May 2025 21:49:40 +0900 Subject: [PATCH 036/259] fix: error handling --- modules/weko-swordserver/weko_swordserver/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index c2d5b01a17..7e9a4d8f94 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -248,10 +248,10 @@ def check_import_items( current_app.logger.error( f"Workflow not found for item type ID: {item_type_name}" ) - raise WekoSwordserverException( - "Workflow not found for registration your item.", - errorType=ErrorType.BadRequest - ) + error = check_result.get("error", "") + error += "; Workflow not found for item type ID." + check_result.update({"error": error}) + workflow = list_workflow[0] workflow_id = workflow.id check_result.update({"workflow_id": workflow_id}) From a96ee7ed4c2d4dbcb011477ecde4473dcb625492 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 2 May 2025 21:51:46 +0900 Subject: [PATCH 037/259] fix: not to use deleted workflow. W-OA-IT-227 --- modules/weko-items-ui/weko_items_ui/utils.py | 23 ++++++++++----- modules/weko-items-ui/weko_items_ui/views.py | 31 ++++++++------------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/modules/weko-items-ui/weko_items_ui/utils.py b/modules/weko-items-ui/weko_items_ui/utils.py index 6d769723b3..0924718c45 100644 --- a/modules/weko-items-ui/weko_items_ui/utils.py +++ b/modules/weko-items-ui/weko_items_ui/utils.py @@ -3799,20 +3799,29 @@ def translate_validation_message(item_property, cur_lang): translate_validation_message(value, cur_lang) -def get_workflow_by_item_type_id(item_type_name_id, item_type_id): +def get_workflow_by_item_type_id( + item_type_name_id, item_type_id, with_deleted=True +): """Get workflow settings by item type id.""" from weko_workflow.models import WorkFlow - workflow = WorkFlow.query.filter_by( - itemtype_id=item_type_id).first() + query = WorkFlow.query.filter_by(itemtype_id=item_type_id) + if not with_deleted: + query = query.filter_by(is_deleted=False) + workflow = query.first() + if not workflow: item_type_list = ItemTypes.get_by_name_id(item_type_name_id) id_list = [x.id for x in item_type_list] - workflow = ( + query = ( WorkFlow.query .filter(WorkFlow.itemtype_id.in_(id_list)) .order_by(WorkFlow.itemtype_id.desc()) - .order_by(WorkFlow.flow_id.asc()).first()) + .order_by(WorkFlow.flow_id.asc()) + ) + if not with_deleted: + query = query.filter_by(is_deleted=False) + workflow = query.first() return workflow @@ -4209,7 +4218,7 @@ def check_duplicate(data, is_item=True): recids_resource = {r[0] for r in result} matched_recids &= recids_resource matched_recids.discard(int(data.get("metainfo", {}).get("id") or 0)) - + if not matched_recids: return False, [], [] @@ -5636,4 +5645,4 @@ def send_mail_direct_registered(pid_value, user_id): context_obj=deposit, content_creator=create_direct_registered_data, record_url=record_url - ) \ No newline at end of file + ) diff --git a/modules/weko-items-ui/weko_items_ui/views.py b/modules/weko-items-ui/weko_items_ui/views.py index 5dbd1d4ce5..2e5cfe0bfa 100644 --- a/modules/weko-items-ui/weko_items_ui/views.py +++ b/modules/weko-items-ui/weko_items_ui/views.py @@ -1201,6 +1201,7 @@ def prepare_delete_item(id=None, community=None): msg=_('Item cannot be edited because the import is in progress.') ) + workflow, workflow_id = None, None # ! Check Record is being edit item_uuid = latest_pid.object_uuid latest_activity = work_activity.get_workflow_activity_by_item_id(item_uuid) @@ -1215,38 +1216,30 @@ def prepare_delete_item(id=None, community=None): ) if latest_activity: - post_activity['workflow_id'] = latest_activity.workflow_id - else: - latest_activity = work_activity.get_workflow_activity_by_item_id( - recid.object_uuid - ) + workflow = WorkFlows().get_workflow_by_id(latest_activity.workflow_id) + if not workflow.is_deleted: + workflow_id = latest_activity.workflow_id + + if not workflow_id: workflow = get_workflow_by_item_type_id( - item_type.name_id, item_type_id + item_type.name_id, item_type_id, with_deleted=False ) - if not workflow: - return jsonify( - code=err_code, - msg=_('Workflow setting does not exist.') - ) - post_activity['workflow_id'] = workflow.id + if workflow: + workflow_id = workflow.id post_activity['itemtype_id'] = item_type_id post_activity['community'] = community - post_activity['post_workflow'] = latest_activity - - workflow_detail = WorkFlows().get_workflow_by_id( - post_activity['workflow_id'] - ) + post_activity['workflow_id'] = workflow_id from weko_records_ui.views import soft_delete from .utils import send_mail_item_deleted, send_mail_delete_request - if workflow_detail.delete_flow_id is None: + if not workflow or workflow.delete_flow_id is None: soft_delete(pid_value) send_mail_item_deleted(pid_value, deposit, user_id) return jsonify(code=0, msg="success") - post_activity['flow_id'] = workflow_detail.delete_flow_id + post_activity['flow_id'] = workflow.delete_flow_id try: rtn = prepare_delete_workflow(post_activity, recid, deposit) From 06a4a205f9679d3014e1b4c2eaca87f824e30e03 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 7 May 2025 15:24:37 +0900 Subject: [PATCH 038/259] refactor: clarified the type and add unit test in weko-admin. --- .../invenio_oauth2server/models.py | 52 +-- .../invenio-oauth2server/tests/test_models.py | 20 +- modules/weko-admin/requirements2.txt | 1 + modules/weko-admin/tests/conftest.py | 122 ++++++- .../tests/data/ro-crate_mapping.json | 50 +++ modules/weko-admin/tests/test_admin.py | 335 +++++++++++------- modules/weko-admin/weko_admin/admin.py | 82 +++-- modules/weko-records/tests/test_api.py | 14 +- modules/weko-records/weko_records/api.py | 54 +-- modules/weko-records/weko_records/models.py | 7 + modules/weko-swordserver/tests/test_api.py | 66 ++-- .../weko-swordserver/weko_swordserver/api.py | 17 +- 12 files changed, 547 insertions(+), 273 deletions(-) create mode 100644 modules/weko-admin/tests/data/ro-crate_mapping.json diff --git a/modules/invenio-oauth2server/invenio_oauth2server/models.py b/modules/invenio-oauth2server/invenio_oauth2server/models.py index f14dc1975f..bdead30620 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/models.py +++ b/modules/invenio-oauth2server/invenio_oauth2server/models.py @@ -152,6 +152,12 @@ class Client(db.Model): nullable=True, index=True) """Creator of the client application.""" + user = db.relationship( + User, + backref=db.backref("oauth2clients", cascade="all, delete-orphan") + ) + """Relationship to user.""" + client_id = db.Column(db.String(255), primary_key=True) """Client application ID.""" @@ -285,32 +291,32 @@ def get_users(self): return no_users @classmethod - def get_client_id_by_user_id(cls, user_id): - """Get client_id, name by user_id. """ - query =( - db.session.query(cls) - .with_entities(cls.client_id, cls.name, cls.is_internal) - .filter(cls.user_id == user_id) - ) - return query.all() + def get_by_user_id(cls, user_id): + """Get client_id, name by user_id. - @classmethod - def get_client_id_all(cls): - """Get client_id all. """ - query = db.session.query(cls).with_entities(cls.client_id, cls.name) - return query.all() + Args: + user_id (int): User ID. - @classmethod - def get_name_by_client_id(cls, client_id): - """Get name by client_id. """ - query = db.session.query(cls).with_entities(cls.name).filter(cls.client_id == client_id) - return query.first() + Returns: + list[Client]: + List of Client object. If not found, return empty list. + """ + query = cls.query.filter(cls.user_id == user_id) + return [client for client in query.all() if isinstance(client, cls)] @classmethod - def get_user_id_by_client_id(cls, client_id): - """Get user_id by client_id. """ - query = db.session.query(cls).with_entities(cls.user_id).filter(cls.client_id == client_id) - return query.first() + def get_by_client_id(cls, client_id): + """Get client by client_id. + + Args: + client_id (str): Client ID. + + Returns: + Client: Client object. If not found, return `None`. + """ + obj = cls.query.filter_by(client_id=client_id).one_or_none() + return obj if isinstance(obj, cls) else None + class Token(db.Model): """A bearer token is the final token that can be used by the client.""" @@ -427,7 +433,7 @@ def create_personal(cls, name, user_id, scopes=None, is_internal=False): :param user_id: User ID. :param scopes: The list of permitted scopes. (Default: ``None``) :param is_internal: If ``True`` it's a internal access token. - (Default: ``False``) + (Default: ``False``) :returns: A new access token. """ with db.session.begin_nested(): diff --git a/modules/invenio-oauth2server/tests/test_models.py b/modules/invenio-oauth2server/tests/test_models.py index 7fbd8c66f4..04508851b5 100644 --- a/modules/invenio-oauth2server/tests/test_models.py +++ b/modules/invenio-oauth2server/tests/test_models.py @@ -255,26 +255,14 @@ def test_deletion_of_token2(models_fixture): # delete consumer db.session.delete(User.query.get(app.consumer_id)) -def test_get_client_id_by_user_id(models_fixture): +def test_get_by_user_id(models_fixture): app = models_fixture with app.app_context(): - lst = Client.get_client_id_by_user_id(app.resource_owner_id) + lst = Client.get_by_user_id(app.resource_owner_id) assert len(lst) > 0 -def test_get_client_id_all(models_fixture): +def test_get_by_client_id(models_fixture): app = models_fixture with app.app_context(): - lst = Client.get_client_id_all() - assert len(lst) > 0 - -def test_get_name_by_client_id(models_fixture): - app = models_fixture - with app.app_context(): - lst = Client.get_name_by_client_id(app.u1c1_id) - assert len(lst) > 0 - -def test_get_user_id_by_client_id(models_fixture): - app = models_fixture - with app.app_context(): - lst = Client.get_user_id_by_client_id(app.u1c1_id) + lst = Client.get_by_client_id(app.u1c1_id) assert len(lst) > 0 diff --git a/modules/weko-admin/requirements2.txt b/modules/weko-admin/requirements2.txt index 8995d6fb16..9938b4c3d1 100644 --- a/modules/weko-admin/requirements2.txt +++ b/modules/weko-admin/requirements2.txt @@ -276,6 +276,7 @@ webencodings==0.5.1 -e ../weko-theme -e ../weko-user-profiles -e ../weko-workflow +-e ../weko-workspace Werkzeug==0.15.2 whichcraft==0.4.1 WTForms==2.1 diff --git a/modules/weko-admin/tests/conftest.py b/modules/weko-admin/tests/conftest.py index a417878d57..3c7ff9e5ac 100644 --- a/modules/weko-admin/tests/conftest.py +++ b/modules/weko-admin/tests/conftest.py @@ -25,8 +25,9 @@ import tempfile import uuid import json -from datetime import datetime +from datetime import datetime, timedelta from elasticsearch import Elasticsearch +from invenio_accounts.utils import jwt_create_token from invenio_indexer import InvenioIndexer import pytest from invenio_indexer.api import RecordIndexer @@ -66,7 +67,7 @@ from invenio_records.ext import InvenioRecords from invenio_records.models import RecordMetadata from invenio_pidstore.models import PersistentIdentifier -from invenio_oauth2server.models import Client +from invenio_oauth2server.models import Client, Token from weko_authors import WekoAuthors from weko_authors.models import Authors @@ -77,6 +78,7 @@ from weko_records import WekoRecords from weko_records.models import SiteLicenseInfo, SiteLicenseIpAddress,ItemType,ItemTypeName,ItemTypeJsonldMapping from weko_redis.redis import RedisConnection +from weko_swordserver.models import SwordClientModel from weko_theme import WekoTheme from weko_schema_ui import WekoSchemaUI from weko_search_ui import WekoSearchUI @@ -92,9 +94,8 @@ LogAnalysisRestrictedCrawlerList,StatisticsEmail,RankingSettings, Identifier from weko_admin.views import blueprint_api -from tests.helpers import json_data, create_record +from .helpers import json_data, create_record from weko_admin.models import FacetSearchSetting -from tests.helpers import json_data @pytest.yield_fixture() def instance_path(): @@ -1221,3 +1222,116 @@ def community(db, users, indexes): db.session.add(comm1) db.session.commit() return comm1 + +@pytest.fixture +def tokens(app,users,db): + scopes = [ + "deposit:write deposit:actions item:create", + "deposit:write deposit:actions item:create user:activity", + "deposit:write user:activity", + "" + ] + tokens = [] + + for i, scope in enumerate(scopes): + user = users[i] + user_id = str(user["id"]) + + test_client = Client( + client_id=f"dev{user_id}", + client_secret=f"dev{user_id}", + name="Test name", + description="test description", + is_confidential=False, + user_id=user_id, + _default_scopes="deposit:write" + ) + test_token = Token( + client=test_client, + user_id=user_id, + token_type="bearer", + access_token=jwt_create_token(user_id=user_id), + expires=datetime.now() + timedelta(hours=10), + is_personal=False, + is_internal=False, + _scopes=scope + ) + + db.session.add(test_client) + db.session.add(test_token) + tokens.append({"token":test_token, "client":test_client, "scope":scope}) + + db.session.commit() + + return tokens + + +@pytest.fixture +def sword_mapping(db, item_type): + sword_mapping = [] + for i in range(1, 4): + obj = ItemTypeJsonldMapping( + name=f"test{i}", + mapping=json_data("data/ro-crate_mapping.json"), + item_type_id=item_type[1]["obj"].id, + is_deleted=False + ) + with db.session.begin_nested(): + db.session.add(obj) + + sword_mapping.append({ + "id": obj.id, + "sword_mapping": obj, + "name": obj.name, + "mapping": obj.mapping, + "item_type_id": obj.item_type_id, + "version_id": obj.version_id, + "is_deleted": obj.is_deleted + }) + + db.session.commit() + + return sword_mapping + +@pytest.fixture +def sword_client(db, tokens, sword_mapping, flows): + client = tokens[0]["client"] + sword_client1 = SwordClientModel( + client_id=client.client_id, + active=True, + registration_type_id=SwordClientModel.RegistrationType.DIRECT, + mapping_id=sword_mapping[0]["sword_mapping"].id, + duplicate_check=False, + meta_data_api=[], + ) + client = tokens[1]["client"] + sword_client2 = SwordClientModel( + client_id=client.client_id, + active=True, + registration_type_id=SwordClientModel.RegistrationType.WORKFLOW, + mapping_id=sword_mapping[1]["sword_mapping"].id, + workflow_id=flows["workflow"][1].id, + duplicate_check=True, + meta_data_api=[], + ) + client = tokens[2]["client"] + sword_client3 = SwordClientModel( + client_id=client.client_id, + active=False, + registration_type_id=SwordClientModel.RegistrationType.DIRECT, + mapping_id=sword_mapping[0]["sword_mapping"].id, + duplicate_check=False, + meta_data_api=[], + ) + + with db.session.begin_nested(): + db.session.add(sword_client1) + db.session.add(sword_client2) + db.session.add(sword_client3) + db.session.commit() + + return [ + {"sword_client": sword_client1}, + {"sword_client": sword_client2}, + {"sword_client": sword_client3} + ] diff --git a/modules/weko-admin/tests/data/ro-crate_mapping.json b/modules/weko-admin/tests/data/ro-crate_mapping.json new file mode 100644 index 0000000000..16499fbc6f --- /dev/null +++ b/modules/weko-admin/tests/data/ro-crate_mapping.json @@ -0,0 +1,50 @@ +{ + "PubDate": "datePublished", + "File": "hasPart", + "File.サイズ.サイズ": "hasPart.contentSize", + "File.バージョン情報": "hasPart.version", + "File.日付": "hasPart.dateCreated", + "File.日付.日付": "hasPart.dateCreated.value", + "File.日付.日付タイプ": "hasPart.dateCreated.name", + "File.ファイル名": "hasPart.name", + "File.本文URL": "hasPart.identifier", + "File.本文URL.オブジェクトタイプ": "hasPart.url.value", + "File.本文URL.ラベル": "hasPart.@id", + "File.フォーマット": "hasPart.encodingFormat", + "File.アクセス": "hasPart.wk:accessMode", + "File.公開日.公開日": "hasPart.datePublished", + "Title": "dc:title", + "Title.タイトル": "dc:title.value", + "Title.言語": "dc:title.language", + "Creator": "creator", + "Creator.作成者姓名.姓名": "creator.name", + "Creator.作成者姓名.言語": "creator.language", + "Creator.作成者所属.所属機関名.所属機関名": "creator.affiliation.name", + "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "creator.affiliation.url", + "Resource Type": "dc:type", + "Resource Type.資源タイプ識別子": "dc:type.@id", + "Resource Type.資源タイプ": "dc:type.name", + "Contributor": "contributor", + "Contributor.寄与者名.名": "contributor.givenName.name", + "Contributor.寄与者名.言語": "contributor.givenName.language", + "Contributor.寄与者姓": "contributor.familyName", + "Contributor.寄与者姓.姓": "contributor.familyName.name", + "Contributor.寄与者姓.言語": "contributor.familyName.language", + "Contributor.寄与者識別子": "contributor.identifier", + "Contributor.寄与者識別子.寄与者識別子": "contributor.identifier.@id", + "Contributor.寄与者識別子.寄与者識別子URI": "contributor.identifier.uri", + "Contributor.寄与者識別子.寄与者識別子Scheme": "contributor.identifier.scheme", + "Contributor.寄与者メールアドレス.メールアドレス": "contributor.email", + "Contributor.寄与者姓名.言語": "contributor.language", + "Contributor.寄与者姓名.姓名": "contributor.name", + "Contributor.寄与者所属": "contributor.affiliation", + "Contributor.寄与者所属.所属機関名.所属機関名": "contributor.affiliation.name", + "Contributor.寄与者所属.所属機関名.言語": "contributor.affiliation.language", + "Contributor.寄与者所属.所属機関識別子": "contributor.affiliation.identifier", + "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "contributor.affiliation.identifier.uri", + "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "contributor.affiliation.identifier.scheme", + "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "contributor.affiliation.identifier.@id", + "Contributor.寄与者別名": "contributor.alternateName", + "Contributor.寄与者別名.別名": "contributor.alternateName.name", + "Contributor.寄与者別名.言語": "contributor.alternateName.language" +} diff --git a/modules/weko-admin/tests/test_admin.py b/modules/weko-admin/tests/test_admin.py index 4e5f1b8d02..c18843fbb2 100644 --- a/modules/weko-admin/tests/test_admin.py +++ b/modules/weko-admin/tests/test_admin.py @@ -1,39 +1,42 @@ import os -from os.path import dirname, join -from flask import url_for,current_app,make_response -from flask_admin import Admin -from mock import patch -from mock import MagicMock, patch +import io import json -from io import BytesIO import pytest from datetime import datetime +from mock import MagicMock, patch + +from flask import url_for,current_app,make_response +from flask_admin import Admin +from flask_wtf import FlaskForm,Form +from sqlalchemy.exc import SQLAlchemyError from wtforms.validators import ValidationError from werkzeug.datastructures import ImmutableMultiDict -from flask_wtf import FlaskForm,Form -from weko_workflow.api import WorkFlow -from weko_workflow.models import WorkFlow - -import pytest -from requests import Response - -from invenio_accounts.testutils import login_user_via_session -from .test_views import assert_role -from invenio_accounts.testutils import login_user_via_session, create_test_user from invenio_access.models import ActionUsers +from invenio_accounts.testutils import login_user_via_session, create_test_user from invenio_communities.models import Community +from invenio_oauth2server.models import Client +from weko_admin.models import ( + AdminSettings,StatisticsEmail,LogAnalysisRestrictedCrawlerList, RankingSettings, + SearchManagement, Identifier,FacetSearchSetting +) from weko_index_tree.models import IndexStyle,Index -from weko_admin.admin import StyleSettingView,LogAnalysisSettings,ItemExportSettingsView,IdentifierSettingView,\ - identifier_adminview,facet_search_adminview,FacetSearchSettingView,SwordAPISettingsView, SwordAPIJsonldSettingsView,\ - JsonldMappingView -from weko_admin.models import AdminSettings,StatisticsEmail,LogAnalysisRestrictedCrawlerList,\ - RankingSettings,SearchManagement, Identifier,FacetSearchSetting +from weko_records.api import JsonldMapping +from weko_swordserver.api import SwordClient +from weko_workflow.api import WorkFlow +from weko_workflow.models import WorkFlow from weko_records.models import ItemTypeJsonldMapping from weko_swordserver.models import SwordClientModel -from invenio_oauth2server.models import Client + +from weko_admin.admin import ( + StyleSettingView,LogAnalysisSettings,ItemExportSettingsView,IdentifierSettingView, + identifier_adminview,facet_search_adminview,FacetSearchSettingView,SwordAPISettingsView, + SwordAPIJsonldSettingsView, JsonldMappingView +) + +from .test_views import assert_role # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp @@ -48,9 +51,9 @@ def test_index(self,client,users,mocker): url = url_for("stylesetting.index") # get - scss_dir = join(current_app.instance_path, current_app.config['WEKO_THEME_INSTANCE_DATA_DIR']) + scss_dir = os.path.join(current_app.instance_path, current_app.config['WEKO_THEME_INSTANCE_DATA_DIR']) os.makedirs(scss_dir, exist_ok=True) - scss_file = join(scss_dir, '_variables.scss') + scss_file = os.path.join(scss_dir, '_variables.scss') with open(scss_file, "w") as f: f.write("$body-bg: #ffff;\n$panel-bg: #ffff;\n$footer-default-bg: #0d5f89;\n$navbar-default-bg: #0d5f89;\n$panel-default-border: #dddddd;\n$input-bg-transparent: rgba(255, 255, 255, 0);") mock_render = mocker.patch("weko_admin.admin.StyleSettingView.render",return_value=make_response()) @@ -116,17 +119,17 @@ def test_upload_editor(self, client, users,f_or_h,data,result,status_code,res_da login_user_via_session(client,email=users[0]["email"]) url = url_for("stylesetting.upload_editor") from weko_theme.views import blueprint as theme_bp - path_to_instance = join("../../../../../",current_app.instance_path) + path_to_instance = os.path.join("../../../../../",current_app.instance_path) current_app.config.update( - THEME_FOOTER_WYSIWYG_TEMPLATE=join(path_to_instance,"footer_wysiwtg_template.html"), - THEME_HEADER_WYSIWYG_TEMPLATE=join(path_to_instance,"header_wysiwtg_template.html") + THEME_FOOTER_WYSIWYG_TEMPLATE=os.path.join(path_to_instance,"footer_wysiwtg_template.html"), + THEME_HEADER_WYSIWYG_TEMPLATE=os.path.join(path_to_instance,"header_wysiwtg_template.html") ) target_file = str() if f_or_h == "footer": - target_file = join(theme_bp.root_path, theme_bp.template_folder, current_app.config["THEME_FOOTER_WYSIWYG_TEMPLATE"]) + target_file = os.path.join(theme_bp.root_path, theme_bp.template_folder, current_app.config["THEME_FOOTER_WYSIWYG_TEMPLATE"]) elif f_or_h == "header": - target_file = join(theme_bp.root_path, theme_bp.template_folder, current_app.config["THEME_HEADER_WYSIWYG_TEMPLATE"]) + target_file = os.path.join(theme_bp.root_path, theme_bp.template_folder, current_app.config["THEME_HEADER_WYSIWYG_TEMPLATE"]) else: pass if target_file: @@ -145,7 +148,7 @@ def test_upload_editor(self, client, users,f_or_h,data,result,status_code,res_da # def get_contents(self, f_path): # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestStyleSettingView::test_get_contents -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp def test_get_contents(self,client,mocker): - path = join(current_app.instance_path,dirname(__file__),"data/_variables.scss") + path = os.path.join(current_app.instance_path,os.path.dirname(__file__),"data/_variables.scss") result = StyleSettingView().get_contents(path) test = [ "$body-bg: #ffff;\n", @@ -166,8 +169,8 @@ def test_get_contents(self,client,mocker): # def cmp_files(self, f_path1, f_path2): # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestStyleSettingView::test_cmp_files -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp def test_cmp_files(self,app,client,mocker): - path1=join(current_app.instance_path,dirname(__file__),"data/_variables.scss") - path2=join(current_app.instance_path,dirname(__file__),"data/actions.json") + path1=os.path.join(current_app.instance_path,os.path.dirname(__file__),"data/_variables.scss") + path2=os.path.join(current_app.instance_path,os.path.dirname(__file__),"data/actions.json") result = StyleSettingView().cmp_files(path1,path2) assert result == False @@ -382,7 +385,7 @@ def test_get_file_stats_output(self,client,users,statistic_email_addrs,mocker): data = { "report":json.dumps(stats_json),"year":"2022","month":"10","send_email":"False" } - mocker.patch("weko_admin.admin.package_reports",return_value=BytesIO()) + mocker.patch("weko_admin.admin.package_reports",return_value=io.BytesIO()) result = client.post(url,data=data) assert result.headers["Content-Type"] == "application/x-zip-compressed" assert result.headers["Content-Disposition"] == "attachment; filename=logReport_2022-10.zip" @@ -2065,11 +2068,12 @@ def test_index(self, client, users, db, admin_settings, mocker): assert res.status_code == 200 -#class SwordAPIJsonldSettingsView(ModelView): -# +# class SwordAPIJsonldSettingsView(ModelView): +# .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::test_create_view -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp class TestSwordAPIJsonldSettingsView: - - def test_create_view(self, client, users, db, mocker): + # def create_view(self): + # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::test_create_view -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp + def test_create_view(self, client, db, users, item_type, flows, tokens, sword_mapping, mocker): url = url_for("swordapi/jsonld.create_view") #no administrator @@ -2094,47 +2098,100 @@ def test_create_view(self, client, users, db, mocker): res = client.get(url) assert res.status_code == 200 args, kwargs = mock_render.call_args + assert args[0] == "weko_admin/admin/sword_api_jsonld_settings.html" + assert kwargs["workflows"] + assert kwargs["can_edit"] # post # success registration_type:Direct, active:True login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.SwordClient.register", return_value=None): - res = client.post(url, - data=json.dumps({'application': '1', - 'registration_type': 'Direct', - 'mapping_id': '1', - 'active': 'True', - 'Meta_data_API_selected':[]}), - content_type='application/json') + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + res = client.post( + url, + data=json.dumps({ + 'application': tokens[0]["client"].client_id, + 'registration_type': 'Direct', + 'mapping_id': '1', + 'active': 'True', + 'Meta_data_API_selected':[] + }), + content_type='application/json' + ) assert res.status_code == 200 # post # success registration_type:Workflow, active:false login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.SwordClient.register", return_value=None): - res = client.post(url, - data=json.dumps({'application': '1', - 'registration_type': 'Workflow', - 'workflow_id':31001, - 'mapping_id': '1', - 'active': 'False', - 'Meta_data_API_selected':[]}), - content_type='application/json') + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + res = client.post( + url, + data=json.dumps({ + 'application': tokens[1]["client"].client_id, + 'registration_type': 'Workflow', + 'workflow_id': flows["workflow"][0].id, + 'mapping_id': '1', + 'active': 'False', + 'Meta_data_API_selected':[] + }), + content_type='application/json' + ) assert res.status_code == 200 # post - # Error + # invalid mapping login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.SwordClient.register", side_effect=Exception("test_error")): - res = client.post(url, - data=json.dumps({'application': '1', - 'registration_type': 'Workflow', - 'mapping_id': '1', - 'active': 'False'}), - content_type='application/json') + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=["test_error"]): + res = client.post( + url, + data=json.dumps({ + 'application': tokens[0]["client"].client_id, + 'registration_type': 'Direct', + 'mapping_id': '1', + 'active': 'True', + 'Meta_data_API_selected':[] + }), + content_type='application/json' + ) assert res.status_code == 400 - def test_edit_view(self, client, users, item_type, flows, db, mocker): + # post + # error in validate + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonLdMapper.validate", side_effect=KeyError("test_key_error")): + res = client.post( + url, + data=json.dumps({ + 'application': tokens[0]["client"].client_id, + 'registration_type': 'Direct', + 'mapping_id': '1', + 'active': 'True', + 'Meta_data_API_selected':[] + }), + content_type='application/json' + ) + assert res.status_code == 400 + + + # # post + # # Error + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + with patch("weko_admin.admin.SwordClient.register", side_effect=SQLAlchemyError("test_db_error")): + res = client.post( + url, + data=json.dumps({ + 'application': tokens[0]["client"].client_id, + 'registration_type': 'Workflow', + 'mapping_id': '1', + 'active': 'False' + }), + content_type='application/json' + ) + assert res.status_code == 400 + + # def edit_view(self, id): + # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::test_edit_view -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp + def test_edit_view(self, client, db, users, item_type, flows, tokens, sword_mapping, sword_client, mocker): url = url_for("swordapi/jsonld.edit_view", id=1) #no administrator @@ -2145,92 +2202,114 @@ def test_edit_view(self, client, users, item_type, flows, db, mocker): res = client.get(url) assert res.status_code == 403 + mock_render = mocker.patch("weko_admin.admin.SwordAPIJsonldSettingsView.render", return_value=make_response()) # get # model_none url_none = url_for("swordapi/jsonld.edit_view", id=999) login_user_via_session(client,email=users[0]["email"])# sysadmin - mock_render = mocker.patch("weko_admin.admin.SwordAPIJsonldSettingsView.render", return_value=make_response()) res = client.get(url_none) assert res.status_code == 404 + mock_can_edit = mocker.patch("weko_admin.admin.SwordAPIJsonldSettingsView._is_editable", return_value=True) # get # work_flow_id exit - settings = list() - settings.append(Client(name=1,description=1,website=1,user_id=1,client_id="1",client_secret="KDjy6ntGKUX",is_confidential=True,is_internal=False,_redirect_uris="https://" ,_default_scopes="NULL")) - db.session.add_all(settings) - db.session.commit() - settings = list() - settings.append(ItemTypeJsonldMapping(id=1,name="sample1",mapping="{data:{}}",item_type_id=item_type[0]["obj"].id,version_id=6,is_deleted=False)) - db.session.add_all(settings) - db.session.commit() - settings = list() - settings.append(SwordClientModel(id=1,client_id=1,registration_type_id=2,mapping_id=1,workflow_id=31001,active=True,meta_data_api=[])) - db.session.add_all(settings) + deleted_workflow = WorkFlow().query.filter_by(id=31001).first() + deleted_workflow.is_deleted = True db.session.commit() login_user_via_session(client,email=users[0]["email"])# sysadmin - mock_render = mocker.patch("weko_admin.admin.SwordAPIJsonldSettingsView.render", return_value=make_response()) - deleted_workflow = WorkFlow( - id = "1", - flows_name = "test_workflow" - ) - with patch("weko_admin.admin.WorkFlow.get_workflows_by_roles", return_value=["workflow 1"]): - with patch("weko_admin.admin.WorkFlow.get_deleted_workflow_list", return_value=[deleted_workflow]): - with patch("weko_admin.admin.WorkActivity.count_waiting_approval_by_workflow_id", return_value=2): - res = client.get(url) - assert res.status_code == 200 - with patch("weko_admin.admin.WorkFlow.get_workflows_by_roles", return_value=["workflow 1"]): - with patch("weko_admin.admin.WorkFlow.get_deleted_workflow_list", return_value=[deleted_workflow]): - res = client.get(url) + res = client.get(url) assert res.status_code == 200 + args, kwargs = mock_render.call_args + assert args[0] == "weko_admin/admin/sword_api_jsonld_settings.html" + assert kwargs["workflows"] + assert kwargs["can_edit"] # get - # work_flow_id none - SwordClientModel.query.filter_by(id=1).delete() - settings = list() - settings.append(SwordClientModel(id=1,client_id=1,registration_type_id=1,mapping_id=1,workflow_id=None,active=True,meta_data_api=[])) - db.session.add_all(settings) - db.session.commit() + # cannot edit + mock_can_edit.return_value = False login_user_via_session(client,email=users[0]["email"])# sysadmin - mock_render = mocker.patch("weko_admin.admin.SwordAPIJsonldSettingsView.render", return_value=make_response()) - deleted_workflow = WorkFlow( - id = "1", - flows_name = "test_workflow" - ) - with patch("weko_admin.admin.WorkFlow.get_workflows_by_roles", return_value=["workflow 1"]): - with patch("weko_admin.admin.WorkFlow.get_deleted_workflow_list", return_value=[deleted_workflow]): - res = client.get(url) + res = client.get(url) assert res.status_code == 200 + mock_can_edit.return_value = True # post # success registration_type:Direct, active:True login_user_via_session(client,email=users[0]["email"])# sysadmin - res = client.post(url, - data=json.dumps({'registration_type': 'Direct', - 'workflow_id':31001, - 'mapping_id': '1', - 'active': 'True', - 'Meta_data_API_selected':[]}), - content_type='application/json') + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + res = client.post( + url, + data=json.dumps({ + 'registration_type': 'Direct', + 'workflow_id': 31001, + 'mapping_id': '1', + 'active': 'True', + 'metadata_api_selected':[] + }), + content_type='application/json' + ) assert res.status_code == 200 # post # success registration_type:Workflow, active:false login_user_via_session(client,email=users[0]["email"])# sysadmin - res = client.post(url, - data=json.dumps({'registration_type': 'Workflow', - 'workflow_id':31001, - 'mapping_id': '1', - 'active': 'False', - 'Meta_data_API_selected':[]}), - content_type='application/json') + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + res = client.post( + url, + data=json.dumps({ + 'registration_type': 'Workflow', + 'workflow_id':31001, + 'mapping_id': '1', + 'active': 'False', + 'metadata_api_selected': ["Original"] + }), + content_type='application/json' + ) assert res.status_code == 200 # post - # error + # canntot edit + mock_can_edit.return_value = False + login_user_via_session(client,email=users[0]["email"]) + res = client.post( + url, + data=json.dumps({ + 'registration_type': 'Workflow', + 'workflow_id':31001, + 'mapping_id': '1', + 'active': 'False', + 'metadata_api_selected':[] + }), + content_type='application/json' + ) + assert res.status_code == 400 + assert json.loads(res.data) == "Unapproved items exit." + + # post + # invalid mapping + + mock_can_edit.return_value = True login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.db.session.commit", side_effect=Exception("test_error")): + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=["test_error"]): + res = client.post( + url, + data=json.dumps({ + 'registration_type': 'Direct', + 'workflow_id': 31001, + 'mapping_id': '1', + 'active': 'True', + 'metadata_api_selected':[] + }), + content_type='application/json' + ) + assert res.status_code == 400 + assert json.loads(res.data) == {"error": "Invalid jsonld mapping."} + + # post + # error in validate + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonLdMapper.validate", side_effect=KeyError("test_key_error")): res = client.post(url, data=json.dumps({'registration_type': 'Workflow', 'workflow_id':31001, @@ -2239,6 +2318,22 @@ def test_edit_view(self, client, users, item_type, flows, db, mocker): 'Meta_data_API_selected':[]}), content_type='application/json') assert res.status_code == 400 + assert json.loads(res.data) == {"error": "Failed to validate jsonld mapping."} + + # post + # error in update + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=None): + with patch("weko_admin.admin.SwordClient.update", side_effect=SQLAlchemyError("test_db_error")): + res = client.post(url, + data=json.dumps({'registration_type': 'Workflow', + 'workflow_id':31001, + 'mapping_id': '1', + 'active': 'False', + 'Meta_data_API_selected':[]}), + content_type='application/json') + assert res.status_code == 400 + assert json.loads(res.data) == {"error": "Failed to update application settings: Test name"} def test_get_query_in_role_ids(self, client, users, db, mocker): @@ -2253,8 +2348,8 @@ def test_get_query(self, client, users, db, mocker): -#class JsonldMappingView(ModelView): -# +# class JsonldMappingView(ModelView): +# .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestJsonldMappingView::test_create_view -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp class TestJsonldMappingView: def test_create_view(self, client, users, item_type, db, mocker): diff --git a/modules/weko-admin/weko_admin/admin.py b/modules/weko-admin/weko_admin/admin.py index f4d2b1660b..98fdd74e4a 100644 --- a/modules/weko-admin/weko_admin/admin.py +++ b/modules/weko-admin/weko_admin/admin.py @@ -1585,12 +1585,12 @@ class SwordAPIJsonldSettingsView(ModelView): "metadata_collection", "duplicate_check" ) - column_searchable_list = ("registration_type_id", "client_id", "workflow_id") def _format_application_name(view, context, model, name): - result = Client.get_name_by_client_id(model.client_id) - return result.name + if not isinstance(model, SwordClientModel): + return "" + return model.oauth_client.name def _format_active(view, context, model, name): if model.active: @@ -1599,21 +1599,24 @@ def _format_active(view, context, model, name): return _("Inactive Message") def _format_creator(view, context, model, name): - client = Client.get_user_id_by_client_id(model.client_id) - result = User.get_email_by_id(client.user_id) - return result.email + if not isinstance(model, SwordClientModel): + return "" + + return model.oauth_client.user.email def _format_registration_type(view, context, model, name): - if model.registration_type_id == 1: - return "Direct" - else: - return "Workflow" + if not isinstance(model, SwordClientModel): + return "" + if model.registration_type == "Direct": + return _("Direct_Registration") + elif model.registration_type == "Workflow": + return _("WorkFlow_Registration") def _format_metadata_collection(view, context, model, name): if len(model.meta_data_api) > 0: - return "ON" + return _("Active Message") else: - return "OFF" + return _("Inactive Message") def _format_duplicate_check(view, context, model, name): if model.duplicate_check: @@ -1660,18 +1663,16 @@ def create_view(self): form = FlaskForm(request.form) # GET current user oauth clients - current_user_clients = [ + list_cur_user_client = [ client - for client in Client.get_client_id_by_user_id(current_user.get_id()) + for client in Client.get_by_user_id(current_user.get_id()) # exclude personal clients if not client.is_internal ] - list_sword_clients = [ - client.client_id for client in SwordClient.get_client_id_all() - ] + list_sword_clients = SwordClient.get_client_id_all() # exclude already registered clients client_list = [ - client for client in current_user_clients + client for client in list_cur_user_client if client.client_id not in list_sword_clients ] @@ -1742,9 +1743,8 @@ def create_view(self): meta_data_api = request.json.get("metadata_api_selected") obj = JsonldMapping.get_mapping_by_id(mapping_id) - itemtype_id = obj.item_type_id try: - if not JsonLdMapper(itemtype_id, obj.mapping).is_valid: + if not JsonLdMapper(obj.item_type_id, obj.mapping).is_valid: msg = f"Invalid jsonld mapping." current_app.logger.error(msg) return jsonify({"error": msg}), 400 @@ -1874,15 +1874,17 @@ def edit_view(self, id): duplicate_check = request.json.get("duplicate_check") == "True" meta_data_api = request.json.get("metadata_api_selected") + if meta_data_api == ["Original"]: + meta_data_api = [] + obj = JsonldMapping.get_mapping_by_id(mapping_id) - itemtype_id = obj.item_type_id try: - if not JsonLdMapper(itemtype_id, obj.mapping).is_valid: - msg = f"Invalid jsonld mapping." + if not JsonLdMapper(obj.item_type_id, obj.mapping).is_valid: + msg = "Invalid jsonld mapping." current_app.logger.error(msg) return jsonify({"error": msg}), 400 except Exception as ex: - msg = f"Failed to validate jsonld mapping." + msg = "Failed to validate jsonld mapping." current_app.logger.error(msg) traceback.print_exc() return jsonify({"error": msg}), 400 @@ -2003,21 +2005,23 @@ class JsonldMappingView(ModelView): ) def _item_type_name(view, context, model, name): - result_name = None - result = ItemTypeNames.get_record(id_=model.item_type_id) - if result is not None: - result_name = result.name + item_type_name = "" + obj = ItemTypeNames.get_record(id_=model.item_type_id, with_deleted=True) + if obj is not None and obj.is_active: + item_type_name = obj.name + elif obj is not None and not obj.is_active: + item_type_name = _("Deleted ItemType") + current_app.logger.info( + f"ItemType: {model.item_type_id} is deleted itemtype." + ) else: - result = ItemTypeNames.get_record(id_=model.item_type_id,\ - with_deleted=True) - if result is not None: - result_name = _("Deleted ItemType") - current_app.logger.info( - f"ItemType: {model.item_type_id} is deleted itemtype." - ) - return result_name + current_app.logger.info( + f"ItemType: {model.item_type_id} is not found." + ) + item_type_name = _("Not Found ItemType") + return item_type_name - def _formatting_mapping_json(view, context, model, name): + def _formated_jsonld_mapping(view, context, model, name): format_json =json.dumps(model.mapping, indent=4, ensure_ascii=False) return Markup( '
 0
-
     # def register(cls, client_id, registration_type_id, mapping_id, workflow_id):
     # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_api.py::TestSwordClient::test_register -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
     def test_register(app, db, tokens, sword_mapping, workflow):
@@ -230,19 +202,45 @@ def test_remove(app, db, sword_client):
     # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_api.py::TestSwordClient::test_get_client_by_id -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
     def test_get_client_by_id(self, db, sword_client):
         # Get client by valid ID (Direct registration)
-        client = sword_client[0]["sword_client"]
-        result = SwordClient.get_client_by_id(client.client_id)
-        assert result == client
+        obj = sword_client[0]["sword_client"]
+        result = SwordClient.get_client_by_id(obj.client_id)
+        assert result == obj
         assert result.registration_type_id == SwordClientModel.RegistrationType.DIRECT
+        assert result.registration_type == "Direct"
+
 
         # Get client by valid ID (Workflow registration)
-        client = sword_client[1]["sword_client"]
-        result = SwordClient.get_client_by_id(client.client_id)
-        assert result == client
+        obj = sword_client[1]["sword_client"]
+        result = SwordClient.get_client_by_id(obj.client_id)
+        assert result == obj
         assert result.registration_type_id == SwordClientModel.RegistrationType.WORKFLOW
+        assert result.registration_type == "Workflow"
 
         # Get client by non-existent ID
         result = SwordClient.get_client_by_id("non_existent_client_id")
         assert result is None
 
 
+    # def get_client_id_all(cls):
+    # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_api.py::TestSwordClient::test_get_client_id_all -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
+    def test_get_client_id_all(app, db, sword_client):
+        lst = SwordClient.get_client_id_all()
+        assert len(lst) == 3
+        assert lst[0] == sword_client[0]["sword_client"].client_id
+        assert lst[1] == sword_client[1]["sword_client"].client_id
+        assert lst[2] == sword_client[2]["sword_client"].client_id
+
+
+    # def get_clients_by_mapping_id(cls, mapping_id):
+    # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_api.py::TestSwordClient::test_get_clients_by_mapping_id -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
+    def test_get_clients_by_mapping_id(app, db, sword_client):
+        # Get clients by valid mapping ID
+        obj = sword_client[0]["sword_client"]
+        result = SwordClient.get_clients_by_mapping_id(obj.mapping_id)
+        assert len(result) == 2
+        assert result[0].client_id == sword_client[0]["sword_client"].client_id
+        assert result[1].client_id == sword_client[2]["sword_client"].client_id
+
+        # Get clients by non-existent mapping ID
+        result = SwordClient.get_clients_by_mapping_id(999)
+        assert len(result) == 0
diff --git a/modules/weko-swordserver/weko_swordserver/api.py b/modules/weko-swordserver/weko_swordserver/api.py
index 9a17c56833..5730929d9b 100644
--- a/modules/weko-swordserver/weko_swordserver/api.py
+++ b/modules/weko-swordserver/weko_swordserver/api.py
@@ -172,14 +172,18 @@ def get_client_by_id(cls, client_id):
             SwordClient: Client object. If not found, return `None`.
         """
         obj = SwordClientModel.query.filter_by(client_id=client_id).one_or_none()
-        return obj
+        return obj if isinstance(obj, SwordClientModel) else None
 
 
     @classmethod
     def get_client_id_all(cls):
-        """Get client_id all. """
+        """Get client_id all.
+
+        Returns:
+            list: List of client_id. If not found, return empty list.
+        """
         query = db.session.query(SwordClientModel.client_id).distinct()
-        return query.all()
+        return [str(client.client_id) for client in query.all()]
 
 
     @classmethod
@@ -190,7 +194,10 @@ def get_clients_by_mapping_id(cls, mapping_id):
             mapping_id (int): Mapping ID.
 
         Returns:
-            SwordClient: Client object. If not found, return `None`.
+            SwordClient: Client object. If not found, return empty list.
         """
         query = SwordClientModel.query.filter_by(mapping_id=mapping_id)
-        return query.all()
+        return [
+            client for client in query.all()
+            if isinstance(client, SwordClientModel)
+        ]

From 9956e1f6cffaaaec6bd20dab78a1ee577c810fba Mon Sep 17 00:00:00 2001
From: ivis-kuroda 
Date: Wed, 7 May 2025 15:26:29 +0900
Subject: [PATCH 039/259] refactor: clarified the type and add unit test in
 weko-swordserver.

---
 .../weko-search-ui/weko_search_ui/utils.py    |   2 +-
 modules/weko-swordserver/requirements2.txt    |   1 +
 modules/weko-swordserver/tests/conftest.py    |  46 +-
 .../weko-swordserver/tests/test_decorators.py |  82 +-
 modules/weko-swordserver/tests/test_utils.py  | 291 ++++++-
 modules/weko-swordserver/tests/test_views.py  | 805 +++++++-----------
 .../weko_swordserver/config.py                |   5 +-
 .../weko_swordserver/decorators.py            |   4 +-
 .../weko_swordserver/utils.py                 |  38 +-
 .../weko_swordserver/views.py                 |  46 +-
 .../weko_workflow/headless/activity.py        |  15 +-
 scripts/instance.cfg                          |   6 +
 12 files changed, 769 insertions(+), 572 deletions(-)

diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py
index acd1538afb..a299c90dcd 100644
--- a/modules/weko-search-ui/weko_search_ui/utils.py
+++ b/modules/weko-search-ui/weko_search_ui/utils.py
@@ -2284,7 +2284,7 @@ def import_items_to_activity(item, request_info):
         )
         traceback.print_exc()
         url = headless.detail
-        recid = headless.recid
+        recid = str(headless.recid)
         current_action = headless.current_action
         error = str(ex)
 
diff --git a/modules/weko-swordserver/requirements2.txt b/modules/weko-swordserver/requirements2.txt
index 8995d6fb16..9938b4c3d1 100644
--- a/modules/weko-swordserver/requirements2.txt
+++ b/modules/weko-swordserver/requirements2.txt
@@ -276,6 +276,7 @@ webencodings==0.5.1
 -e ../weko-theme
 -e ../weko-user-profiles
 -e ../weko-workflow
+-e ../weko-workspace
 Werkzeug==0.15.2
 whichcraft==0.4.1
 WTForms==2.1
diff --git a/modules/weko-swordserver/tests/conftest.py b/modules/weko-swordserver/tests/conftest.py
index 2e67ca9ed5..9b0e69b5c6 100644
--- a/modules/weko-swordserver/tests/conftest.py
+++ b/modules/weko-swordserver/tests/conftest.py
@@ -248,8 +248,8 @@ def db(app):
 @pytest.fixture
 def tokens(app,users,db):
     scopes = [
-        "deposit:write deposit:actions",
-        "deposit:write deposit:actions user:activity",
+        "deposit:write deposit:actions item:create",
+        "deposit:write deposit:actions item:create user:activity",
         "deposit:write user:activity",
         ""
     ]
@@ -275,13 +275,12 @@ def tokens(app,users,db):
             access_token=jwt_create_token(user_id=user_id),
             expires=datetime.now() + timedelta(hours=10),
             is_personal=False,
-            is_internal=True,
+            is_internal=False,
             _scopes=scope
         )
 
         db.session.add(test_client)
         db.session.add(test_token)
-
         tokens.append({"token":test_token, "client":test_client, "scope":scope})
 
     db.session.commit()
@@ -289,6 +288,39 @@ def tokens(app,users,db):
     return tokens
 
 
+@pytest.fixture()
+def personal_token(app, users, db):
+    tokens = []
+
+    for i, user in enumerate(users):
+        user_id = str(user["id"])
+        test_client = Client(
+            client_id=f"dev{user_id}",
+            client_secret=f"dev{user_id}",
+            name="Test name",
+            description="test description",
+            user_id=user_id,
+            is_internal=True,
+        )
+        test_token = Token(
+            client=test_client,
+            user_id=user_id,
+            token_type="bearer",
+            access_token=jwt_create_token(user_id=user_id),
+            expires=datetime.now() + timedelta(hours=10),
+            is_personal=True,
+            is_internal=True,
+        )
+
+        db.session.add(test_client)
+        db.session.add(test_token)
+        tokens.append({"token":test_token, "client":test_client})
+
+    db.session.commit()
+
+    return tokens
+
+
 @pytest.fixture()
 def users(app, db):
     """Create users."""
@@ -563,7 +595,7 @@ def records(db,location):
 
 @pytest.fixture()
 def admin_settings(db):
-    settings = list()
+    settings = []
     settings.append(AdminSettings(id=1,name='items_display_settings',settings={"items_display_email": False, "items_search_author": "name", "item_display_open_date": False}))
     settings.append(AdminSettings(id=2,name='storage_check_settings',settings={"day": 0, "cycle": "weekly", "threshold_rate": 80}))
     settings.append(AdminSettings(id=3,name='site_license_mail_settings',settings={"auto_send_flag": False}))
@@ -573,7 +605,7 @@ def admin_settings(db):
     settings.append(AdminSettings(id=7,name="display_stats_settings",settings={"display_stats":False}))
     settings.append(AdminSettings(id=8,name='convert_pdf_settings',settings={"path":"/tmp/file","pdf_ttl":1800}))
     settings.append(AdminSettings(id=9,name="elastic_reindex_settings",settings={"has_errored": False}))
-    settings.append(AdminSettings(id=10,name="sword_api_setting",settings={"data_format": {"TSV": {"item_type": "15", "register_format": "Direct"}, "XML": {"workflow": "-1", "item_type": "15", "register_format": "Workflow"}}, "default_format": "TSV"}))
+    settings.append(AdminSettings(id=10,name="sword_api_setting",settings={"TSV/CSV": {"item_type": "15", "registration_type": "Direct", "duplicate_check": False}, "XML": {"workflow": "1", "item_type": "15", "registration_type": "Workflow", "duplicate_check": False}}))
     db.session.add_all(settings)
     db.session.commit()
     return settings
@@ -951,4 +983,4 @@ def sword_client(db, tokens, sword_mapping, workflow):
         {"sword_client": sword_client1},
         {"sword_client": sword_client2},
         {"sword_client": sword_client3}
-        ]
+    ]
diff --git a/modules/weko-swordserver/tests/test_decorators.py b/modules/weko-swordserver/tests/test_decorators.py
index b8ceafc74a..dd9907d136 100644
--- a/modules/weko-swordserver/tests/test_decorators.py
+++ b/modules/weko-swordserver/tests/test_decorators.py
@@ -128,6 +128,48 @@ def test_check_package_contents(app, client, make_crate, tokens, mocker):
             request.files = original
             request.headers = original_headers
 
+    # error message:"Packaging is required."
+    app.config["WEKO_SWORDSERVER_CONTENT_LENGTH"] = True
+    maxSize = app.config["WEKO_SWORDSERVER_SERVICEDOCUMENT_MAX_UPLOAD_SIZE"] = 10000
+    contentType = app.config.get(
+        "WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_ARCHIVE_FORMAT"
+    )
+    app.config["WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_PACKAGING"] = ["*"]
+    zip = make_crate()
+    mock_data = io.BytesIO(zip[0].read())
+    mock_data.seek(0, io.SEEK_END)
+    size = mock_data.tell()
+    mock_data.seek(0, 0)
+    mock_stream = MagicMock()
+    mock_stream.read = MagicMock(side_effect=mock_data.read)
+    mock_stream.seek = MagicMock(side_effect=mock_data.seek)
+    mock_stream.tell = MagicMock(side_effect=mock_data.tell)
+    mock_file = MagicMock(spec=FileStorage)
+    mock_file.filename = "mockfile.zip"
+    mock_file.stream = mock_stream
+    mock_file.seek = MagicMock(side_effect=mock_stream.seek)
+    mock_file.tell = MagicMock(side_effect=mock_stream.tell)
+    mock_file.headers = {"Content-Type": contentType[0]}
+
+    with app.test_request_context():
+        original = request.files
+        request.files = LocalProxy(lambda: {"file": mock_file})
+        original_headers = request.headers
+        request.headers = LocalProxy(
+            lambda: {
+                "Content-Length": str(size),
+                "Content-Type": contentType[0]
+            }
+        )
+        try:
+            with pytest.raises(WekoSwordserverException) as e:
+                res = check_package_contents()(lambda x, y: x + y)(x=1, y=2)
+            assert e.value.errorType == ErrorType.PackagingFormatNotAcceptable
+            assert e.value.message == "Packaging is required."
+        finally:
+            request.files = original
+            request.headers = original_headers
+
     # error message:"Not accept Content-Type:
     app.config["WEKO_SWORDSERVER_CONTENT_LENGTH"] = True
     maxSize = app.config["WEKO_SWORDSERVER_SERVICEDOCUMENT_MAX_UPLOAD_SIZE"] = 10000
@@ -186,7 +228,7 @@ def test_check_package_contents(app, client, make_crate, tokens, mocker):
     mock_file.stream = mock_stream
     mock_file.seek = MagicMock(side_effect=mock_stream.seek)
     mock_file.tell = MagicMock(side_effect=mock_stream.tell)
-    mock_file.headers = {"Content-Type": None}
+    mock_file.headers = {"Content-Type": contentType[0]}
 
     with app.test_request_context():
         original = request.files
@@ -393,3 +435,41 @@ def test_check_package_contents(app, client, make_crate, tokens, mocker):
         assert res == 3
         request.files = original
         request.headers = original_headers
+
+    # fake content length, and has option in content type
+    app.config["WEKO_SWORDSERVER_CONTENT_LENGTH"] = False
+    maxSize = app.config["WEKO_SWORDSERVER_SERVICEDOCUMENT_MAX_UPLOAD_SIZE"] = 10000
+    contentType = app.config.get(
+        "WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_ARCHIVE_FORMAT"
+    )
+    app.config["WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_PACKAGING"] = ["*"]
+    zip = make_crate()
+    mock_data = io.BytesIO(zip[0].read())
+    mock_data.seek(0, io.SEEK_END)
+    size = mock_data.tell()
+    mock_data.seek(0, 0)
+    mock_stream = MagicMock()
+    mock_stream.read = MagicMock(side_effect=mock_data.read)
+    mock_stream.seek = MagicMock(side_effect=mock_data.seek)
+    mock_stream.tell = MagicMock(side_effect=mock_data.tell)
+    mock_file = MagicMock(spec=FileStorage)
+    mock_file.filename = "mockfile.zip"
+    mock_file.stream = mock_stream
+    mock_file.seek = MagicMock(side_effect=mock_stream.seek)
+    mock_file.tell = MagicMock(side_effect=mock_stream.tell)
+    mock_file.headers = {"Content-Type": contentType[0] + ";charset=UTF-8"}
+    with app.test_request_context():
+        original = request.files
+        request.files = LocalProxy(lambda: {"file": mock_file})
+        original_headers = request.headers
+        request.headers = LocalProxy(
+            lambda: {
+                "Content-Length": "9999",
+                "Content-Type": contentType[0] + ";charset=UTF-8",
+                "Packaging": "http://purl.org/net/sword/3.0/package/Binary"
+            }
+        )
+        res = check_package_contents()(lambda x, y: x + y)(x=1, y=2)
+        assert res == 3
+        request.files = original
+        request.headers = original_headers
diff --git a/modules/weko-swordserver/tests/test_utils.py b/modules/weko-swordserver/tests/test_utils.py
index da295bb0f1..7959e9dd45 100644
--- a/modules/weko-swordserver/tests/test_utils.py
+++ b/modules/weko-swordserver/tests/test_utils.py
@@ -10,8 +10,12 @@
 from hashlib import sha256,sha512
 from zipfile import ZipFile
 from unittest.mock import MagicMock, patch
+from weko_accounts.models import ShibbolethUser
+from weko_admin.models import AdminSettings
 from weko_swordserver.utils import (
     check_import_file_format,
+    check_import_items,
+    get_shared_id_from_on_behalf_of,
     is_valid_file_hash,
     update_item_ids
 )
@@ -26,15 +30,14 @@
 # def check_import_file_format(file, packaging):
 # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_check_import_file_format -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
 def test_check_import_file_format(app):
-    # No 1
+    # SWORDBagIt; metadata/sword.json
     file_content = BytesIO()
     with ZipFile(file_content, 'w') as zip_file:
         zip_file.writestr('metadata/sword.json', '{}')
     file_content.seek(0)
     assert check_import_file_format(file_content, 'SWORDBagIt') == 'JSON'
 
-
-    # No 2
+    # SWORDBagIt; invalid json file
     file_content = BytesIO()
     with ZipFile(file_content, 'w') as zip_file:
         zip_file.writestr('metadata/invalid.json', '{}')
@@ -44,21 +47,44 @@ def test_check_import_file_format(app):
     assert e.value.errorType == ErrorType.MetadataFormatNotAcceptable
     assert e.value.message == "SWORDBagIt requires metadate/sword.json."
 
-    # No 3
+    # SWORDBagIt; mismatch packaging
+    file_content = BytesIO()
+    with ZipFile(file_content, 'w') as zip_file:
+        zip_file.writestr('metadata/sword.json', '{}')
+    file_content.seek(0)
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_file_format(file_content, 'SimpleZip')
+    assert e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    assert e.value.message == "packaging format is SimpleZip, but sword.json is found."
+
+    # RO-Crate; ro-crate-metadata.json
     file_content = BytesIO()
     with ZipFile(file_content, 'w') as zip_file:
-        zip_file.writestr('ro-crate-metadata.json', '{}')
+        zip_file.writestr('data/ro-crate-metadata.json', '{}')
     file_content.seek(0)
     assert check_import_file_format(file_content, 'SimpleZip') == 'JSON'
 
-    # No 4
+    # RO-Crate; invalid place
+    file_content = BytesIO()
+    with ZipFile(file_content, 'w') as zip_file:
+        zip_file.writestr('metadata/ro-crate-metadata.json', '{}')
+    file_content.seek(0)
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_file_format(file_content, 'SimpleZip')
+    assert e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    assert e.value.message == "ro-crate-metadata.json is required in data/ directory."
+
+    # Invalid json file
     file_content = BytesIO()
     with ZipFile(file_content, 'w') as zip_file:
         zip_file.writestr('invalid.json', '{}')
     file_content.seek(0)
-    assert check_import_file_format(file_content, 'SimpleZip') == 'OTHERS'
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_file_format(file_content, 'SimpleZip')
+    assert e.value.errorType == ErrorType.ContentMalformed
+    assert e.value.message == "SimpleZip requires ro-crate-metadata.json or other metadata file."
 
-    # No 5
+    # Invalid packaging format
     file_content = BytesIO()
     with ZipFile(file_content, 'w') as zip_file:
         zip_file.writestr('metadata/sword.json', '{}')
@@ -69,41 +95,256 @@ def test_check_import_file_format(app):
     assert e.value.errorType == ErrorType.PackagingFormatNotAcceptable
     assert e.value.message == f"Not accept packaging format: {packaging}"
 
+    # TSV
+    file_content = BytesIO()
+    with ZipFile(file_content, 'w') as zip_file:
+        zip_file.writestr('data/itemtype(1).tsv', '')
+    file_content.seek(0)
+    assert check_import_file_format(file_content, 'SimpleZip') == 'TSV/CSV'
+
+    # CSV
+    file_content = BytesIO()
+    with ZipFile(file_content, 'w') as zip_file:
+        zip_file.writestr('data/itemtype(1).csv', '')
+    file_content.seek(0)
+    assert check_import_file_format(file_content, 'SimpleZip') == 'TSV/CSV'
+
+    # XML
+    file_content = BytesIO()
+    with ZipFile(file_content, 'w') as zip_file:
+        zip_file.writestr('data/itemtype.xml', '')
+    file_content.seek(0)
+    assert check_import_file_format(file_content, 'SimpleZip') == 'XML'
+
+
+# def get_shared_id_from_on_behalf_of(on_behalf_of):
+# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_get_shared_id_from_on_behalf_of -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
+def test_get_shared_id_from_on_behalf_of(app, db, users, personal_token):
+    on_behalf_of = None
+    assert get_shared_id_from_on_behalf_of(on_behalf_of) == -1
+
+    on_behalf_of = users[3].get("email")
+    assert get_shared_id_from_on_behalf_of(on_behalf_of) == users[3]["id"]
+
+    on_behalf_of = personal_token[3]["token"].access_token
+    assert get_shared_id_from_on_behalf_of(on_behalf_of) == personal_token[3]["token"].user_id
+    assert get_shared_id_from_on_behalf_of(on_behalf_of) == users[3]["id"]
+
+    shib_user = ShibbolethUser(shib_eppn="test@example.ac.jp", shib_user_name="testuser", weko_uid=users[3]["id"])
+    db.session.add(shib_user)
+    db.session.commit()
+    on_behalf_of = shib_user.shib_eppn
+    assert get_shared_id_from_on_behalf_of(on_behalf_of) == users[3]["id"]
 
+    on_behalf_of = "invalid"
+    with pytest.raises(WekoSwordserverException) as e:
+        get_shared_id_from_on_behalf_of(on_behalf_of)
+    assert e.value.errorType == ErrorType.BadRequest
+    assert e.value.message == "No user found by On-Behalf-Of."
+
+    on_behalf_of = 999
+    with pytest.raises(WekoSwordserverException) as e:
+        get_shared_id_from_on_behalf_of(on_behalf_of)
+    assert e.value.errorType == ErrorType.ServerError
+    assert e.value.message == "Failed to get shared ID from On-Behalf-Of."
 
 # def is_valid_file_hash(expected_hash, file):
 # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_is_valid_file_hash -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
 def test_is_valid_file_hash():
-    # No 1
-    file_content = BytesIO(b'This is a test file content')
+    # correct hash
+    body = b"This is a test file content."
+    file_content = BytesIO(body)
     file_content.seek(0)
-    expected_hash = sha256(b'This is a test file content').hexdigest()
-    result = is_valid_file_hash(expected_hash,file_content)
-    assert result is True
+    expected_hash = sha256(body).hexdigest()
+    assert is_valid_file_hash(expected_hash, file_content)
 
-    # No 2
+    # incorrect hash
     file_content.seek(0)
     invalid_hash = sha256(b'Invalid content').hexdigest()
-    result = is_valid_file_hash(invalid_hash ,file_content)
-    assert result is False
+    assert not is_valid_file_hash(invalid_hash, file_content)
 
-    # No 3:
-    file_content = BytesIO(b'This is a test file content')
+    # invalid hash algorithm
+    file_content = BytesIO(body)
     file_content.seek(0)
-    expected_hash = sha512(b'This is a test file content').hexdigest()
-    result = is_valid_file_hash(expected_hash,file_content )
-    assert result is False
+    expected_hash = sha512(body).hexdigest()
+    assert not is_valid_file_hash(expected_hash, file_content)
 
     # No 4:
-    file_content = BytesIO(b'This is a test file content')
+    file_content = BytesIO(body)
     file_content.seek(0)
-    expected_hash = sha256(b'This is a test file content')
-    result = is_valid_file_hash(expected_hash,file_content )
-    assert result is False
+    expected_hash = sha256(body)
+    assert not is_valid_file_hash(expected_hash, file_content)
+
+
+# def check_import_items(file, file_format, is_change_identifier=False, shared_id=-1, **kwargs):
+# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_check_import_items -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
+def test_check_import_items(app, admin_settings, item_type, workflow, sword_client, mocker):
+    item_type_id = item_type[0]["item_type"].id
+    item_type_name = item_type[0]["item_type_name"].name
+    AdminSettings.update("sword_api_setting", {"TSV/CSV": {"active": True, "item_type": str(item_type_id), "registration_type": "Direct", "duplicate_check": False}})
+
+    # check tsv, direct registration, shared_id is -1
+    file_content = BytesIO()
+    mocker_tsv_check = mocker.patch("weko_swordserver.utils.check_tsv_import_items")
+    mocker_tsv_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}],
+    }
+    check_result = check_import_items(file_content, "TSV/CSV")
+
+    mocker_tsv_check.assert_called_once_with(file_content, False, shared_id=-1)
+    assert check_result["list_record"][0]["item_type_id"] == item_type_id
+    assert check_result["list_record"][0]["item_type_name"] == item_type_name
+    assert check_result["list_record"][0]["metadata"]["title"] == "test"
+    assert check_result["register_type"] == "Direct"
+    assert check_result["duplicate_check"] == False
+    assert check_result["weko_shared_id"] == -1
+
+    # check tsv, workflow registration, shared_id is 3
+    AdminSettings.update("sword_api_setting", {"TSV/CSV": {"active": True, "item_type": str(item_type_id), "registration_type": "Workflow", "duplicate_check": True}})
+    file_content = BytesIO()
+    mocker_tsv_check = mocker.patch("weko_swordserver.utils.check_tsv_import_items")
+    mocker_tsv_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}]
+    }
+    check_result = check_import_items(file_content, "TSV/CSV", True, 3)
+
+    mocker_tsv_check.assert_called_once_with(file_content, True, shared_id=3)
+    assert check_result["list_record"][0]["item_type_id"] == item_type_id
+    assert check_result["list_record"][0]["item_type_name"] == item_type_name
+    assert check_result["list_record"][0]["metadata"]["title"] == "test"
+    assert check_result["register_type"] == "Workflow"
+    assert check_result["workflow_id"] == workflow[0]["workflow"].id
+    assert check_result["duplicate_check"] == True
+    assert check_result["weko_shared_id"] == 3
+
+    # check jsonld, direct registration, shared_id is -1
+    client_id = sword_client[0]["sword_client"].client_id
+    mapping_id = sword_client[0]["sword_client"].mapping_id
+    file_content = BytesIO()
+    mocker_jsonld_check = mocker.patch("weko_swordserver.utils.check_jsonld_import_items")
+    mocker_jsonld_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}]
+    }
+    check_result = check_import_items(file_content, "JSON", False, -1, packaging="SimpleZip", client_id=client_id)
+
+    mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], -1, is_change_identifier=False)
+    assert check_result["list_record"][0]["item_type_id"] == item_type_id
+    assert check_result["list_record"][0]["item_type_name"] == item_type_name
+    assert check_result["list_record"][0]["metadata"]["title"] == "test"
+    assert check_result["register_type"] == "Direct"
+    assert check_result["duplicate_check"] == False
+    assert check_result["weko_shared_id"] == -1
+
+    # check jsonld, workflow registration, shared_id is 3
+    client_id = sword_client[1]["sword_client"].client_id
+    mapping_id = sword_client[1]["sword_client"].mapping_id
+    file_content = BytesIO()
+    mocker_jsonld_check = mocker.patch("weko_swordserver.utils.check_jsonld_import_items")
+    mocker_jsonld_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}]
+    }
+    check_result = check_import_items(file_content, "JSON", True, 3, packaging="SimpleZip", client_id=client_id)
+    mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], 3, is_change_identifier=True)
+    assert check_result["list_record"][0]["item_type_id"] == item_type_id
+    assert check_result["list_record"][0]["item_type_name"] == item_type_name
+    assert check_result["list_record"][0]["metadata"]["title"] == "test"
+    assert check_result["register_type"] == "Workflow"
+    assert check_result["workflow_id"] == sword_client[1]["sword_client"].workflow_id
+    assert check_result["duplicate_check"] == True
+    assert check_result["weko_shared_id"] == 3
+
+    # tsv, workflow not found
+    file_content = BytesIO()
+    mocker_tsv_check = mocker.patch("weko_swordserver.utils.check_tsv_import_items")
+    mocker_tsv_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}]
+    }
+    with patch("weko_swordserver.utils.WorkFlows.get_workflow_by_itemtype_id", return_value=[]):
+        check_result = check_import_items(file_content, "TSV/CSV", True, 3)
+    assert check_result["error"] == "Workflow not found for item type ID."
+
+    item_type_id = item_type[1]["item_type"].id
+    item_type_name = item_type[1]["item_type_name"].name
+    # xml, direct registration, not supported
+    AdminSettings.update("sword_api_setting", {"XML": {"active": True, "item_type": str(item_type_id), "registration_type": "Direct", "duplicate_check": False}})
+    file_content = BytesIO()
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_items(file_content, "XML")
+    assert e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    assert e.value.message == "Direct registration is not allowed for XML metadata yet."
+
+    # xml, workflow registration, shared_id is 3
+    AdminSettings.update("sword_api_setting", {"XML": {"active": True, "item_type": str(item_type_id), "registration_type": "Workflow", "workflow": str(workflow[1]["workflow"].id), "duplicate_check": True}})
+    file_content = BytesIO()
+    mocker_xml_check = mocker.patch("weko_swordserver.utils.check_xml_import_items")
+    mocker_xml_check.return_value = {
+        "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}]
+    }
+    check_result = check_import_items(file_content, "XML", True, 3)
+    mocker_xml_check.assert_called_once_with(file_content, item_type_id, shared_id=3)
+    assert check_result["list_record"][0]["item_type_id"] == item_type_id
+    assert check_result["list_record"][0]["item_type_name"] == item_type_name
+    assert check_result["list_record"][0]["metadata"]["title"] == "test"
+    assert check_result["register_type"] == "Workflow"
+    assert check_result["workflow_id"] == workflow[1]["workflow"].id
+    assert check_result["duplicate_check"] == True
+    assert check_result["weko_shared_id"] == 3
+
+    # import item format is not active
+    AdminSettings.update("sword_api_setting", {"TSV/CSV": {"active": False, "item_type": str(item_type_id), "registration_type": "Workflow", "duplicate_check": True}})
+    file_content = BytesIO()
+    mocker_tsv_check = mocker.patch("weko_swordserver.utils.check_tsv_import_items")
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_items(file_content, "TSV/CSV", True, 3)
+    e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    e.value.message == "TSV/CSV metadata import is not enabled."
+
+    AdminSettings.update("sword_api_setting", {})
+    file_content = BytesIO()
+    mocker_tsv_check = mocker.patch("weko_swordserver.utils.check_tsv_import_items")
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_items(file_content, "XML", True, 3)
+    e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    e.value.message == "XML metadata import is not enabled."
+
+    # xml, workflow not found
+    AdminSettings.update("sword_api_setting", {"XML": {"active": True, "item_type": str(item_type_id), "registration_type": "Workflow", "workflow": str(workflow[1]["workflow"].id), "duplicate_check": False}})
+    file_content = BytesIO()
+    with patch("weko_swordserver.utils.WorkFlows.get_workflow_by_id", return_value=None):
+        with pytest.raises(WekoSwordserverException) as e:
+            check_result = check_import_items(file_content, "XML", True, 3)
+    assert e.value.errorType == ErrorType.BadRequest
+    assert e.value.message == "Workflow not found for registration your item."
+
+    # jsonld, sword_client not found
+    client_id = "invalid_client_id"
+    file_content = BytesIO()
+    with pytest.raises(WekoSwordserverException) as e:
+        check_result = check_import_items(file_content, "JSON", True, 3, packaging="SimpleZip", client_id=client_id)
+    assert e.value.errorType == ErrorType.BadRequest
+    assert e.value.message == "No SWORD API setting found for client ID that you are using."
+
+    # jsonld, workflow not found
+    client_id = sword_client[1]["sword_client"].client_id
+    mapping_id = sword_client[1]["sword_client"].mapping_id
+    file_content = BytesIO()
+    with patch("weko_swordserver.utils.WorkFlows.get_workflow_by_id", return_value=None):
+        with pytest.raises(WekoSwordserverException) as e:
+            check_result = check_import_items(file_content, "JSON", True, 3, packaging="SimpleZip", client_id=client_id)
+    assert e.value.errorType == ErrorType.BadRequest
+    assert e.value.message == "Workflow not found for registration your item."
+
+    # invalid file format
+    file_content = BytesIO()
+    with pytest.raises(WekoSwordserverException) as e:
+        check_import_items(file_content, "InvalidFormat")
+    assert e.value.errorType == ErrorType.MetadataFormatNotAcceptable
+    assert e.value.message == "Unsupported file format: InvalidFormat"
 
 
 # def update_item_ids(list_record, new_id):
 # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_update_item_ids -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace
+@pytest.mark.skip()
 def test_update_item_ids(app, mocker):
     """
     update_item_ids 関数の動作をテストする。
diff --git a/modules/weko-swordserver/tests/test_views.py b/modules/weko-swordserver/tests/test_views.py
index 7dbd78e6a3..8bf42f40b5 100644
--- a/modules/weko-swordserver/tests/test_views.py
+++ b/modules/weko-swordserver/tests/test_views.py
@@ -54,105 +54,304 @@ def test_get_service_document(client,users,tokens):
         assert res.json["dc:title"] == "testRepositoryName2"
 
 # def post_service_document():
-# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test_post_service_document_json_ld -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp
-def test_post_service_document_json_ld(app,client,db,users,esindex,location,index,make_crate,tokens,item_type,doi_identifier,sword_mapping,sword_client,mocker):
+# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test_post_service_document -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp
+def test_post_service_document(app,client,db,users,make_crate,esindex,location,index,make_zip,tokens,item_type,doi_identifier,mocker,sword_mapping,sword_client):
     def update_location_size():
         loc = db.session.query(Location).filter(
                     Location.id == 1).one()
         loc.size = 1547
-    mocker.patch("weko_swordserver.views._get_status_document",side_effect=lambda x:{"recid":x})
-    mocker.patch("weko_search_ui.utils.find_and_update_location_size",side_effect=update_location_size)
-    mocker.patch("weko_search_ui.utils.send_item_created_event_to_es")
+    mocker.patch("weko_swordserver.views._get_status_document", side_effect=lambda id:{"recid": id})
+    mocker.patch("weko_swordserver.views._get_status_workflow_document", side_effect=lambda aid, id:{"activity": aid,"recid": id})
+    mocker.patch("weko_search_ui.utils.find_and_update_location_size", side_effect=update_location_size)
     mocker.patch("weko_swordserver.views.dbsession_clean")
 
     token_direct = tokens[0]["token"].access_token
     token_workflow = tokens[1]["token"].access_token
-    token_none = tokens[3]["token"].access_token
     url = url_for("weko_swordserver.post_service_document")
 
-    # Digest VERIFICATION ON
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-
-    login_user_via_session(client=client,email=users[0]["email"])
     # Direct registration
-    zip, _ = make_crate()
-    storage = FileStorage(filename="payload.zip",stream=zip)
-    mapped_json = json_data("data/item_type/mapped_json_2.json")
+    login_user_via_session(client=client, email=users[0]["email"])
+    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = False
     headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-        "Digest":"SHA-256={}".format(calculate_hash(storage))
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+        "On-Behalf-Of": "test_on_behalf_of",
     }
-    with patch("weko_swordserver.mapper.WekoSwordMapper.map",return_value=mapped_json):
-        with patch("weko_swordserver.registration.bagit.Bag.validate"):
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 200
-        recid = res.json["recid"]
-        recid = PersistentIdentifier.get("recid",recid)
-        record = RecordMetadata.query.filter_by(id=recid.object_uuid).one_or_none()
-        assert record is not None
-        record = record.json
-        file_metadata = record["item_1617604990215"]["attribute_value_mlt"][0]
-        assert file_metadata.get("url") is not None
-        assert file_metadata.get("url").get("url") == f"https://localhost/record/{recid.id}/files/sample.rst"
+    zip = make_zip()
+    storage=FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "new"}]
+    }
+    mocker.patch("weko_swordserver.views.import_items_to_system", return_value={"success": True, "recid": 2000001})
+    mocker.patch("weko_items_ui.utils.send_mail_direct_registered")
+    os.makedirs("/var/tmp/test", exist_ok=True)
 
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 200
+    assert result.json.get("recid") == "2000001"
+    assert not os.path.exists("/var/tmp/test"), os.rmdir("/var/tmp/test")
 
-    # invalid hash and be rejected
+    # Workflow registration, duplicate check
+    login_user_via_session(client=client, email=users[1]["email"])
     app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    zip, _ = make_crate()
-    storage = FileStorage(filename="payload.zip",stream=zip)
     headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-        "Digest":"SHA-256=1NVAL1DHASHTEST"
+        "Authorization": "Bearer {}".format(token_workflow),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+        "On-Behalf-Of": "test_on_behalf_of"
     }
-    with patch("weko_swordserver.mapper.WekoSwordMapper.map",return_value=mapped_json):
-        with patch("weko_swordserver.registration.bagit.Bag.validate"):
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 412
-        assert res.json.get("@type") == "DigestMismatch"
-        assert res.json.get("error") == "Request body and digest verification failed."
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Workflow",
+        "workflow_id": 1001,
+        "list_record": [{"status": "new", "metadata": {}}],
+        "duplicate_check": True
+    }
+    mocker.patch("weko_items_ui.utils.check_duplicate", return_value=(False, [], []))
+    mocker.patch("weko_swordserver.views.import_items_to_activity", return_value=(url_for("weko_workflow.display_activity", activity_id="A-TEST-00001"), "2000001", "end_action", None))
 
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 200
+    assert result.json.get("recid") == "2000001"
 
-    # invalid hash but setting is off
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = False
 
-    # Workflow registration
-    login_user_via_session(client=client,email=users[0]["email"])
+    # invalid Content-Disposition's filename
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=invalid_payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "Not found invalid_payload.zip in request body."
+
+    # invalid Content-Disposition
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "invalid",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "Cannot get filename by Content-Disposition."
+
+    # Workflow registration, not have activity sqope
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Workflow",
+        "list_record": [{"status": "new", "metadata": {}}],
+        "duplicate_check": True
+    }
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 403
+    assert result.json.get("error") == "Not allowed operation in your token scope."
+
+    # error in result
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "error": "Unexpected error.",
+    }
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "Item check error: Unexpected error."
+
+    # error in item
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "new", "errors": ["Item check error."]}],
+    }
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "Item check error: Item check error."
+
+    # not new item
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "keep", "item_title": "test_title"}],
+    }
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "This item is already registered: test_title."
+
+    # item duplicated
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "new", "metadata": {},}],
+        "duplicate_check": True
+    }
+    mocker.patch("weko_items_ui.utils.check_duplicate", return_value=(True, ["2000001"], ["/records/2000001"]))
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 400
+    assert result.json.get("error") == "Some similar items are already registered: ['/records/2000001']."
+
+    # failed to import to system
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "new", "metadata": {},}]
+    }
+    with patch("weko_swordserver.views.import_items_to_system", return_value={"success": False, "error_id": "Failed to import to system."}):
+        result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+        assert result.status_code == 500
+        assert result.json.get("error") == "Failed to import item."
+
+    # unexpected error in import to system
+    login_user_via_session(client=client, email=users[0]["email"])
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+    }
+    zip = make_zip()
+    storage = FileStorage(filename="payload.zip", stream=zip)
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Workflow",
+        "list_record": [{"status": "new", "metadata": {},}]
+    }
+
+    # jsonid format
+    login_user_via_session(client=client, email=users[0]["email"])
+    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
     zip, _ = make_crate()
-    storage = FileStorage(filename="payload.zip",stream=zip)
+    storage = FileStorage(filename="payload.zip", stream=zip)
     headers = {
-        "Authorization":"Bearer {}".format(token_workflow),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-        "Digest":"SHA-256={}".format(calculate_hash(storage))
-    }
-    detail = "http://test_server.localdomain/workflow/activity/detail/A-TEST-00002"
-    current_action = "item_login"
-    recid = 200001
-    mapped_json = json_data("data/item_type/mapped_json_2.json")
-    with patch("weko_swordserver.views.import_items_to_activity", return_value=(detail, recid, current_action)):
-        with patch("weko_swordserver.mapper.WekoSwordMapper.map",return_value=mapped_json):
-            with patch("weko_swordserver.registration.bagit.Bag.validate"):
-                res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 200
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+        "Digest": "SHA-256={}".format(calculate_hash(storage)),
+    }
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="JSON")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker.patch("weko_swordserver.views.is_valid_file_hash", return_value=True)
+    mocker_check_item = mocker.patch("weko_swordserver.views.check_import_items")
+    mocker_check_item.return_value = {
+        "data_path": "/var/tmp/test",
+        "register_type": "Direct",
+        "list_record": [{"status": "new", "metadata": {}}],
+    }
 
-    # no scopes
-    zip, _  = make_crate()
-    storage = FileStorage(filename="payload.zip",stream=zip)
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 200
+    assert result.json.get("recid") == "2000001"
+
+    # digest mismatch
+    login_user_via_session(client=client, email=users[0]["email"])
+    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
+    zip, _ = make_crate()
+    storage = FileStorage(filename="payload.zip", stream=zip)
     headers = {
-        "Authorization":"Bearer {}".format(token_none),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-        "Digest":"SHA-256={}".format(calculate_hash(storage))
-    }
-    mapped_json = json_data("data/item_type/mapped_json_2.json")
-    with patch("weko_swordserver.mapper.WekoSwordMapper.map",return_value=mapped_json):
-        with patch("weko_swordserver.registration.bagit.Bag.validate"):
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 403
+        "Authorization": "Bearer {}".format(token_direct),
+        "Content-Disposition": "attachment; filename=payload.zip",
+        "Packaging": "http://purl.org/net/sword/3.0/package/SimpleZip",
+        "Digest": "SHA-256={}".format(calculate_hash(storage)),
+    }
+    mocker.patch("weko_swordserver.views.check_import_file_format", return_value="JSON")
+    mocker.patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value=-1)
+    mocker.patch("weko_swordserver.views.is_valid_file_hash", return_value=False)
+
+    result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers)
+    assert result.status_code == 412
+    assert result.json.get("error") == "Request body and digest verification failed."
 
 
 # def get_status_document(recid):
@@ -345,24 +544,59 @@ def test__get_status_workflow_document(app, records):
 
 # def delete_item(recid):
 # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test_delete_item -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp
-def test_delete_item(client, tokens, users,es_records):
+def test_delete_item(app, client, db, tokens, sword_client, users,es_records, mocker):
+    mocker.patch("weko_swordserver.views._get_status_document", side_effect=lambda id:{"recid": id})
+    mocker.patch("weko_swordserver.views.dbsession_clean")
+    mocker.patch("weko_items_ui.utils.send_mail_item_deleted")
+
+    token_direct = tokens[0]["token"].access_token
+    token_workflow = tokens[1]["token"].access_token
+
+    # direct deletion
     login_user_via_session(client=client,email=users[0]["email"])
-    token = tokens[0]["token"].access_token
-    delete_item = es_records[0][0].pid_value
-    url = url_for("weko_swordserver.delete_item",recid=delete_item)
+    tokens[0]["token"]._scopes = "deposit:write item:delete"
+    tokens[1]["token"]._scopes = "deposit:write item:delete user:activity"
+    db.session.commit()
+
+    mock_record = MagicMock()
+    mock_record.pid_doi = None
+    mocker.patch("weko_swordserver.views.WekoRecord.get_record_by_pid", return_value=mock_record)
+    mocker.patch("weko_swordserver.views.get_deletion_type", return_value={"deletion_type": "Direct"})
+    mock_soft_delete = mocker.patch("weko_swordserver.views.soft_delete")
+
+    url = url_for("weko_swordserver.delete_object", recid=2000001)
     headers = {
-        "Authorization":"Bearer {}".format(token),
+        "Authorization":"Bearer {}".format(token_direct),
     }
 
     res = client.delete(url, headers=headers)
     assert res.status_code == 204
-    target = PersistentIdentifier.query.filter_by(pid_type="recid",pid_value=delete_item).first()
-    assert target.status == "D"
+    mock_soft_delete.assert_called_once_with("2000001")
+
+    # workflow deletion, not have activity scope
+    login_user_via_session(client=client,email=users[1]["email"])
+    mocker.patch("weko_swordserver.views.get_deletion_type", return_value={"deletion_type": "Workflow"})
+
+    url = url_for("weko_swordserver.delete_object", recid=2000001)
+    headers = {
+        "Authorization": "Bearer {}".format(token_direct)
+    }
+    res = client.delete(url, headers=headers)
+    assert res.status_code == 403
+    assert res.json.get("error") == "Not allowed operation in your token scope."
+
+    # workflow deletion, have activity scope
+    login_user_via_session(client=client,email=users[1]["email"])
+    mock_delete_with_activity = mocker.patch("weko_swordserver.views.delete_items_with_activity")
+    headers = {
+        "Authorization": "Bearer {}".format(token_workflow)
+    }
+
+    res = client.delete(url, headers=headers)
+    print(res.json)
+    assert res.status_code == 202
+    mock_delete_with_activity.assert_called_once
 
-    # coverage - Exception
-    with patch("weko_swordserver.views.soft_delete", side_effect=Exception):
-        res = client.delete(url, headers=headers)
-        assert res.status_code == 204
 
 # def _create_error_document(type, error):
 # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test__create_error_document -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp
@@ -454,404 +688,3 @@ def test_handle_weko_swordserver_exception(client,sessionlifetime):
         res = client.get(url)
         assert res.status_code == 400
         assert res.json == {"type":"BadRequest","msg":"this is test BadRequest exception"}
-
-
-# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test_post_service_document -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp
-def test_post_service_document(app,client,db,users,make_crate,esindex,location,index,make_zip,tokens,item_type,doi_identifier,mocker,sword_mapping,sword_client,admin_settings):
-    token_direct = tokens[0]["token"].access_token
-    token_workflow = tokens[1]["token"].access_token
-    token_none = tokens[3]["token"].access_token
-    admin_settings[9].settings = {"data_format": {"TSV": {"item_type": "1", "register_format": "Direct"}, "XML": {"workflow": "1", "item_type": "1", "register_format": "Workflow"}}, "default_format": "TSV"}
-    db.session.commit()
-    url = url_for("weko_swordserver.post_service_document")
-
-    # ケース1: Content-Dispositionが不正
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"inline",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-    assert res.status_code == 400
-    assert json.loads(res.data).get("@type") == "BadRequest"
-    assert json.loads(res.data).get("error") == "Cannot get filename by Content-Disposition."
-
-    # ケース2: filenameがNone
-    url = url_for("weko_swordserver.post_service_document")
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-    assert res.status_code == 400
-    assert json.loads(res.data).get("@type") == "BadRequest"
-    assert json.loads(res.data).get("error") == "Cannot get filename by Content-Disposition."
-
-    # ケース3: ファイルが見つからない
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=invalid.txt",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-    assert res.status_code == 400
-    assert json.loads(res.data).get("@type") == "BadRequest"
-    assert json.loads(res.data).get("error") == "Not found invalid.txt in request body."
-
-    # ケース4: Digestなし (JSON)
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    login_user_via_session(client=client,email=users[0]["email"])
-    with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-        zip, _ = make_crate()
-        storage = FileStorage(filename="payload.zip", stream=zip)
-        headers = {
-            "Authorization":"Bearer {}".format(token_direct),
-            "Content-Disposition":"attachment; filename=payload.zip",
-            "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-        }
-
-        # POSTリクエストを送信
-        res = client.post(
-            url,
-            data=dict(file=storage),
-            content_type="multipart/form-data",
-            headers=headers,
-        )
-
-        assert res.status_code == 412
-        assert json.loads(res.data).get("@type") == "DigestMismatch"
-        assert json.loads(res.data).get("error") == "Request body and digest verification failed."
-
-    # ケース5: DigestがSHA-256でない
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-        zip, _ = make_crate()
-        storage = FileStorage(filename="payload.zip", stream=zip)
-        headers = {
-            "Authorization":"Bearer {}".format(token_direct),
-            "Content-Disposition":"attachment; filename=payload.zip",
-            "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-            "Digest":"Md5=1234567890"
-        }
-
-        # POSTリクエストを送信
-        res = client.post(
-            url,
-            data=dict(file=storage),
-            content_type="multipart/form-data",
-            headers=headers,
-        )
-
-        assert res.status_code == 412
-        assert json.loads(res.data).get("@type") == "DigestMismatch"
-        assert json.loads(res.data).get("error") == "Request body and digest verification failed."
-
-    # ケース6: Digest不一致
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-        zip, _ = make_crate()
-        storage = FileStorage(filename="payload.zip", stream=zip)
-        headers = {
-            "Authorization":"Bearer {}".format(token_direct),
-            "Content-Disposition":"attachment; filename=payload.zip",
-            "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-            "Digest":"SHA-256=1234567890"
-        }
-
-        # POSTリクエストを送信
-        res = client.post(
-            url,
-            data=dict(file=storage),
-            content_type="multipart/form-data",
-            headers=headers,
-        )
-
-        assert res.status_code == 412
-        assert json.loads(res.data).get("@type") == "DigestMismatch"
-        assert json.loads(res.data).get("error") == "Request body and digest verification failed."
-
-    # ケース6: Digest一致
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    login_user_via_session(client=client,email=users[0]["email"])
-    with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-        zip, _ = make_crate()
-        storage = FileStorage(filename="payload.zip", stream=zip)
-        headers = {
-            "Authorization":"Bearer {}".format(token_direct),
-            "Content-Disposition":"attachment; filename=payload.zip",
-            "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-            "Digest":"SHA-256={}".format(calculate_hash(storage))
-        }
-        with patch("weko_swordserver.views.check_import_items", return_value={
-            "data_path": "/tmp/test",
-            "list_record": [{"status": "new"}],
-            "register_type": "Direct"
-        }):
-            with patch("weko_swordserver.views.get_shared_id_from_on_behalf_of", return_value="share_id"):
-                with patch("weko_swordserver.views.import_items_to_system", return_value={"success": True,  "recid": "recid_test"}):
-                    with patch("weko_swordserver.views._get_status_document", return_value={"status": "created"}):
-                        res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-                        assert json.loads(res.data)=={"status": "created"}
-
-    # ケース6: registration typeでエラー かつ data_pathが存在している場合
-    app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True
-    login_user_via_session(client=client,email=users[0]["email"])
-    with patch('shutil.rmtree') as mock_rmtree:
-        with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-            zip, _ = make_crate()
-            storage = FileStorage(filename="payload.zip", stream=zip)
-            headers = {
-                "Authorization":"Bearer {}".format(token_direct),
-                "Content-Disposition":"attachment; filename=payload.zip",
-                "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-                "Digest":"SHA-256={}".format(calculate_hash(storage))
-            }
-            with patch("weko_swordserver.views.check_import_items", return_value={
-                "data_path": "/tmp/test",
-                "list_record": [{"status": "new"}]
-            }):
-                with patch("os.path.exists", return_value=True):
-                    res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-                    assert res.status_code == 500
-                    assert json.loads(res.data).get("@type") == "ServerError"
-                    assert json.loads(res.data).get("error") == "Invalid register type in admin settings"
-                    mock_rmtree.assert_called_with("/tmp/test")
-
-    # ケース7: registration typeでエラー かつ data_pathが存在していない場合
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test_data_path",
-        "list_record": [{"status": "new", "item_title": "Test Item"}]
-    }):
-        res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 500
-        assert json.loads(res.data).get("@type") == "ServerError"
-        assert json.loads(res.data).get("error") == "Invalid register type in admin settings"
-
-
-    # ケース8: check_import_itemsでエラー
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test_data_path",
-        "register_type": "Direct",
-        "list_record": [{"errors": "error example", "item_title": "Test Item"}]
-    }):
-        res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 400
-        assert json.loads(res.data).get("@type") == "ContentMalformed"
-        assert "Error in check_import_items:" in json.loads(res.data).get("error")
-
-    # ケース9: 既存アイテム
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test_data_path",
-        "register_type": "Direct",
-        "list_record":[{"status": "existing", "item_title": "Test Item"}],
-    }):
-        res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-        assert res.status_code == 400
-        assert json.loads(res.data).get("@type") == "BadRequest"
-        assert json.loads(res.data).get("error") == "This item is already registered: Test Item"
-
-    # ケース10: Direct - import失敗
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"}],
-        "register_type": "Direct"
-    }):
-        with patch("weko_swordserver.views.import_items_to_system", return_value={"success": False, "error_id": "err1"}):
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-            assert res.status_code == 500
-            assert json.loads(res.data).get("@type") == "ServerError"
-            assert json.loads(res.data).get("error") == "An error occurred by importing Item!"
-
-    # ケース11: Direct - 成功
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"}],
-        "register_type": "Direct"
-    }):
-        with patch("weko_swordserver.views.import_items_to_system", return_value={"success": True,  "recid": "recid_test"}):
-            with patch("weko_swordserver.views._get_status_document", return_value={"status": "created"}):
-                res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-                assert json.loads(res.data)=={"status": "created"}
-
-    # # ケース12: Workflow - activity スコープ不足
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"}],
-        "register_type": "Workflow"
-    }):
-        # POSTリクエストを送信
-        res = client.post(
-            url_for("weko_swordserver.post_service_document"),
-            data=dict(file=storage),
-            content_type="multipart/form-data",
-            headers=headers,
-        )
-        # レスポンスの検証
-        print("res.data:",res.data)
-        assert res.status_code == 403
-        assert res.json.get("@type") == "Forbidden"
-        assert res.json.get("error") == "Not allowed operation in your token scope."
-
-    # ケース13: Workflow - importエラー
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_workflow),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"}],
-        "register_type": "Workflow"
-    }):
-        with patch("weko_swordserver.views.import_items_to_activity", return_value=(
-            "http://test_server.localdomain/workflow/activity/detail/A-TEST-00002",
-            "test_recid",
-            "item_login",
-            True
-            )):
-
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-            assert "An error occurred. Please open the following URL to " \
-                "continue with the remaining operations." \
-                "http://test_server.localdomain/workflow/activity/detail/A-TEST-00002: " \
-                "Item id: test_recid." in json.loads(res.data).get("error")
-
-    # ケース14: Workflow - 成功
-    login_user_via_session(client=client,email=users[0]["email"])
-    with patch('shutil.rmtree') as mock_rmtree:
-        mock_rmtree.reset_mock()
-        with patch("weko_swordserver.views.check_import_file_format", return_value="JSON"):
-            zip = make_zip()
-            storage=FileStorage(filename="payload.zip",stream=zip)
-            headers = {
-                "Authorization":"Bearer {}".format(token_workflow),
-                "Content-Disposition":"attachment; filename=payload.zip",
-                "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip",
-                "Digest":"SHA-256={}".format(calculate_hash(storage))
-            }
-            test_dir = "/tmp/test"
-            os.makedirs(test_dir, exist_ok=True)
-            with patch("weko_swordserver.views.check_import_items", return_value={
-                "data_path": test_dir,
-                "list_record": [{"status": "new"}],
-                "register_type": "Workflow"
-            }):
-
-                with patch("weko_swordserver.views.import_items_to_activity", return_value=(
-                    "http://test_server.localdomain/workflow/activity/detail/A-TEST-00002",
-                    "test_recid",
-                    "item_login",
-                    False
-                    )):
-                    res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-                    mock_rmtree.assert_called_with("/tmp/test")
-                    assert "An error occurred. " not in json.loads(res.data)
-                    shutil.rmtree(test_dir)
-
-    # ケース15: 複数なWorkflow - 一番目失敗、二番目成功
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_workflow),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"},{"status": "new"}],
-        "register_type": "Workflow"
-    }):
-        with patch("weko_swordserver.views.import_items_to_activity", side_effect=[(
-            "http://test_server.localdomain/workflow/activity/detail/A-TEST-00002",
-            "test_recid",
-            "item_login",
-            False),
-            ("http://test_server.localdomain/workflow/activity/detail/A-TEST-00001",
-            "test_recid",
-            "item_login",
-            True)
-            ]):
-
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-            assert "An error occurred. " not in json.loads(res.data)
-
-    # ケース18: process_item - 予期しない例外
-    login_user_via_session(client=client,email=users[0]["email"])
-    headers = {
-        "Authorization":"Bearer {}".format(token_direct),
-        "Content-Disposition":"attachment; filename=payload.zip",
-        "Packaging":"http://purl.org/net/sword/3.0/package/SimpleZip"
-    }
-    zip = make_zip()
-    storage=FileStorage(filename="payload.zip",stream=zip)
-    with patch("weko_swordserver.views.check_import_items", return_value={
-        "data_path": "/tmp/test",
-        "list_record": [{"status": "new"}],
-        "register_type": "Direct"
-    }):
-        with patch("weko_swordserver.views.import_items_to_system", side_effect=Exception("Exception")):
-            res = client.post(url, data=dict(file=storage),content_type="multipart/form-data",headers=headers)
-            assert json.loads(res.data).get("@type") == "NotFound"
-            assert json.loads(res.data).get("error") =="Item not found. (recid=None)"
diff --git a/modules/weko-swordserver/weko_swordserver/config.py b/modules/weko-swordserver/weko_swordserver/config.py
index ae5bb03345..136ec7ec6e 100644
--- a/modules/weko-swordserver/weko_swordserver/config.py
+++ b/modules/weko-swordserver/weko_swordserver/config.py
@@ -36,7 +36,10 @@
 ]
 """ List of Metadata Formats which are acceptable to the server. """
 
-WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_PACKAGING = ["*"]
+WEKO_SWORDSERVER_SERVICEDOCUMENT_ACCEPT_PACKAGING = [
+    "http://purl.org/net/sword/3.0/package/SimpleZip",
+    "http://purl.org/net/sword/3.0/package/SWORDBagIt",
+]
 """ List of Packaging Formats which are acceptable to the server.
 
     ["*"] or List of Packaging Formats URI
diff --git a/modules/weko-swordserver/weko_swordserver/decorators.py b/modules/weko-swordserver/weko_swordserver/decorators.py
index a33796cb69..92a8d74d54 100644
--- a/modules/weko-swordserver/weko_swordserver/decorators.py
+++ b/modules/weko-swordserver/weko_swordserver/decorators.py
@@ -136,10 +136,8 @@ def decorated(*args, **kwargs):
             failedContentType = None
             if reqContentType not in acceptArchiveFormat:
                 failedContentType = reqContentType
-                if filesContentType is not None:
+                if filesContentType not in acceptArchiveFormat:
                     failedContentType = filesContentType
-                    if filesContentType in acceptArchiveFormat:
-                        failedContentType = None
             if failedContentType is not None:
                 current_app.logger.error(
                     f"Not accept Content-Type: {failedContentType}"
diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py
index 7e9a4d8f94..ecf9258b52 100644
--- a/modules/weko-swordserver/weko_swordserver/utils.py
+++ b/modules/weko-swordserver/weko_swordserver/utils.py
@@ -95,11 +95,11 @@ def check_import_file_format(file, packaging):
             file_format = "TSV/CSV"
         else:
             current_app.logger.error(
-                "ro-crate-metadata.json or other metadata file is not found."
+                "Metadata file is not found in SimpleZip."
             )
             raise WekoSwordserverException(
                 "SimpleZip requires ro-crate-metadata.json or other metadata file.",
-                ErrorType.MetadataFormatNotAcceptable
+                ErrorType.ContentMalformed
                 )
     else:
         current_app.logger.error(
@@ -126,7 +126,8 @@ def get_shared_id_from_on_behalf_of(on_behalf_of):
 
     Raises:
         WekoSwordserverException:
-            If an error occurs while searching user by On-Behalf-Of.
+            - If no user found by On-Behalf-Of.
+            - If an error occurs while searching user by On-Behalf-Of.
     """
     shared_id = -1
     if on_behalf_of is None:
@@ -135,21 +136,21 @@ def get_shared_id_from_on_behalf_of(on_behalf_of):
     try:
         # get weko user id from email
         user = User.query.filter_by(email=on_behalf_of).one_or_none()
-        shared_id = user.id if user is not None else None
+        shared_id = int(user.id) if user is not None else None
         if shared_id is None:
             # get weko user id from personal access token
             token = (
                 Token.query
                 .filter_by(access_token=on_behalf_of).one_or_none()
             )
-            shared_id = token.user_id if token is not None else None
+            shared_id = int(token.user_id) if token is not None else None
         if shared_id is None:
             # get weko user id from shibboleth user eppn
             shib_user = (
                 ShibbolethUser.query
                 .filter_by(shib_eppn=on_behalf_of).one()
             )
-            shared_id = shib_user.weko_uid if shib_user is not None else None
+            shared_id = int(shib_user.weko_uid)
     except NoResultFound as ex:
         msg = "No user found by On-Behalf-Of."
         current_app.logger.error(msg)
@@ -158,16 +159,17 @@ def get_shared_id_from_on_behalf_of(on_behalf_of):
             msg, errorType=ErrorType.BadRequest
         ) from ex
     except SQLAlchemyError as ex:
-        msg = "DB error occurred while searching user by On-Behalf-Of."
-        current_app.logger.error(msg)
+        current_app.logger.error(
+            "DB error occurred while searching user by On-Behalf-Of."
+        )
         traceback.print_exc()
         raise WekoSwordserverException(
-            msg, errorType=ErrorType.ServerError
+            "Failed to get shared ID from On-Behalf-Of.",
+            errorType=ErrorType.ServerError
         ) from ex
     return shared_id
 
 
-
 def is_valid_file_hash(expected_hash, file):
     """Validate body hash.
 
@@ -182,6 +184,9 @@ def is_valid_file_hash(expected_hash, file):
     Returns:
         bool: Check result.
     """
+    if not isinstance(expected_hash, str):
+        return False
+
     file.seek(0)
     hasher = sha256()
     for chunk in iter(lambda: file.read(4096), b""):
@@ -249,12 +254,13 @@ def check_import_items(
                     f"Workflow not found for item type ID: {item_type_name}"
                 )
                 error = check_result.get("error", "")
-                error += "; Workflow not found for item type ID."
+                error_ = "Workflow not found for item type ID."
+                error += f"; {error_}" if error else error_
                 check_result.update({"error": error})
-
-            workflow = list_workflow[0]
-            workflow_id = workflow.id
-            check_result.update({"workflow_id": workflow_id})
+            else:
+                workflow = list_workflow[0]
+                workflow_id = workflow.id
+                check_result.update({"workflow_id": workflow_id})
 
     elif file_format == "XML":
         if register_type == "Direct":
@@ -292,7 +298,7 @@ def check_import_items(
             )
             raise WekoSwordserverException(
                 "No SWORD API setting found for client ID that you are using.",
-                errorType=ErrorType.ServerError
+                errorType=ErrorType.BadRequest
             )
         mapping_id = sword_client.mapping_id
         workflow_id = sword_client.workflow_id
diff --git a/modules/weko-swordserver/weko_swordserver/views.py b/modules/weko-swordserver/weko_swordserver/views.py
index e54ab3466b..2b1c5cd29d 100644
--- a/modules/weko-swordserver/weko_swordserver/views.py
+++ b/modules/weko-swordserver/weko_swordserver/views.py
@@ -264,7 +264,7 @@ def post_service_document():
         and current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"]
     ):
         if (
-            digest is None
+            not isinstance(digest, str)
             or not digest.startswith("SHA-256=")
             or not is_valid_file_hash(digest.split("SHA-256=")[-1], file)
         ):
@@ -306,7 +306,7 @@ def post_service_document():
         )
 
     # Validate items in the check result
-    for item in check_result.get("list_record", [{}]):
+    for item in check_result.get("list_record") or [{}]:
         if not item or item.get("errors"):
             error_msg = (
                 ", ".join(item.get("errors"))
@@ -323,20 +323,21 @@ def post_service_document():
                 f"This item is already registered: {item.get('item_title')}"
             )
             raise WekoSwordserverException(
-                f"This item is already registered: {item.get('item_title')}",
+                f"This item is already registered: {item.get('item_title')}.",
                 ErrorType.BadRequest,
             )
 
-        from weko_items_ui.utils import check_duplicate
-        result, list_id, list_url = check_duplicate(item["metadata"], is_item=True)
-        if check_result.get("duplicate_check", False) and result:
-            current_app.logger.error(
-                f"New item appears to be a duplicate: {list_id}"
-            )
-            raise WekoSwordserverException(
-                f"Some similar items are already registered: {list_url}",
-                ErrorType.BadRequest,
-            )
+        if check_result.get("duplicate_check", False):
+            from weko_items_ui.utils import check_duplicate
+            result, list_id, list_url = check_duplicate(item["metadata"], is_item=True)
+            if result:
+                current_app.logger.error(
+                    f"New item appears to be a duplicate: {list_id}"
+                )
+                raise WekoSwordserverException(
+                    f"Some similar items are already registered: {list_url}.",
+                    ErrorType.BadRequest,
+                )
 
     # Prepare request information
     owner = -1
@@ -362,7 +363,7 @@ def process_item(item, request_info):
         Returns:
             tuple: A tuple containing the response and the record ID.
         """
-        activity_id, recid, error = None, None, False
+        activity_id, recid, error = None, None, None
         if register_type == "Direct":
             import_result = import_items_to_system(
                 item, request_info=request_info
@@ -371,9 +372,9 @@ def process_item(item, request_info):
                 current_app.logger.error(
                     f"Error in import_items_to_system: {item.get('error_id')}"
                 )
-                error = True
+                error = str(item.get('error_id'))
             else:
-                recid = import_result.get("recid")
+                recid = str(import_result.get("recid"))
                 from weko_items_ui.utils import send_mail_direct_registered
                 send_mail_direct_registered(recid, current_user.id)
 
@@ -381,7 +382,7 @@ def process_item(item, request_info):
             url, recid, _ , error = import_items_to_activity(
                 item, request_info=request_info
             )
-            activity_id = url.split("/")[-1]
+            activity_id = str(url.split("/")[-1])
 
         return activity_id, recid, error
 
@@ -400,7 +401,9 @@ def process_item(item, request_info):
                 update_item_ids(
                     check_result["list_record"], recid, item.get("_id"))
         except Exception as ex:
-            current_app.logger.error(f"Unexpected error during item import: {ex}")
+            current_app.logger.error(f"Unexpected error: {ex}")
+            traceback.print_exc()
+            warns.append(("Unexpected error: {ex}", activity_id, recid))
             continue  # Skip to the next iteration
 
     # Clean up temporary directory
@@ -414,7 +417,7 @@ def process_item(item, request_info):
     if len(warns) > 0:
         if register_type == "Direct":
             raise WekoSwordserverException(
-                "An error occurred by importing Item!", ErrorType.ServerError)
+                "Failed to import item.", ErrorType.ServerError)
         else:
             error_messages = "; ".join(
                 [
@@ -554,7 +557,7 @@ def put_object(recid):
         and current_app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"]
     ):
         if (
-            digest is None
+            not isinstance(digest, str)
             or not digest.startswith("SHA-256=")
             or not is_valid_file_hash(digest.split("SHA-256=")[-1], file)
         ):
@@ -596,7 +599,7 @@ def put_object(recid):
         )
 
     # only first item
-    item = check_result.get("list_record", [{}])[0]
+    item = (check_result.get("list_record") or [{}])[0]
     if not item or item.get("errors"):
         error_msg = (
             ", ".join(item.get("errors"))
@@ -917,7 +920,6 @@ def delete_object(recid):
     # check if the item exists
     _ = _get_status_document(recid)
     has_update_version_role(current_user)
-
     record = WekoRecord.get_record_by_pid(recid)
     if record.pid_doi:
         current_app.logger.error(f"Cannot delete item with DOI; item id {recid}")
diff --git a/modules/weko-workflow/weko_workflow/headless/activity.py b/modules/weko-workflow/weko_workflow/headless/activity.py
index 5c4a9ef186..a18b8def9c 100644
--- a/modules/weko-workflow/weko_workflow/headless/activity.py
+++ b/modules/weko-workflow/weko_workflow/headless/activity.py
@@ -20,14 +20,12 @@
 from invenio_db import db
 from invenio_files_rest.errors import FileSizeError
 from invenio_files_rest.models import Bucket, ObjectVersion
-from invenio_indexer.api import RecordIndexer
 from invenio_pidstore import current_pidstore
 from invenio_pidstore.models import PersistentIdentifier
 
 from weko_deposit.api import WekoDeposit, WekoRecord
 from weko_deposit.links import base_factory
 from weko_deposit.serializer import file_uploaded_owner
-from weko_items_autofill.utils import get_workflow_journal
 from weko_items_ui.utils import (
     update_index_tree_for_record, validate_form_input_data, to_files_js
 )
@@ -36,8 +34,6 @@
 )
 from weko_records.api import ItemTypes, ItemsMetadata
 from weko_records.serializers.utils import get_mapping
-from weko_records_ui.utils import soft_delete
-from weko_search_ui.utils import get_data_by_property
 
 from ..api import Action, WorkActivity, WorkFlow, ActivityStatusPolicy
 from ..errors import WekoWorkflowException
@@ -52,7 +48,6 @@
     next_action,
     verify_deletion,
     init_activity,
-    get_feedback_maillist,
     lock_activity
 )
 
@@ -111,7 +106,7 @@ def __init__(
 
         actions = Action().get_action_list()
         self._actions = {
-            action.id: action.action_endpoint for action in actions
+            int(action.id): str(action.action_endpoint) for action in actions
         }
 
     @property
@@ -122,7 +117,7 @@ def activity_id(self):
     @property
     def current_action_id(self):
         """int: current action id."""
-        return self._model.action_id if self._model is not None else None
+        return int(self._model.action_id) if self._model is not None else None
 
     @property
     def current_action(self):
@@ -141,11 +136,11 @@ def community(self):
     @property
     def detail(self):
         """str: activity detail URL."""
-        return url_for(
+        return str(url_for(
             "weko_workflow.display_activity",
             activity_id=self.activity_id, community=self.community,
             _external=True
-        ) if self._model is not None else ""
+        )) if self._model is not None else ""
 
     def init_activity(self, user_id, **kwargs):
         """Manual initialization of activity.
@@ -314,7 +309,7 @@ def auto(self, **params):
         self._activity_unlock(locked_value)
         self._user_unlock()
 
-        returns = (self.detail, self.current_action, self.recid)
+        returns = (str(self.detail), self.current_action, str(self.recid))
 
         self.end()
 
diff --git a/scripts/instance.cfg b/scripts/instance.cfg
index f3e67cd4f6..7d69ddb7fd 100644
--- a/scripts/instance.cfg
+++ b/scripts/instance.cfg
@@ -510,6 +510,12 @@ WEKO_NOTIFICATIONS_INBOX_ENDPOINT = '/inbox'
 # Push Notification Template
 WEKO_NOTIFICATIONS_PUSH_TEMPLATE_PATH = '/code/modules/weko-notifications/weko_notifications/templates/weko_notifications/push.json'
 
+# Check Content-Length header in SWORD API
+WEKO_SWORDSERVER_CONTENT_LENGTH = False
+
+# Required Digest header in SWORD API
+WEKO_SWORDSERVER_DIGEST_VERIFICATION = True
+
 # Disable self sign up function
 SECURITY_REGISTERABLE = False
 

From 70801dd6cea8ed4c1dd019c6f0cf161db965b540 Mon Sep 17 00:00:00 2001
From: ivis-kuroda 
Date: Wed, 7 May 2025 15:28:35 +0900
Subject: [PATCH 040/259] fix: circular imports

---
 modules/weko-index-tree/weko_index_tree/api.py      |  5 +++--
 .../weko_items_autofill/utils.py                    | 12 ++++++------
 modules/weko-items-ui/weko_items_ui/utils.py        | 13 +++++++------
 3 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/modules/weko-index-tree/weko_index_tree/api.py b/modules/weko-index-tree/weko_index_tree/api.py
index 1756200086..f29ef1a724 100644
--- a/modules/weko-index-tree/weko_index_tree/api.py
+++ b/modules/weko-index-tree/weko_index_tree/api.py
@@ -25,7 +25,6 @@
 import sys
 from datetime import date, datetime, timezone
 from functools import partial
-from socketserver import DatagramRequestHandler
 import traceback
 
 from b2handle.clientcredentials import PIDClientCredentials
@@ -45,7 +44,6 @@
 
 from weko_groups.api import Group
 from weko_redis.redis import RedisConnection
-from weko_logging.activity_logger import UserActivityLogger
 from weko_handle.api import Handle
 
 from .models import Index
@@ -69,6 +67,7 @@ def create(cls, pid=None, indexes=None):
 
         # delay import
         from weko_workflow.config import WEKO_SERVER_CNRI_HOST_LINK
+        from weko_logging.activity_logger import UserActivityLogger
 
         def _add_index(data):
             with db.session.begin_nested():
@@ -211,6 +210,8 @@ def update(cls, index_id, **data):
         :param detail: new index info for update.
         :return: Updated index info
         """
+        from weko_logging.activity_logger import UserActivityLogger
+
         try:
             with db.session.begin_nested():
                 index = cls.get_index(index_id)
diff --git a/modules/weko-items-autofill/weko_items_autofill/utils.py b/modules/weko-items-autofill/weko_items_autofill/utils.py
index e5040310d8..8c8b6a287e 100644
--- a/modules/weko-items-autofill/weko_items_autofill/utils.py
+++ b/modules/weko-items-autofill/weko_items_autofill/utils.py
@@ -35,12 +35,6 @@
 from weko_workflow.api import WorkActivity
 from weko_workflow.models import ActionJournal
 from weko_workflow.utils import MappingData
-from weko_workspace.utils import (
-    get_jalc_record_data,
-    get_jamas_record_data,
-    get_datacite_record_data,
-    get_cinii_record_data
-)
 
 from . import config
 from .api import CiNiiURL, CrossRefOpenURL
@@ -256,6 +250,12 @@ def get_doi_with_original(doi, item_type_id, original_metadeta=None, **kwargs):
     :param original_metadeta: The original metadata
     :return: doi data
     """
+    from weko_workspace.utils import (
+        get_jalc_record_data,
+        get_jamas_record_data,
+        get_datacite_record_data,
+        get_cinii_record_data
+    )
     record_funcs_map = {
         "JaLC API": get_jalc_record_data,
         "医中誌 Web API": get_jamas_record_data,
diff --git a/modules/weko-items-ui/weko_items_ui/utils.py b/modules/weko-items-ui/weko_items_ui/utils.py
index 0924718c45..f91ae75af5 100644
--- a/modules/weko-items-ui/weko_items_ui/utils.py
+++ b/modules/weko-items-ui/weko_items_ui/utils.py
@@ -4108,14 +4108,15 @@ def check_duplicate(data, is_item=True):
     """
     Check if a record or item is duplicate in records_metadata.
 
-    Parameters:
-    - data (dict or str): Metadata dictionary (or JSON string).
-    - is_item (bool): True if checking an item, False if checking a record.
+    Args:
+        data (dict or str): Metadata dictionary (or JSON string).
+        is_item (bool): True if checking an item, False if checking a record.
 
     Returns:
-    - bool: True if duplicate exists, False otherwise.
-    - list: List of duplicate record IDs.
-    - list: List of duplicate record URLs.
+        tuple:
+            - bool: True if duplicate exists, False otherwise.
+            - list: List of duplicate record IDs.
+            - list: List of duplicate record URLs.
     """
     if isinstance(data, str):
         try:

From 48f30be705f4546c08608aff578cc2f49abcbc38 Mon Sep 17 00:00:00 2001
From: ivis-kuroda 
Date: Wed, 7 May 2025 15:30:13 +0900
Subject: [PATCH 041/259] refactor: clarified the type

---
 .../weko_notifications/client.py                 |  2 +-
 .../weko_notifications/models.py                 |  3 ++-
 .../weko_notifications/notifications.py          | 16 ++++++++++++++--
 .../weko_notifications/utils.py                  |  2 +-
 4 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/modules/weko-notifications/weko_notifications/client.py b/modules/weko-notifications/weko_notifications/client.py
index 5b87eb0936..54725b4524 100644
--- a/modules/weko-notifications/weko_notifications/client.py
+++ b/modules/weko-notifications/weko_notifications/client.py
@@ -30,7 +30,7 @@ def send(self, notification):
         notification.validate() if not notification._is_validated else None
         sender = ldnlib.Sender()
         sender.send(self.inbox, notification.payload)
-        return notification.id
+        return str(notification.id)
 
     def notifications(self):
         """
diff --git a/modules/weko-notifications/weko_notifications/models.py b/modules/weko-notifications/weko_notifications/models.py
index db4b65192f..f91b3e3af3 100644
--- a/modules/weko-notifications/weko_notifications/models.py
+++ b/modules/weko-notifications/weko_notifications/models.py
@@ -80,7 +80,8 @@ def get_by_user_id(cls, user_id):
         Returns:
             NotificationsUserSettings: Settings.
         """
-        return cls.query.filter_by(user_id=user_id).one_or_none()
+        obj = cls.query.filter_by(user_id=user_id).one_or_none()
+        return obj if isinstance(obj, cls) else None
 
     @classmethod
     def create_or_update(
diff --git a/modules/weko-notifications/weko_notifications/notifications.py b/modules/weko-notifications/weko_notifications/notifications.py
index 9a7164114b..b9a12a40a3 100644
--- a/modules/weko-notifications/weko_notifications/notifications.py
+++ b/modules/weko-notifications/weko_notifications/notifications.py
@@ -73,12 +73,12 @@ def __eq__(self, other):
     @property
     def id(self):
         """str: Notification ID."""
-        return self.payload["id"] if "id" in self.payload else None
+        return str(self.payload["id"]) if "id" in self.payload else None
 
     @property
     def updated(self):
         """str: Updated timestamp. """
-        return self.payload["updated"] if "updated" in self.payload else None
+        return str(self.payload["updated"]) if "updated" in self.payload else None
 
     @property
     def current_body(self):
@@ -131,6 +131,7 @@ def load(cls, payload):
 
         Args:
             payload (dict): Notification payload.
+
         Returns:
             Notification: Notification instance with payload.
         """
@@ -145,9 +146,14 @@ def validate(self):
 
         Returns:
             Notification: Notification instance with payload.
+
+        Raises:
+            ValidationError: If the payload is not valid.
         """
         from .schema import NotificationSchema
         self.payload = NotificationSchema().load(self.current_body).data
+        self._is_validated = True
+        return self
 
     def set_type(self, activity_type):
         """Set activity type.
@@ -155,6 +161,7 @@ def set_type(self, activity_type):
         Args:
             activity_type (ActivityType):
                 One of the Activity Stream 2.0 Activity Types
+
         Returns:
             Notification: Notification instance
         """
@@ -172,6 +179,7 @@ def set_origin(self, id, inbox, entity_type):
                 Inbox URL of the origin entity.
             entity_type (list[str] | str):
                 Type of the origin entity.
+
         Returns:
             Notification: Notification instance
         """
@@ -190,6 +198,7 @@ def set_target(self, id, inbox, entity_type):
                 Inbox URL of the target entity.
             entity_type (list[str] | str):
                 Type of the target entity.
+
         Returns:
             Notification: Notification instance
         """
@@ -216,6 +225,7 @@ def set_object(
                 URL of the object entity.
             name (str | None):
                 Name of the object entity.
+
         Returns:
             Notification: Notification instance
         """
@@ -238,6 +248,7 @@ def set_actor(self, id, entity_type, name):
                 Type of the actor entity.
             name (str):
                 Name of the actor entity.
+
         Returns:
             Notification: Notification instance
         """
@@ -255,6 +266,7 @@ def set_context(self, id, ietf_cite_as=None, entity_type=None):
                 IETF Cite As of the context entity. alias of 'ietf:cite-as'.
             entity_type (list[str] | str):
                 Type of the context entity.
+
         Returns:
             Notification: Notification instance
         """
diff --git a/modules/weko-notifications/weko_notifications/utils.py b/modules/weko-notifications/weko_notifications/utils.py
index 2bc1429209..1c8a2638c2 100644
--- a/modules/weko-notifications/weko_notifications/utils.py
+++ b/modules/weko-notifications/weko_notifications/utils.py
@@ -37,7 +37,7 @@ def inbox_url(endpoint=None, _external=False):
     else:
         url += current_app.config["WEKO_NOTIFICATIONS_INBOX_ENDPOINT"]
 
-    return url
+    return str(url)
 
 
 def rfc3339(timezone=None):

From b8cb4d1e09c3853b479524702c9d3c946ff3b26f Mon Sep 17 00:00:00 2001
From: ivis-kuroda 
Date: Wed, 7 May 2025 15:36:59 +0900
Subject: [PATCH 042/259] fix: deletion url

---
 modules/weko-items-ui/weko_items_ui/views.py  |  8 +--
 .../weko_records_ui/body_contents.html        |  2 +-
 modules/weko-workflow/weko_workflow/utils.py  | 67 ++++---------------
 3 files changed, 17 insertions(+), 60 deletions(-)

diff --git a/modules/weko-items-ui/weko_items_ui/views.py b/modules/weko-items-ui/weko_items_ui/views.py
index 2e5cfe0bfa..be76633648 100644
--- a/modules/weko-items-ui/weko_items_ui/views.py
+++ b/modules/weko-items-ui/weko_items_ui/views.py
@@ -1095,7 +1095,7 @@ def prepare_edit_item(id=None, community=None):
     )
 
 
-@blueprint_api.route('/prepare_delete_item', methods=['POST'])
+@blueprint.route('/prepare_delete_item', methods=['POST'])
 @login_required
 def prepare_delete_item(id=None, community=None):
     """Prepare_delete_item.
@@ -1231,10 +1231,10 @@ def prepare_delete_item(id=None, community=None):
         post_activity['community'] = community
         post_activity['workflow_id'] = workflow_id
 
-        from weko_records_ui.views import soft_delete
         from .utils import send_mail_item_deleted, send_mail_delete_request
 
         if not workflow or workflow.delete_flow_id is None:
+            from weko_records_ui.views import soft_delete
             soft_delete(pid_value)
             send_mail_item_deleted(pid_value, deposit, user_id)
             return jsonify(code=0, msg="success")
@@ -1274,15 +1274,11 @@ def prepare_delete_item(id=None, community=None):
             )
 
         if rtn.action_id == 2:   # end_action
-            soft_delete(pid_value)
             send_mail_item_deleted(pid_value, deposit, user_id)
 
         if rtn.action_id == 4:   # approval
             send_mail_delete_request(rtn)
 
-        if url_redirect.startswith("/api/"):
-            url_redirect = url_redirect[4:]
-
         return jsonify(
             code=0,
             msg='success',
diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html
index 18b3cd082d..62e86619ee 100644
--- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html
+++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html
@@ -377,7 +377,7 @@ 

{{_('Item')}}

{{_('Edit')}}
{{_('Delete')}} diff --git a/modules/weko-workflow/weko_workflow/utils.py b/modules/weko-workflow/weko_workflow/utils.py index 8e69f2225a..e92c25942a 100644 --- a/modules/weko-workflow/weko_workflow/utils.py +++ b/modules/weko-workflow/weko_workflow/utils.py @@ -1904,13 +1904,13 @@ def prepare_delete_workflow(post_activity, recid, deposit): Create new workflow activity. Clone Identifier and Feedbackmail relation to last activity. - parameter: - post_activity: latest activity information. - recid: current record id. - deposit: current deposit data. - return: - rtn: new activity + Args: + post_activity (dict): latest activity information. + recid (PersistentIdentifier): current record id. + deposit (WekoDeposit): current deposit data. + Returns: + Activity: new activity object. """ # ! Check pid's version community = post_activity['community'] @@ -1923,55 +1923,16 @@ def prepare_delete_workflow(post_activity, recid, deposit): if not draft_pid: draft_record = deposit.prepare_draft_item(recid) - rtn = activity.init_activity( - post_activity, community, draft_record.model.id - ) - # create item link info of draft record from parent record - weko_record = WekoRecord.get_record_by_pid( - draft_record.pid.pid_value - ) - if weko_record: - weko_record.update_item_link(recid.pid_value) + item_id = draft_record.model.id else: - # Clone org bucket into draft record. - try: - _parent = WekoDeposit.get_record(recid.object_uuid) - _deposit = WekoDeposit.get_record(draft_pid.object_uuid) - _deposit['path'] = _parent.get('path') - _deposit.merge_data_to_record_without_version(recid, True) - _deposit.publish() - _bucket = Bucket.get(_deposit.files.bucket.id) + item_id = draft_pid.object_uuid - if not _bucket: - _bucket = Bucket.create( - quota_size=current_app.config['WEKO_BUCKET_QUOTA_SIZE'], - max_file_size=current_app.config['WEKO_MAX_FILE_SIZE'], - ) - RecordsBuckets.create(record=_deposit.model, bucket=_bucket) - _deposit.files.bucket.id = _bucket - - bucket = deposit.files.bucket - - sync_bucket = RecordsBuckets.query.filter_by( - bucket_id=_deposit.files.bucket.id - ).first() - - snapshot = bucket.snapshot(lock=False) - snapshot.locked = False - _bucket.locked = False - - sync_bucket.bucket_id = snapshot.id - _deposit['_buckets']['deposit'] = str(snapshot.id) - - db.session.add(sync_bucket) - _bucket.remove() - - except SQLAlchemyError as ex: - raise ex - - rtn = activity.init_activity( - post_activity, community, draft_pid.object_uuid - ) + rtn = activity.init_activity( + post_activity, community, item_id + ) + if rtn.action_id == 2: # end_action + from weko_records_ui.views import soft_delete + soft_delete(recid.pid_value) return rtn From 261c2bcb16c1bebfb80628c66560a849241abec8 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 7 May 2025 15:40:19 +0900 Subject: [PATCH 043/259] fix: can open deletion activity from closed list. --- .../static/js/weko_workflow/activity_list.js | 18 +++++++++--------- modules/weko-workflow/weko_workflow/views.py | 13 ++++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/modules/weko-workflow/weko_workflow/static/js/weko_workflow/activity_list.js b/modules/weko-workflow/weko_workflow/static/js/weko_workflow/activity_list.js index 9a15321023..5a5b083f57 100644 --- a/modules/weko-workflow/weko_workflow/static/js/weko_workflow/activity_list.js +++ b/modules/weko-workflow/weko_workflow/static/js/weko_workflow/activity_list.js @@ -18,7 +18,7 @@ require([ $('#filter_form_clear').on('click', function () { $('#activitylogs_clear_modal').fadeIn(); - }); + }); $('#confirm_clear_activitylogs').on('click', function () { $('#activitylogs_clear_modal').fadeOut(); @@ -27,7 +27,7 @@ require([ $('#cancel_clear_activitylogs').on('click', function () { $('#activitylogs_clear_modal').fadeOut(); - }); + }); $('.clear-activitylog').on('click', function () { $('#activitylog_clear_modal').fadeIn(); @@ -52,7 +52,7 @@ require([ method:"GET", async:false, success: function(data, status) { - if(data.is_deleted === true){ + if(data.is_deleted === true && data.for_delete === false){ alert($('#item_deleted_msg').text()); event.preventDefault(); } @@ -681,7 +681,7 @@ async function downloadActivities(activity_id=''){ } } return setActivitylogSubmit(paramsAfterFilter); - + } //clear all filtered activities @@ -711,7 +711,7 @@ function clearActivities(){ } } } - + let urlEncodedDataPairs = []; for (let key in paramsAfterFilter) { paramsAfterFilter[key].name = decodeURIComponent(paramsAfterFilter[key].name.replace(/\+/g, ' ')); @@ -719,7 +719,7 @@ function clearActivities(){ urlEncodedDataPairs.push(encodeURIComponent(paramsAfterFilter[key].name) + '=' + encodeURIComponent(paramsAfterFilter[key].value)); } clearURL = window.location.pathname + 'clear_activitylog/?' + urlEncodedDataPairs.join('&').replace(/%20/g, '+'); - + $.ajax({ url: clearURL, method: 'GET', @@ -732,7 +732,7 @@ function clearActivities(){ } }); }); - + } @@ -742,7 +742,7 @@ function clearActivity(activity_id){ downloadActivities(activity_id).then(()=>{ clearURL = window.location.pathname + 'clear_activitylog/?activity_id=' + activity_id; - + $.ajax({ url: clearURL, method: 'GET', @@ -781,4 +781,4 @@ async function setActivitylogSubmit(paramsAfterFilter) { } } -} \ No newline at end of file +} diff --git a/modules/weko-workflow/weko_workflow/views.py b/modules/weko-workflow/weko_workflow/views.py index 560256c837..36e0eaf7fe 100644 --- a/modules/weko-workflow/weko_workflow/views.py +++ b/modules/weko-workflow/weko_workflow/views.py @@ -766,7 +766,16 @@ def display_guest_activity(file_name=""): @workflow_blueprint.route('/verify_deletion/', methods=['GET']) @login_required_customize def verify_deletion(activity_id="0"): + """Verify if the activity is deleted. + + Args: + activity_id (str, optional): Activity ID. Defaults to "0". + + Returns: + dict: JSON response with code, is_deleted, and for_delete status. + """ is_deleted = False + for_delete = False activity = WorkActivity().get_activity_by_id(activity_id) if activity and activity.item_id: item_id = str(activity.item_id) @@ -776,7 +785,9 @@ def verify_deletion(activity_id="0"): ).one_or_none() if recid and recid.is_deleted(): is_deleted = True - res = {'code': 200, 'is_deleted':is_deleted} + + for_delete = activity.flow_define.flow_type == WEKO_WORKFLOW_DELETION_FLOW_TYPE + res = {'code': 200, 'is_deleted':is_deleted, 'for_delete':for_delete} return jsonify(res), 200 From 46857b42a0fd1087135aca498ff4d10633747275 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 7 May 2025 15:44:36 +0900 Subject: [PATCH 044/259] fix: repo admin to be able to see Activity List Setting. --- modules/weko-admin/weko_admin/config.py | 1 + modules/weko-theme/weko_theme/config.py | 2 +- modules/weko-workflow/weko_workflow/admin.py | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/weko-admin/weko_admin/config.py b/modules/weko-admin/weko_admin/config.py index 45d976ba76..7e9585c1c9 100644 --- a/modules/weko-admin/weko_admin/config.py +++ b/modules/weko-admin/weko_admin/config.py @@ -1161,6 +1161,7 @@ 'site-license', 'search-management', 'sitemap', + 'activity', 'indexlink', 'itemsetting', 'statssettings', diff --git a/modules/weko-theme/weko_theme/config.py b/modules/weko-theme/weko_theme/config.py index 729b54c250..6c3fac9d20 100644 --- a/modules/weko-theme/weko_theme/config.py +++ b/modules/weko-theme/weko_theme/config.py @@ -310,7 +310,7 @@ 'order': 1 }, { - 'name': 'Activity', + 'name': 'Activity List', 'order': 2 }, { diff --git a/modules/weko-workflow/weko_workflow/admin.py b/modules/weko-workflow/weko_workflow/admin.py index 52712c3329..0247d884ed 100644 --- a/modules/weko-workflow/weko_workflow/admin.py +++ b/modules/weko-workflow/weko_workflow/admin.py @@ -407,7 +407,6 @@ def workflow_detail(self, workflow_id='0'): display_hide_label=display_hide, is_sysadmin=is_sysadmin, repositories=repositories - ) @expose('/', methods=['POST', 'PUT']) @@ -623,7 +622,7 @@ def index(self): name='workspace_workflow_settings', dict_to_object=False) - if not current_settings: + if not current_settings: AdminSettings.update('workspace_workflow_settings', default_workspace_workflowselect_api) current_settings = AdminSettings.get( name='workspace_workflow_settings', @@ -696,7 +695,7 @@ def index(self): 'view_class': ActivitySettingsView, 'kwargs': { 'category': _('Setting'), - 'name': _('Activity'), + 'name': _('Activity List'), 'endpoint': 'activity' } } From d61fc52c00b31c94f1420bc8875890608ce42241 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 7 May 2025 15:48:30 +0900 Subject: [PATCH 045/259] update: translations --- .../translations/en/LC_MESSAGES/messages.mo | Bin 9695 -> 9705 bytes .../translations/en/LC_MESSAGES/messages.po | 4 +- .../translations/ja/LC_MESSAGES/messages.mo | Bin 10870 -> 10875 bytes .../translations/ja/LC_MESSAGES/messages.po | 2 +- .../weko_theme/translations/messages.pot | 4 +- modules/weko-workflow/babel.ini | 2 +- .../admin/workspace_workflow_setting.html | 4 +- .../translations/en/LC_MESSAGES/messages.mo | Bin 9659 -> 15380 bytes .../translations/en/LC_MESSAGES/messages.po | 1321 +++++++++++---- .../translations/ja/LC_MESSAGES/messages.mo | Bin 12510 -> 16807 bytes .../translations/ja/LC_MESSAGES/messages.po | 1430 +++++++++++------ .../weko_workflow/translations/messages.pot | 961 ++++++++--- 12 files changed, 2730 insertions(+), 998 deletions(-) diff --git a/modules/weko-theme/weko_theme/translations/en/LC_MESSAGES/messages.mo b/modules/weko-theme/weko_theme/translations/en/LC_MESSAGES/messages.mo index 2f829d73666a2a8483cbb1539f010db59c581d36..c765d05e573c5bd5a80f6c47da2162ba2f2d1b41 100644 GIT binary patch delta 2277 zcmXZeZD^KN7{KwfIUjSm>E4_q8FQy5G0go*#E!hfb4h9x$kqX>s;sDn?JN& zZX2B}NRtr4@S+gv@eD4^EYnVv&4`aQmI6P-Ap`j=ug7Gq;8vRAQ`_~AMYkD!=UT%ZD7pa%I6LK9lj2cnPT9Qx0q^PWd5n8PAG z5XTQ8MGS}0^(N4SrlP-M4e`Sb8b=Da>3mCGjt{v(vqymjzKJee#`ev8akLh_3t6<} zYtS>l7oGPgdUv|ejdi0(+KVQ>8&h|3i6T0q(I_huZ!8p*4?ywVG_-QnuopF2@`hFjp$m?j}AvEAo zG=VX6-pTm^>@MRabO-d#RRI*9WO%zG)C`=u16PWK`XZf7horv zNKZ5u?L*`AqZK%aCi+fF!-*qk#$#v#$I*pPqX|r+XZlTiehCe91x@rHbe+N^|NcTW zP<6BxU2i2iZ!Nlk^nMx!Y(p35KugqxzVHHi8TZ8g0px=ThtZOrMBkrAzR>U^dI_(g z8(L7GKl?JYzXFZ33P~gl%`_~{CY((KElmfyvraUTZsbD=IW(a+(f5v^^WTq7pzC~! zR^VLp%lQ0TOc?(e>;3+V_$}iCjcAG2qC03s182PowkB zqa~j~Oa6DXkiS^=OVGbRVF?X?F|0-dw4!JJSoFy_z8#&|gYGaFpT83Ody!3rek4|S z4?U_WQ0(z7_c2Z)2(Piy~w=^`!K-~H1Jt;XOrj=TtMglg3kW~ z{oV^3@)fO!HsBn_H=y%2HiVYh61Fl>#K4aDpa=cc`69YtKbp{R^f>w-k1xhwF5aA( an{0V5_tM)*MdkR^qFiadJHC1OQ09N$M$Tyf delta 2265 zcmXZeYiO2b7{Kv+?%*M}Ugv3Rx$Va8ijj-Xa8Iv5Ei8fB>kMdZGej-}B5zxM*!?>;>DeO>o;KhNf$?Z31i z`L-ylNhux3rPP2UScgC3V!RU8%qjHOU@haDu?lyi>-*4khj1Z&gVlH*-S-M!gLRcD zEyY%J{(;JrO$F*@U=aiR(T(0i7o0!?jfXSl7B+0a`HbI=OK=Oi{wcf`2hsH(VuHiV1b#a_o-&0A5G`BjjH?%MVX_2~DJW zK}y6-x1g2Wh|cds_k9itkv`05%%E`$ufmhzIV_`p0ZZ}U@V}TZqR#bT!_&#)>e&m2zdOaRESQtpd=!x>^UH*W+EE8xcE9qF;mFV~eet42jG+-}! zC413?_M=yL80+ynw6fz^LHu-)hF4I^E^bhPZm=8vLRFpBPb3a!K+wbb7YFUG($-V_tap(kF5251Pc4_nYcYtYKA$Jw|E zO{6pI3Li(~WM~DRN7ujPfC~rFjNe8R7)CeFqX~?nclveA|AYoQk0yEn-Dd)QzO=p& zs63pD?pKYjYeXxMt)gMTb?63dXo>DcAJ~DujE_ZsAM(MZSJ9FlL7yK(zR+|MeF?|W z15M*w_U@;n{VFuhVkD6)HPNs%t8pq3v@~t#$u^;hY(qYj)P*MW4Eo#vy8g}ZFuKoC zwA7!5U&j0~tY`c*Hu(Kd#>66i+gRd8^aQPF;EovIh9=aFCh`RG+N6Fop?70`3|;p< zTJke!$^Q-~!eah%`S&MP(C`<-5;Q<7dWRdrJ7fGFbfZr6gk3TJX!Q3WXGs|nBMqTf z^*QoIrZebQb2-LyOL^9lG|@2N9q65Qpb0&Uyie&#tj9q#@CbUcQS=Iqqw9Y}*Plc0 zd;+cL%w+{@u$cY|bY07`w05e5tqhbfuq7rs(O;d9pc`grLa&6w=zlyu%lFP&SN8JR R-2D8B+^&jzd)*sl{{vf-%?!!i~6$@g2ArU&Ko6LFWx%IgVfjUP9x}E=g$# z)?f{8LZ7#mr0h(hP6n1T(1!*(iB1?p6HSB*N^=2g@dn1X;LZ3rI=>5V!ei+CuW==w z$2FK=mg}#>+v)Et%cgW2jUEPW!PB@FuVMq%ki`XU#Z~xR^p9d4{V$MDy2zJ1y@oDi z*^McYGp$D}*^EBlgT{RwNs&%vXLT zJ(2_Hg8I-S9K;NcqLrP%BJ!tc8XiF*Q4COu2B<_{gw%+Z^q#N<=g{Ac&TB&}*p74Y zaE$jNMNDs_@rKa_oeeKwCHd1e8hx|4>AXu`jv;Q4XE%gCkm2diUXGn%Lc9dC{C4s_xx;akWj4e;fWjiB>>LXYGU zdK7=6^QPi?K_<8G3iNU}WwN=~>E0M_G!`paBn}3Eo2|oD9E3U!zfU-mhqaD`)#!hK-riyKPwWVCPtycCv6w+}rPXNQhp`@aBiAS$ zLO0Tn-mzhH1Lx8Cmyr!-Qvpv+yaHXw#^^tS255`%edyb9Fvf?_fM14Z!ZCEm7t#4s zVOo)kUx?g?!Sj zD|2~TFl)j#8U}hP9vljfqX9n;&tX3OE9g!pqra#=x3I-%#a5y7n!<->>fjFYo94dC&8l^PJyL=eN#n z9sRB#t4k>zDNm^pKf`+b6|cohVck{vemh>r_%^J;=g{>7=(-WS8pp5}&!TZJ;Wb!a zkiKfHF%klvmv5N7Hcs)LXuJ6Yc_y)TEb8N=b zcqz)@TGG41PQ0A{PIO&2TEQMH z#Dg(Dh!iosiN?#J8~QdpgLULj7ik&Y&AD;XaJByq@|y(Gmk2&_tc+cvp<~q6=RLUq?P^gfFjb3|;pldL?Jk ztM~(5HyiVX4f%~%q7QdlLzaJS*2h2xTIz?=l# zGK@ZBIrIRh(e>w$17@j^Sra#)8)=XJgJ^*67=IdlJNC!;C>rq7@SAV~J@FK}em0y( z;}_kK&sVb`_rIEkC+LWY9bq3DU=U4o7%kl}F1)6AFa1gMf5ysH@$8_lZCBWX95|8>wiDI}6vK58-|23AUgcdJ;Y9o^UUk=#_97{ipU7w&S13Cp9(a z^K@cn!fqM{dOju&gh$bUABU%~nErY6Bs0-p(vsiUGPGi=&~KxDnmJ_FP}--Nmm?l~?5^%HON~AKZ`BO#lD@ diff --git a/modules/weko-theme/weko_theme/translations/ja/LC_MESSAGES/messages.po b/modules/weko-theme/weko_theme/translations/ja/LC_MESSAGES/messages.po index 46b8e36473..525a9e07f2 100644 --- a/modules/weko-theme/weko_theme/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-theme/weko_theme/translations/ja/LC_MESSAGES/messages.po @@ -913,7 +913,7 @@ msgid "Admin menu_Setting_Items" msgstr "アイテム表示" #: /weko-theme/weko_theme/templates/weko_theme/admin_layout.html: -msgid "Admin menu_Setting_Activity" +msgid "Admin menu_Setting_Activity List" msgstr "アクティビティ一覧表示" #: /weko-theme/weko_theme/templates/weko_theme/admin_layout.html: diff --git a/modules/weko-theme/weko_theme/translations/messages.pot b/modules/weko-theme/weko_theme/translations/messages.pot index 1b97855d9b..a35a0f2c60 100644 --- a/modules/weko-theme/weko_theme/translations/messages.pot +++ b/modules/weko-theme/weko_theme/translations/messages.pot @@ -569,8 +569,8 @@ msgid "Admin menu_Setting_Items" msgstr "Items" #: /weko-theme/weko_theme/templates/weko_theme/admin_layout.html: -msgid "Admin menu_Setting_Activity" -msgstr "Activity" +msgid "Admin menu_Setting_Activity List" +msgstr "Activity List" #: /weko-theme/weko_theme/templates/weko_theme/admin_layout.html: msgid "Admin menu_Setting_Index Link" diff --git a/modules/weko-workflow/babel.ini b/modules/weko-workflow/babel.ini index 88aab57967..d69b3884bd 100644 --- a/modules/weko-workflow/babel.ini +++ b/modules/weko-workflow/babel.ini @@ -27,7 +27,7 @@ encoding = utf-8 [jinja2: **/templates/**.html] encoding = utf-8 -extensions = jinja2.ext.autoescape, jinja2.ext.with_ +extensions = jinja2.ext.autoescape, jinja2.ext.with_, webassets.ext.jinja2.AssetsExtension, jinja2.ext.loopcontrols, jinja2.ext.i18n # Extraction from JavaScript files diff --git a/modules/weko-workflow/weko_workflow/templates/weko_workflow/admin/workspace_workflow_setting.html b/modules/weko-workflow/weko_workflow/templates/weko_workflow/admin/workspace_workflow_setting.html index c64e43964e..fe052a80fa 100644 --- a/modules/weko-workflow/weko_workflow/templates/weko_workflow/admin/workspace_workflow_setting.html +++ b/modules/weko-workflow/weko_workflow/templates/weko_workflow/admin/workspace_workflow_setting.html @@ -49,11 +49,11 @@


-

+

- + {{ form.csrf_token }} + + {%- set select_flg = current_settings.workFlow_select_flg %} + {%- set selected_item_type_id = current_settings.item_type_id %} + {%- set selected_work_flow_id = current_settings.work_flow_id %}
-

+ +
-
+
+
-


-
-

-
+
- {% for work_flow in work_flow_list %} - + {% endfor %}
+
@@ -77,3 +83,22 @@
{%- endblock %} + +{% block tail %} + +{% endblock %} \ No newline at end of file From edfe84fee8e432b6335c7c81376dec80c2ac653f Mon Sep 17 00:00:00 2001 From: ivis-futagami Date: Fri, 9 May 2025 19:51:23 +0900 Subject: [PATCH 067/259] fix w-oa-it-208 --- modules/weko-records/tests/conftest.py | 14 +- modules/weko-records/tests/test_api.py | 676 ++++++++++-------- modules/weko-records/weko_records/api.py | 278 ++++--- .../weko_swordserver/utils.py | 2 +- 4 files changed, 496 insertions(+), 474 deletions(-) diff --git a/modules/weko-records/tests/conftest.py b/modules/weko-records/tests/conftest.py index 2299d4276f..32a0242cba 100644 --- a/modules/weko-records/tests/conftest.py +++ b/modules/weko-records/tests/conftest.py @@ -60,6 +60,7 @@ from weko_itemtypes_ui import WekoItemtypesUI from weko_index_tree.api import Indexes from weko_index_tree.models import Index +from weko_logging.audit import WekoLoggingUserActivity from weko_search_ui import WekoSearchUI from weko_records_ui import WekoRecordsUI from weko_records_ui.config import WEKO_PERMISSION_SUPER_ROLE_USER, WEKO_PERMISSION_ROLE_COMMUNITY, EMAIL_DISPLAY_FLG @@ -138,6 +139,7 @@ def base_app(instance_path): InvenioOAuth2ServerREST(app_) WekoDeposit(app_) WekoItemtypesUI(app_) + WekoLoggingUserActivity(app_) WekoSearchUI(app_) WekoRecordsUI(app_) @@ -346,14 +348,14 @@ def action_data(db): db.session.add_all(actionstatus_db) db.session.commit() return actions_db, actionstatus_db - + @pytest.fixture() def db_register(app, db, users, records, action_data, item_type): from weko_workflow.models import Action, FlowDefine, FlowAction, WorkFlow, Activity, ActivityAction, ActionFeedbackMail,ActivityHistory from datetime import datetime from weko_authors.models import Authors from weko_admin.models import Identifier - + _pid = records[0][0].object_uuid _pid2 = records[1][0].object_uuid flow_define = FlowDefine(flow_id=uuid.uuid4(), @@ -477,7 +479,7 @@ def db_register(app, db, users, records, action_data, item_type): ) with db.session.begin_nested(): db.session.add(activity_03) - + activity_action03_1 = ActivityAction(activity_id=activity_03.activity_id, action_id=1,action_status="M",action_comment="", action_handler=1, action_order=1) @@ -488,7 +490,7 @@ def db_register(app, db, users, records, action_data, item_type): db.session.add(activity_action03_1) db.session.add(activity_action03_2) db.session.commit() - + history = ActivityHistory( activity_id=activity.activity_id, action_id=activity.action_id, @@ -513,7 +515,7 @@ def db_register(app, db, users, records, action_data, item_type): db.session.commit() return {'flow_define':flow_define, 'item_type':item_type, - 'workflow':workflow, + 'workflow':workflow, 'action_feedback_mail':activity_item3_feedbackmail, 'action_feedback_mail1':activity_item4_feedbackmail, 'action_feedback_mail2':activity_item5_feedbackmail, @@ -1038,4 +1040,4 @@ def tokens(app,users,db): db.session.commit() - return tokens \ No newline at end of file + return tokens diff --git a/modules/weko-records/tests/test_api.py b/modules/weko-records/tests/test_api.py index 4524f30037..6b721e502f 100644 --- a/modules/weko-records/tests/test_api.py +++ b/modules/weko-records/tests/test_api.py @@ -1986,171 +1986,358 @@ def test_request_mail_list_delete(app, db): # .tox/c1/bin/pytest --cov=weko_records tests/test_api.py::test_item_link_update -v -s -vv --cov-branch --cov-report=term --cov-config=tox.ini --basetemp=/code/modules/weko-records/.tox/c1/tmp def test_item_link_update(app, db, records): """ - ItemLinkのupdateメソッドをテストする関数。 - 各テストケースで期待データと実際のデータを比較する。 + test cases for ItemLink.update() """ - # テスト用のレコードを作成 - org_item_id = "999" - dst_item_id_1 = "1" - dst_item_id_2 = "2" - dst_item_id_3 = "3" - - # テスト対象のインスタンスを作成 - instance = ItemLink(recid=org_item_id) - - #テストケース 1: 新しいリレーションシップの作成 - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'normal'}, - {'item_id': dst_item_id_2, 'sele_id': 'isSupplementTo'} - ] - result = instance.update(items) - assert result is None # エラーが発生しないことを確認 - - # 期待データ: 新しいリレーションシップが作成されているか確認 - expected_relations = [ - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_1, 'reference_type': 'normal'}, - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_2, 'reference_type': 'isSupplementTo'}, - {'src_item_pid': dst_item_id_2, 'dst_item_pid': org_item_id, 'reference_type': 'isSupplementedBy'} # 逆リレーション - ] - actual_relations = ItemReference.query.all() - assert len(actual_relations) == len(expected_relations) # リレーションシップの数が一致するか確認 - for rel in actual_relations: - assert {'src_item_pid': rel.src_item_pid, 'dst_item_pid': rel.dst_item_pid, 'reference_type': rel.reference_type} in expected_relations - - # # テストケース 2: 既存のリレーションシップの更新 - items = [] - result = instance.update(items) - assert result is None - items = [ - {'item_id': org_item_id, 'dst_item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'}, - {'item_id': org_item_id, 'dst_item_id': dst_item_id_2, 'sele_id': 'isSupplementedBy'} - ] - instance.bulk_create(items) - items = [ - {'item_id': dst_item_id_2, 'sele_id': 'isSupplementedBy'}, - {'item_id': dst_item_id_1, 'sele_id': 'isSupplementedBy'} - ] - result = instance.update(items) - # 期待データ: 新しいリレーションシップが作成されているか確認 - expected_relations = [ - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_2, 'reference_type': 'isSupplementedBy'}, - {'src_item_pid': dst_item_id_2, 'dst_item_pid': org_item_id, 'reference_type': 'isSupplementTo'}, # 逆リレーション - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_1, 'reference_type': 'isSupplementedBy'}, - {'src_item_pid': dst_item_id_1, 'dst_item_pid': org_item_id, 'reference_type': 'isSupplementTo'} # 逆リレーション - ] - - actual_relations = ItemReference.query.all() - assert len(actual_relations) == len(expected_relations) # リレーションシップの数が一致するか確認 - for rel in actual_relations: - assert {'src_item_pid': rel.src_item_pid, 'dst_item_pid': rel.dst_item_pid, 'reference_type': rel.reference_type} in expected_relations - - # テストケース 3: リレーションシップの削除 - items = [] - result = instance.update(items) - assert result is None - - # 期待データ: すべてのリレーションシップが削除されているか確認 - assert ItemReference.query.count() == 0 - - # テストケース 4: 補足リレーションシップの作成と削除 - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} - ] - result = instance.update(items) - assert result is None - - # 期待データ: 補足リレーションシップが作成されているか確認 - supplement_relation = ItemReference.query.filter_by(src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() - assert supplement_relation.reference_type == 'isSupplementTo' - inverse_relation = ItemReference.query.filter_by(src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).first() - assert inverse_relation.reference_type == 'isSupplementedBy' - - # テストケース 5: 無効なアイテムIDの処理 - items = [ - {'item_id': 'invalid', 'sele_id': 'normal'} - ] - result = instance.update(items) - assert result is None - - # 期待データ: 無効なアイテムIDは無視され、リレーションシップが作成されていないか確認 - assert ItemReference.query.filter_by(dst_item_pid='invalid').first() is None - - # テストケース 6: データベースエラーの処理 - # エラーを発生させるための不正なデータを入力 - - # bulk_create メソッドをモックし、IntegrityError を発生させる - with patch.object(instance, 'bulk_create', side_effect=IntegrityError("duplicate key value", None, None)): - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'normal'} - ] - result = instance.update(items) - - # IntegrityError が発生し、エラーメッセージが返されることを確認 - assert result is not None - assert "duplicate key value" in result - - # bulk_create メソッドをモックし、SQLAlchemyError を発生させる - with patch.object(instance, 'bulk_create', side_effect=SQLAlchemyError("transaction error")): - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'normal'} - ] - result = instance.update(items) - - # SQLAlchemyError が発生し、エラーメッセージが返されることを確認 - assert result is not None - assert "transaction error" in result - - # テストケース 7: 複数のリレーションシップの一括処理 - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'normal'}, - {'item_id': dst_item_id_2, 'sele_id': 'isSupplementTo'}, - {'item_id': dst_item_id_3, 'sele_id': 'isSupplementedBy'} - ] - result = instance.update(items) - assert result is None - - # 期待データ: すべてのリレーションシップが正しく作成されているか確認 - expected_relations = [ - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_1, 'reference_type': 'normal'}, - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_2, 'reference_type': 'isSupplementTo'}, - {'src_item_pid': dst_item_id_2, 'dst_item_pid': org_item_id, 'reference_type': 'isSupplementedBy'}, - {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_3, 'reference_type': 'isSupplementedBy'}, - {'src_item_pid': dst_item_id_3, 'dst_item_pid': org_item_id, 'reference_type': 'isSupplementTo'} - ] - actual_relations = ItemReference.query.all() - assert len(actual_relations) == len(expected_relations) # リレーションシップの数が一致するか確認 - for rel in actual_relations: - assert {'src_item_pid': rel.src_item_pid, 'dst_item_pid': rel.dst_item_pid, 'reference_type': rel.reference_type} in expected_relations - - # テストケース 8: 補足リレーションシップの逆リレーションシップの作成 - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} - ] - result = instance.update(items) - assert result is None - - # 期待データ: 逆リレーションシップが正しく作成されているか確認 - inverse_relation = ItemReference.query.filter_by(src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).first() - assert inverse_relation.reference_type == 'isSupplementedBy' - - # テストケース 9: `isSupplementedBy`から`isSupplementTo`への更新 - items = [ - {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} - ] - result = instance.update(items) - assert result is None - - # 期待データ: リレーションシップが正しく更新されているか確認 - updated_relation = ItemReference.query.filter_by(src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() - assert updated_relation.reference_type == 'isSupplementTo' + with app.test_request_context(): + with app.test_client() as client: + # test case with integer id + # src item pid + org_item_id = "999" + # dst item pid + dst_item_id_1 = "1" + dst_item_id_1_0 = "1.0" + dst_item_id_1_1 = "1.1" + dst_item_id_2 = "2" + dst_item_id_2_0 = "2.0" + dst_item_id_2_1 = "2.1" + dst_item_id_3 = "3" + dst_item_id_3_0 = "3.0" + dst_item_id_3_1 = "3.1" + + # create test instance + instance = ItemLink(recid=org_item_id) + + # test case 1: create new relation + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'}, + {'item_id': dst_item_id_2, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + expected_relations = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'reference_type': 'normal'}, + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'reference_type': 'isSupplementTo'}, + {'src_item_pid': dst_item_id_2, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'}, # inverse relation + {'src_item_pid': dst_item_id_2_0, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'}, # inverse relation + {'src_item_pid': dst_item_id_2_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'} # inverse relation + ] + actual_relations = ItemReference.query.all() + assert len(actual_relations) == len(expected_relations) + for rel in actual_relations: + assert {'src_item_pid': rel.src_item_pid, + 'dst_item_pid': rel.dst_item_pid, + 'reference_type': rel.reference_type + } in expected_relations + + # test case 2: update exist relations + items = [] + result = instance.update(items) + assert result is None + items = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'sele_id': 'isSupplementTo'}, + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'sele_id': 'isSupplementedBy'}, + {'src_item_pid': dst_item_id_2, + 'dst_item_pid': org_item_id, + 'sele_id': 'normal'} # overwritten after update + ] + instance.bulk_create(items) + items = [ + {'item_id': dst_item_id_2, 'sele_id': 'isSupplementedBy'}, + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementedBy'} + ] + result = instance.update(items) + expected_relations = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'reference_type': 'isSupplementedBy'}, + {'src_item_pid': dst_item_id_2, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_2_0, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_2_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'reference_type': 'isSupplementedBy'}, + {'src_item_pid': dst_item_id_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_1_0, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_1_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'} # inverse relation + ] + + actual_relations = ItemReference.query.all() + assert len(actual_relations) == len(expected_relations) + for rel in actual_relations: + assert {'src_item_pid': rel.src_item_pid, + 'dst_item_pid': rel.dst_item_pid, + 'reference_type': rel.reference_type + } in expected_relations + + # test case 3: delete relations + items = [] + result = instance.update(items) + assert result is None + assert ItemReference.query.count() == 0 + + # test case 4: create and delete supplement relations + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + supplement_relation = ItemReference.query.filter_by( + src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() + assert supplement_relation.reference_type == 'isSupplementTo' + inverse_relation = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).first() + assert inverse_relation.reference_type == 'isSupplementedBy' + + # test case 5: invalid item id + items = [ + {'item_id': 'invalid', 'sele_id': 'normal'} + ] + result = instance.update(items) + assert result is None + assert ItemReference.query.filter_by( + dst_item_pid='invalid' + ).first() is None + + # test case 6: DB error + # case IntegrityError + with patch.object( + instance, + 'bulk_create', + side_effect=IntegrityError( + "duplicate key value", None, None) + ): + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'} + ] + result = instance.update(items) + + assert result is not None + assert "duplicate key value" in result + + # case SQLAlchemyError + with patch.object( + instance, + 'bulk_create', + side_effect=SQLAlchemyError("transaction error") + ): + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'} + ] + result = instance.update(items) + + assert result is not None + assert "transaction error" in result + + # test case 7: multi relations + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'}, + {'item_id': dst_item_id_2, 'sele_id': 'isSupplementTo'}, + {'item_id': dst_item_id_3, 'sele_id': 'isSupplementedBy'} + ] + result = instance.update(items) + assert result is None + + expected_relations = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'reference_type': 'normal'}, + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'reference_type': 'isSupplementTo'}, + {'src_item_pid': dst_item_id_2, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'}, # inverse relation + {'src_item_pid': dst_item_id_2_0, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'}, # inverse relation + {'src_item_pid': dst_item_id_2_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementedBy'}, # inverse relation + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_3, + 'reference_type': 'isSupplementedBy'}, + {'src_item_pid': dst_item_id_3, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_3_0, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'}, # inverse relation + {'src_item_pid': dst_item_id_3_1, + 'dst_item_pid': org_item_id, + 'reference_type': 'isSupplementTo'} # inverse relation + ] + actual_relations = ItemReference.query.all() + assert len(actual_relations) == len(expected_relations) + for rel in actual_relations: + assert {'src_item_pid': rel.src_item_pid, + 'dst_item_pid': rel.dst_item_pid, + 'reference_type': rel.reference_type + } in expected_relations + + # test case8: create inverse relations of supplement relations + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + inverse_relation = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).first() + assert inverse_relation.reference_type == 'isSupplementedBy' + + # test case 9: update from `isSupplementedBy` to `isSupplementTo` + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + updated_relation = ItemReference.query.filter_by( + src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() + assert updated_relation.reference_type == 'isSupplementTo' + + # test case 10: update from supplement to non-supplement + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + updated_relation = ItemReference.query.filter_by( + src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() + inverse_relation = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).first() + inverse_relation_0 = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1_0, dst_item_pid=org_item_id).first() + inverse_relation_1 = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1_1, dst_item_pid=org_item_id).first() + assert updated_relation.reference_type == 'isSupplementTo' + assert inverse_relation.reference_type == 'isSupplementedBy' + assert inverse_relation_0.reference_type == 'isSupplementedBy' + assert inverse_relation_1.reference_type == 'isSupplementedBy' + + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'} + ] + result = instance.update(items) + updated_relation = ItemReference.query.filter_by( + src_item_pid=org_item_id, dst_item_pid=dst_item_id_1).first() + inverse_relation = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1, dst_item_pid=org_item_id).all() + inverse_relation_0 = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1_0, dst_item_pid=org_item_id).all() + inverse_relation_1 = ItemReference.query.filter_by( + src_item_pid=dst_item_id_1_1, dst_item_pid=org_item_id).all() + assert updated_relation.reference_type == 'normal' + assert len (inverse_relation) == 0 + assert len (inverse_relation_0) == 0 + assert len (inverse_relation_1) == 0 + + # test case 11: delete supplement relations + items = [] + result = instance.update(items) + assert result is None + assert ItemReference.query.count() == 0 + + # test case with decimal id + # src item pid + org_item_id = "999.0" + # dst item pid + dst_item_id_1 = "1" + dst_item_id_2 = "2" + dst_item_id_3 = "3" + + # create test instance + instance = ItemLink(recid=org_item_id) + + # test case 1.0: create new relation + items = [ + {'item_id': dst_item_id_1, 'sele_id': 'normal'}, + {'item_id': dst_item_id_2, 'sele_id': 'isSupplementTo'} + ] + result = instance.update(items) + assert result is None + + expected_relations = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'reference_type': 'normal'}, + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'reference_type': 'isSupplementTo'} + ] + actual_relations = ItemReference.query.all() + assert len(actual_relations) == len(expected_relations) + for rel in actual_relations: + assert {'src_item_pid': rel.src_item_pid, + 'dst_item_pid': rel.dst_item_pid, + 'reference_type': rel.reference_type + } in expected_relations + + # test case 2.0: update exist relations + # create old relations + items = [] + result = instance.update(items) + assert result is None + items = [ + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_1, + 'sele_id': 'isSupplementTo'}, + {'src_item_pid': org_item_id, + 'dst_item_pid': dst_item_id_2, + 'sele_id': 'isSupplementedBy'} + ] + instance.bulk_create(items) + + # update relations + items = [ + {'item_id': dst_item_id_2, 'sele_id': 'isSupplementedBy'}, + {'item_id': dst_item_id_1, 'sele_id': 'isSupplementedBy'} + ] + result = instance.update(items) - # テストケース 10: 補足リレーションシップの削除 - items = [] - result = instance.update(items) - assert result is None + expected_relations = [ + {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_2, + 'reference_type': 'isSupplementedBy'}, + {'src_item_pid': org_item_id, 'dst_item_pid': dst_item_id_1, + 'reference_type': 'isSupplementedBy'} + ] - # 期待データ: すべてのリレーションシップが削除されているか確認 - assert ItemReference.query.count() == 0 + actual_relations = ItemReference.query.all() + assert len(actual_relations) == len(expected_relations) + for rel in actual_relations: + assert {'src_item_pid': rel.src_item_pid, + 'dst_item_pid': rel.dst_item_pid, + 'reference_type': rel.reference_type + } in expected_relations # class ItemLink(object): @@ -2162,8 +2349,8 @@ def test_item_link_bulk_create(app, db, records): _uuid = str(records[0][0].object_uuid) _items = [ { - 'dst_item_id': '1', - 'item_id': _uuid, + 'src_item_pid': _uuid, + 'dst_item_pid': '1', 'sele_id': 'URI' } ] @@ -2179,72 +2366,6 @@ def test_item_link_bulk_create(app, db, records): #assert len(r)==1 #assert r[0]['reference_type']=='URI' -# .tox/c1/bin/pytest --cov=weko_records tests/test_api.py::test_bulk_create_supplement -v -s -vv --cov-branch --cov-report=term --cov-config=tox.ini --basetemp=/code/modules/weko-records/.tox/c1/tmp -def test_bulk_create_supplement(app, db): - """ - bulk_create_supplement メソッドの動作をテストする。 - 各ケースで期待される動作を確認する。 - """ - # テストケース 1: dst_items に1つのアイテムリンク情報が含まれている場合 - dst_items_single = [ - {'src_item_id': '1', 'dst_item_id': '2', 'sele_id': 'isSupplementTo'} - ] - instance = ItemLink(recid='999') - instance.bulk_create_supplement(dst_items_single) - - # データベースに保存されたアイテムを確認 - saved_items = ItemReference.query.all() - assert len(saved_items) == 1 - assert saved_items[0].src_item_pid == '1' - assert saved_items[0].dst_item_pid == '2' - assert saved_items[0].reference_type == 'isSupplementTo' - - # テストケース 2: dst_items に複数のアイテムリンク情報(src_item_id キーが重複しない)が含まれている場合 - dst_items_multiple = [ - {'src_item_id': '3', 'dst_item_id': '4', 'sele_id': 'isSupplementedBy'}, - {'src_item_id': '5', 'dst_item_id': '6', 'sele_id': 'isSupplementTo'} - ] - instance.bulk_create_supplement(dst_items_multiple) - - # データベースに保存されたアイテムを確認 - saved_items = ItemReference.query.all() - assert len(saved_items) == 3 # 前のテストケースで1つ追加されているため、合計3つ - assert saved_items[1].src_item_pid == '3' - assert saved_items[2].src_item_pid == '5' - - # テストケース 3: dst_items のいずれかの辞書に src_item_id, dst_item_id, sele_id のいずれかのキーが欠けている場合 - dst_items_missing_key = [ - {'src_item_id': '7', 'dst_item_id': '8', 'sele_id': 'isSupplementTo'}, - {'src_item_id': '9', 'dst_item_id': '10'}, # sele_id が欠けている - {'src_item_id': '11', 'dst_item_id': '12', 'sele_id': 'isSupplementTo'} - ] - with pytest.raises(Exception): - instance.bulk_create_supplement(dst_items_missing_key) - - # データベースに変更がないことを確認 - saved_items = ItemReference.query.all() - assert len(saved_items) == 3 # 前のテストケースで追加された3つのみ - - # テストケース 4: dst_items に複数のアイテムリンク情報(src_item_id キーが重複)が含まれている場合 - # データベースに変更がないことを確認 - saved_items = ItemReference.query.all() - updateBefore = len(saved_items) - dst_items = [ - {'src_item_id': '1', 'dst_item_id': '2', 'sele_id': 'isSupplementTo'}, - {'src_item_id': '1', 'dst_item_id': '3', 'sele_id': 'isSupplementedBy'}, # src_item_id が重複 - {'src_item_id': '4', 'dst_item_id': '5', 'sele_id': 'isSupplementTo'} - ] - - instance = ItemLink(recid='999') - - # bulk_create_supplement メソッドをモックし、IntegrityError を発生させる - with patch.object(instance, 'bulk_create_supplement', side_effect=IntegrityError("duplicate key value", None, None)): - with pytest.raises(IntegrityError): - instance.bulk_create_supplement(dst_items) - - # データベースに変更がないことを確認 - saved_items = ItemReference.query.all() - assert len(saved_items) == updateBefore # class ItemLink(object): # def bulk_update(self, dst_items): @@ -2253,19 +2374,20 @@ def test_item_link_bulk_update(app, db, records): _uuid = str(records[0][0].object_uuid) _items1 = [ { - 'item_id': '1', + 'src_item_pid': _uuid, + 'dst_item_pid': '1', 'sele_id': 'URI' } ] _items2 = [ { - 'item_id': _uuid, - 'dst_item_id': '1', + 'src_item_pid': _uuid, + 'dst_item_pid': '1', 'sele_id': 'URI' }, { - 'item_id': _uuid, - 'dst_item_id': '2', + 'src_item_pid': _uuid, + 'dst_item_pid': '2', 'sele_id': 'DOI' } ] @@ -2288,24 +2410,24 @@ def test_item_link_bulk_delete(app, db, records): _uuid = str(records[0][0].object_uuid) _items = [ { - 'item_id': _uuid, - 'dst_item_id': '1', + 'src_item_pid': _uuid, + 'dst_item_pid': '1', 'sele_id': 'URI' }, { - 'item_id': _uuid, - 'dst_item_id': '2', + 'src_item_pid': _uuid, + 'dst_item_pid': '2', 'sele_id': 'DOI' }, { - 'item_id': _uuid, - 'dst_item_id': '3', + 'src_item_pid': _uuid, + 'dst_item_pid': '3', 'sele_id': 'HDL' } ] ItemLink.bulk_create(ItemLink(_uuid), _items) - ItemLink.bulk_delete(ItemLink(_uuid), ['1', '2']) + ItemLink.bulk_delete(ItemLink(_uuid), _items[0:2]) r = ItemLink.get_item_link_info(_uuid) assert len(r)==1 assert r[0]['item_links']=='3' @@ -2313,76 +2435,6 @@ def test_item_link_bulk_delete(app, db, records): assert r[0]['value']=='HDL' -# class ItemLink(object): -# def bulk_delete_supplement(self, dst_item_ids): -# .tox/c1/bin/pytest --cov=weko_records tests/test_api.py::test_bulk_delete_supplement -v -s -vv --cov-branch --cov-report=term --cov-config=tox.ini --basetemp=/code/modules/weko-records/.tox/c1/tmp -def test_bulk_delete_supplement(app, db, records): - """ - bulk_delete_supplement メソッドの動作をテストする。 - 各ケースで期待される動作を確認する。 - """ - org_item_id = "999" - - # テストケース 1: dst_item_ids に1つのアイテムIDが含まれている場合 - dst_item_id_single = "1" - - # ItemReference テーブルにテストデータを追加 - db.session.add(ItemReference( - src_item_pid=dst_item_id_single, - dst_item_pid=org_item_id, - reference_type="isSupplementTo" - )) - db.session.add(ItemReference( - src_item_pid=org_item_id, - dst_item_pid=dst_item_id_single, - reference_type="isSupplementedBy" - )) - db.session.commit() - - # bulk_delete_supplement を実行 - instance = ItemLink(recid=org_item_id) - instance.bulk_delete_supplement([dst_item_id_single]) - - # データベースの状態を確認 - deleted_items = db.session.query(ItemReference).filter( - ItemReference.src_item_pid == dst_item_id_single, - ItemReference.dst_item_pid == org_item_id, - ItemReference.reference_type.in_(["isSupplementTo", "isSupplementedBy"]) - ).all() - - assert len(deleted_items) == 0 # 該当するレコードが削除されていることを確認 - - # テストケース 2: dst_item_ids に複数のアイテムIDが含まれている場合 - dst_item_ids_multiple = ["2", "3", "4"] - - # ItemReference テーブルにテストデータを追加 - for dst_item_id in dst_item_ids_multiple: - db.session.add(ItemReference( - src_item_pid=dst_item_id, - dst_item_pid=org_item_id, - reference_type="isSupplementTo" - )) - db.session.add(ItemReference( - src_item_pid=org_item_id, - dst_item_pid=dst_item_id, - reference_type="isSupplementedBy" - )) - db.session.commit() - - # bulk_delete_supplement を実行 - instance.bulk_delete_supplement(dst_item_ids_multiple) - - # データベースの状態を確認 - for dst_item_id in dst_item_ids_multiple: - deleted_items = db.session.query(ItemReference).filter( - ItemReference.src_item_pid == dst_item_id, - ItemReference.dst_item_pid == org_item_id, - ItemReference.reference_type.in_(["isSupplementTo", "isSupplementedBy"]) - ).all() - - assert len(deleted_items) == 0 # 該当するレコードが削除されていることを確認 - - # class JsonldMapping: # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_api.py::TestJsonldMapping -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace class TestJsonldMapping: diff --git a/modules/weko-records/weko_records/api.py b/modules/weko-records/weko_records/api.py index 1af9ceb26a..d90b32a844 100644 --- a/modules/weko-records/weko_records/api.py +++ b/modules/weko-records/weko_records/api.py @@ -2888,134 +2888,143 @@ def get_url(pid_value): def update(self, items): """Update list item link of current record. - This method updates the relationships between the current item (self.org_item_id) - and a list of destination items (items). It handles creation, updating, and deletion - of relationships, including special logic for supplement relationships. + This method updates the relationships between the current item + (self.org_item_id) and a list of destination items (items). + It handles creation, updating, and deletionof relationships, + including special logic for supplement relationships. - :param items: List of dictionaries containing 'item_id' and 'sele_id' (relationship type). - :return: Error message if any, otherwise None. + Args: + items(list): List of dictionaries containing + 'item_id' and 'sele_id' (relationship type). + Returns: + str: Error message if any, otherwise None. """ from weko_logging.activity_logger import UserActivityLogger + from weko_records_ui.utils import get_latest_version - # Fetch all existing relationships where the current item is the source + src_without_ver = self.org_item_id.split('.')[0] + # Fetch all existing relationships where the current item is src dst_relations = ItemReference.get_src_references(self.org_item_id).all() - # Create a set of destination item IDs for quick lookup - dst_ids = {dst_item.dst_item_pid for dst_item in dst_relations} + # Fetch all existing relationships where the current item is dst + src_relations = ItemReference.get_dst_references(src_without_ver).all() + + src_fixed_relations = {rel.dst_item_pid: rel for rel in dst_relations} + dst_fixed_relations = {rel.src_item_pid: rel for rel in src_relations} # Initialize lists to track changes: - # - updated: Items whose relationship type has changed - # - updated_deleted_supplement: Items with supplement relationships that need to be deleted - # - created: New items to be added - # - created_supplement: New supplement relationships to be created - updated = [] - updated_deleted_supplement = [] + # created: new items to be added + # updated: items whose relationship type has changed + # deleted: items to be deleted created = [] - created_supplement = [] + updated = [] + deleted = [] supplement_key = current_app.config["WEKO_RECORDS_REFERENCE_SUPPLEMENT"] # Iterate through each item in the input list + is_src_integer = (self.org_item_id == src_without_ver) for item in items: - item_id = item['item_id'] - if item_id and not item_id.isdigit(): + item_id = str(item['item_id']) + if item_id and not item_id.isdecimal(): continue sele_id = item['sele_id'] - # Check if the item already has a relationship with the current item - if item_id in dst_ids: - # Find the corresponding destination item in the existing relationships - dst_item = next( - (d for d in dst_relations if d.dst_item_pid == item_id), - None - ) - # If the relationship type has changed, handle the update - if dst_item and dst_item.reference_type != sele_id: - # If the old relationship was a supplement type, mark it for deletion - if dst_item.reference_type in ( - supplement_key[0], - supplement_key[1] - ): - updated_deleted_supplement.append(item_id) - itemtmp = item.copy() - if sele_id == supplement_key[1]: - itemtmp['sele_id'] = supplement_key[0] - if self.bulk_select(itemtmp) == False: - created_supplement.append({ - 'item_id': item_id, - 'dst_item_id': self.org_item_id, - 'sele_id': supplement_key[0] - }) - elif sele_id == supplement_key[0]: - itemtmp['sele_id'] = supplement_key[1] - if self.bulk_select(itemtmp) == False: - created_supplement.append({ - 'item_id': item_id, - 'dst_item_id': self.org_item_id, - 'sele_id': supplement_key[1] - }) - # Mark the item as updated - updated.append(item) - # Remove the item from the set of existing relationships - dst_ids.remove(item_id) + relation = { + 'src_item_pid': self.org_item_id, + 'dst_item_pid': item_id, + 'sele_id': sele_id + } + if item_id in src_fixed_relations: + dst_item = src_fixed_relations.pop(item_id) + if sele_id != dst_item.reference_type: + updated.append(relation) else: - # If the item is new, add it to the created list - created.append({ - 'item_id': self.org_item_id, - 'dst_item_id': item['item_id'], - 'sele_id': item['sele_id'] - }) - # If the new relationship is a supplement type, - # create the inverse relationship - if sele_id == supplement_key[1]: - item['sele_id'] = supplement_key[0] - if self.bulk_select(item) == False: - created_supplement.append({ - 'item_id': item_id, - 'dst_item_id': self.org_item_id, - 'sele_id': supplement_key[0] - }) - elif sele_id == supplement_key[0]: - item['sele_id'] = supplement_key[1] - if self.bulk_select(item) == False: - created_supplement.append({ - 'item_id': item_id, - 'dst_item_id': self.org_item_id, - 'sele_id': supplement_key[1] - }) - - deleted = list(dst_ids) - if created_supplement: - created.extend(created_supplement) + created.append(relation) + + # inverse link + if is_src_integer: + inv_src_ids = [ + item_id, + f"{item_id}.0", + get_latest_version(item_id) + ] + if sele_id in supplement_key: + opposite_index = 0 if sele_id == supplement_key[1] else 1 + inv_relation = supplement_key[opposite_index] + for inv_src_id in inv_src_ids: + relation = { + 'src_item_pid': inv_src_id, + 'dst_item_pid': src_without_ver, + 'sele_id': inv_relation + } + if inv_src_id in dst_fixed_relations: + if (inv_relation != + dst_fixed_relations[inv_src_id].reference_type): + updated.append(relation) + else: + created.append(relation) + else: + deleted.extend([ + { + 'src_item_pid': inv_src_id, + 'dst_item_pid': src_without_ver + } + for inv_src_id in inv_src_ids + if ( + inv_src_id in dst_fixed_relations and + dst_fixed_relations[inv_src_id].reference_type + in supplement_key + ) + ]) + + # delete + for deleted_dst, deleted_link in src_fixed_relations.items(): + deleted.append({ + 'src_item_pid': self.org_item_id, + 'dst_item_pid': deleted_dst + }) + sele_id = deleted_link.reference_type + if sele_id in supplement_key and is_src_integer: + inv_src_ids = [ + deleted_dst, + f"{deleted_dst}.0", + get_latest_version(deleted_dst) + ] + deleted.extend([ + { + 'src_item_pid': inv_src_id, + 'dst_item_pid': src_without_ver, + } + for inv_src_id in inv_src_ids + if inv_src_id in dst_fixed_relations + ]) + try: # Perform all database operations within a nested transaction with db.session.begin_nested(): # Delete relationships for removed items if deleted: self.bulk_delete(deleted) - # Delete supplement relationships for deleted items - self.bulk_delete_supplement(deleted) - # Create new relationships # Update existing relationships if updated: - # Delete old supplement relationships for updated items - self.bulk_delete_supplement(updated_deleted_supplement) self.bulk_update(updated) + # Create new relationships if created: self.bulk_create(created) # Commit the transaction if all operations succeed db.session.commit() - for item_id in deleted: + for deleted_link in deleted: UserActivityLogger.info( operation="ITEM_DELETE_LINK", - target_key=item_id + target_key=deleted_link["dst_item_pid"] ) - for updated_item in updated: + for updated_link in updated: UserActivityLogger.info( operation="ITEM_UPDATE_LINK", - target_key=updated_item["item_id"] + target_key=updated_link["dst_item_pid"] ) - for created_info in created: + for created_link in created: UserActivityLogger.info( operation="ITEM_CREATE_LINK", - target_key=created_info["item_id"] + target_key=created_link["dst_item_pid"] ) except IntegrityError as ex: # Log and handle integrity errors (e.g., duplicate entries) @@ -3043,89 +3052,48 @@ def update(self, items): return None - def bulk_select(self, item) : - """select a list of item links in bulk. - """ - return ItemReference.query.filter_by( - src_item_pid=item['item_id'], - dst_item_pid=self.org_item_id.split(".")[0], - reference_type=item['sele_id']).count() > 0 - - def bulk_create(self, dst_items): + def bulk_create(self, items): """Create a list of item links in bulk. - :param dst_items: List of dictionaries containing 'item_id' and 'sele_id'. + Args: + dst_items: List of dictionaries containing + 'src_item_pid', 'dst_item_pid' and 'sele_id' to be created. """ # Create a list of ItemReference objects for bulk insertion objects = [ ItemReference( - src_item_pid=item['item_id'], - dst_item_pid=item['dst_item_id'], + src_item_pid=item['src_item_pid'], + dst_item_pid=item['dst_item_pid'], reference_type=item['sele_id'] - ) for item in dst_items + ) for item in items ] # Save all objects in a single database operation db.session.bulk_save_objects(objects) - def bulk_update(self, dst_items): + def bulk_update(self, items): """Update a list of item links in bulk. - :param dst_items: List of dictionaries containing 'item_id' and 'sele_id'. + Args: + items(list): List of dictionaries containing + 'src_item_pid', 'dst_item_pid' and 'sele_id' to be updated. """ # Update each item relationship by merging changes into the database - for item in dst_items: + for item in items: db.session.merge(ItemReference( - src_item_pid=self.org_item_id, - dst_item_pid=item['item_id'], + src_item_pid=item['src_item_pid'], + dst_item_pid=item['dst_item_pid'], reference_type=item['sele_id'] )) - def bulk_create_supplement(self, dst_items): - """Create a list of supplement item links in bulk. - - Args: - dst_items (list of dict): List of dictionaries containing 'src_item_id', 'dst_item_id', and 'sele_id'. - Each dictionary represents a supplement item link with the source item ID, destination item ID, and reference type. - - Returns: - None - """ - # Create a list of ItemReference objects for bulk insertion - objects = [ - ItemReference( - src_item_pid=item['src_item_id'], - dst_item_pid=item['dst_item_id'], - reference_type=item['sele_id'] - ) for item in dst_items - ] - # Save all objects in a single database operation - db.session.bulk_save_objects(objects) - - def bulk_delete(self, dst_item_ids): + def bulk_delete(self, items): """Delete a list of item links in bulk. - :param dst_item_ids: List of destination item IDs to delete. - """ - db.session.query(ItemReference).filter( - ItemReference.src_item_pid == self.org_item_id, - ItemReference.dst_item_pid.in_(dst_item_ids) - ).delete(synchronize_session='fetch') - - def bulk_delete_supplement(self, dst_item_ids): - """Delete a list of supplement item links in bulk. - Args: - dst_item_ids (list of int): List of destination item IDs to delete from the ItemReference table. - These IDs will be matched against the source item ID in the query. - - Returns: - None - + items(list): List of dictionaries containing + 'src_item_pid', 'dst_item_pid' to be deleted. """ - db.session.query(ItemReference).filter( - ItemReference.src_item_pid.in_(dst_item_ids), - ItemReference.dst_item_pid == self.org_item_id, - ItemReference.reference_type.in_( - current_app.config["WEKO_RECORDS_REFERENCE_SUPPLEMENT"] - ) - ).delete(synchronize_session='fetch') + for item in items: + db.session.query(ItemReference).filter( + ItemReference.src_item_pid==item['src_item_pid'], + ItemReference.dst_item_pid==item['dst_item_pid'], + ).delete(synchronize_session='fetch') diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index ecf9258b52..f650318f21 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -388,7 +388,7 @@ def update_item_ids(list_record, new_id, _id): item_id = link_item.get("item_id") sele_id = link_item.get("sele_id") - if item_id == _id and sele_id == "isSupplementedBy": + if item_id == _id: # If a match is found, overwrite item_id with new_id link_item["item_id"] = new_id current_app.logger.info( From e36f7cd6f2efd20b1e6ff125515330bdc3b2bf3d Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 9 May 2025 19:53:36 +0900 Subject: [PATCH 068/259] fix: some problems --- modules/weko-search-ui/weko_search_ui/mapper.py | 1 - modules/weko-swordserver/weko_swordserver/views.py | 6 ++---- modules/weko-workflow/weko_workflow/headless/activity.py | 5 +++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 35c0e19fad..623b462f69 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -22,7 +22,6 @@ from invenio_pidstore.models import PersistentIdentifier from weko_records.api import Mapping, ItemTypes, FeedbackMailList, RequestMailList, ItemLink -from weko_records.models import ItemType from weko_records.serializers.utils import get_full_mapping from .config import ROCRATE_METADATA_FILE diff --git a/modules/weko-swordserver/weko_swordserver/views.py b/modules/weko-swordserver/weko_swordserver/views.py index 6e614993b6..cdf7ab8cd6 100644 --- a/modules/weko-swordserver/weko_swordserver/views.py +++ b/modules/weko-swordserver/weko_swordserver/views.py @@ -230,8 +230,7 @@ def post_service_document(): ) filename = content_disposition_options.get("filename") - if (content_disposition != "attachment" - or filename is None): + if (content_disposition != "attachment" or filename is None): current_app.logger.error("Cannot get filename by Content-Disposition.") raise WekoSwordserverException( "Cannot get filename by Content-Disposition.", @@ -520,8 +519,7 @@ def put_object(recid): ) filename = content_disposition_options.get("filename") - if (content_disposition != "attachment" - or filename is None): + if content_disposition != "attachment" or filename is None: current_app.logger.error("Cannot get filename by Content-Disposition.") raise WekoSwordserverException( "Cannot get filename by Content-Disposition.", diff --git a/modules/weko-workflow/weko_workflow/headless/activity.py b/modules/weko-workflow/weko_workflow/headless/activity.py index ca3421aa75..323603cd62 100644 --- a/modules/weko-workflow/weko_workflow/headless/activity.py +++ b/modules/weko-workflow/weko_workflow/headless/activity.py @@ -419,6 +419,7 @@ def _input_metadata(self, metadata, files=None, non_extract=None, workspace_regi "recid", cur_pid.pid_value.split(".")[0] ) _deposit = WekoDeposit.get_record(parent_pid.object_uuid) + _deposit.non_extract = non_extract self._deposit = _deposit.newversion(parent_pid) if self._deposit: @@ -520,7 +521,7 @@ def _upload_files(self, files=None): bucket = Bucket.query.get(self._deposit["_buckets"]["deposit"]) files_info = [] - def upload(file_name, stream, size): + def upload(file_name, stream, size, is_thumbnail=False): size_limit = bucket.size_limit location_limit = bucket.location.max_file_size if location_limit is not None: @@ -534,8 +535,8 @@ def upload(file_name, stream, size): current_app.logger.error(desc) raise FileSizeError(description=desc) - # TODO: support thumbnail obj = ObjectVersion.create(bucket, file_name, is_thumbnail=False) + obj.is_thumbnail = is_thumbnail obj.set_contents(stream, size=size, size_limit=size_limit) url = f"{request.url_root}api/files/{obj.bucket_id}/{obj.basename}" return { From b58c71021d9f42b74cce9527fe62dab3f8997217 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 9 May 2025 20:18:14 +0900 Subject: [PATCH 069/259] update: remove angular modal for item deletion. --- modules/weko-items-ui/weko_items_ui/views.py | 10 ++- .../static/js/weko_records_ui/detail.js | 64 ++++++++++++++++++- .../weko_records_ui/body_contents.html | 31 +++++++-- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/modules/weko-items-ui/weko_items_ui/views.py b/modules/weko-items-ui/weko_items_ui/views.py index be76633648..9902b1f4e7 100644 --- a/modules/weko-items-ui/weko_items_ui/views.py +++ b/modules/weko-items-ui/weko_items_ui/views.py @@ -922,7 +922,7 @@ def get_current_login_user_id(): return jsonify(result) -@blueprint_api.route('/prepare_edit_item', methods=['POST']) +@blueprint.route('/prepare_edit_item', methods=['POST']) @login_required def prepare_edit_item(id=None, community=None): """Prepare_edit_item. @@ -961,6 +961,7 @@ def prepare_edit_item(id=None, community=None): db=current_app.config['ACCOUNTS_SESSION_REDIS_DB_NO'], kv = True ) if sessionstorage.redis.exists("pid_{}_will_be_edit".format(pid_value)): + current_app.logger.error(f"Item {pid_value} is being edited.") return jsonify( code=err_code, msg=_('This Item is being edited.') @@ -972,6 +973,7 @@ def prepare_edit_item(id=None, community=None): ttl_secs=3) if pid_value: + pid_value = str(pid_value) record_class = import_string('weko_deposit.api:WekoDeposit') resolver = Resolver(pid_type='recid', object_type='rec', @@ -1023,10 +1025,12 @@ def prepare_edit_item(id=None, community=None): # ! Check Record is being edit item_uuid = latest_pid.object_uuid latest_activity = work_activity.get_workflow_activity_by_item_id(item_uuid) + current_app.logger.info(f"pid_value: {pid_value}, item_uuid: {item_uuid}, latest_activity: {latest_activity}") if latest_activity: is_begin_edit = check_item_is_being_edit(recid, latest_activity, work_activity) if is_begin_edit: + current_app.logger.info(f"Item {pid_value} is being edited.") return jsonify( code=err_code, msg=_('This Item is being edited.'), @@ -1060,6 +1064,7 @@ def prepare_edit_item(id=None, community=None): db.session.commit() except SQLAlchemyError as ex: current_app.logger.error('sqlalchemy error: {}'.format(ex)) + traceback.format_exc() db.session.rollback() return jsonify( code=err_code, @@ -1141,6 +1146,7 @@ def prepare_delete_item(id=None, community=None): db=current_app.config['ACCOUNTS_SESSION_REDIS_DB_NO'], kv = True ) if sessionstorage.redis.exists("pid_{}_will_be_edit".format(pid_value)): + current_app.logger.info(f"Item {pid_value} is being edited.") return jsonify( code=err_code, msg=_('This Item is being edited.') @@ -1153,6 +1159,7 @@ def prepare_delete_item(id=None, community=None): ) if pid_value: + pid_value = str(pid_value) record_class = import_string('weko_deposit.api:WekoDeposit') resolver = Resolver( pid_type='recid', object_type='rec', @@ -1209,6 +1216,7 @@ def prepare_delete_item(id=None, community=None): if latest_activity: is_begin_edit = check_item_is_being_edit(recid, latest_activity, work_activity) if is_begin_edit: + current_app.logger.info(f"Item {pid_value} is being edited.") return jsonify( code=err_code, msg=_('This Item is being edited.'), diff --git a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js index 64087b48d8..f98523d0c4 100644 --- a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js +++ b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js @@ -39,7 +39,7 @@ require([ $('#btn_delete').attr("disabled", true); $('#btn_ver_delete').attr("disabled", true); $('[role="msg"]').css('display', 'inline-block'); - let post_uri = "/api/items/prepare_edit_item"; + let post_uri = "/items/prepare_edit_item"; let pid_val = $(this).data('pid-value'); let community = $(this).data('community'); let post_data = { @@ -70,7 +70,67 @@ require([ if (community) { url = url + "?community=" + community; } - $('[role="alert"]').append('' + res.activity_id + '') + $('[role="alert"]').append(' ' + res.activity_id + '') + } + } + }, + error: function (jqXHE, status) { + $('[role="msg"]').hide(); + $('#btn_edit').removeAttr("disabled"); + $('#btn_delete').removeAttr("disabled"); + $('#btn_ver_delete').removeAttr("disabled"); + $('[role="alert"]').css('display', 'inline-block'); + $('[role="alert"]').text("INTERNAL SERVER ERROR"); + } + }); + }); + + const del_msg = document.getElementById('del_msg').textContent; + $('a#btn_delete').on('click', function () { + $('#confirm_delete_content').text(del_msg); + $('#confirm_delete').modal('show'); + }); + + $('#confirm_delete_button').on('click', function () { + $('#confirm_delete').modal('hide'); + $('#confirm_delete_button').removeAttr("disabled"); + $('[role="alert"]').hide(); + $(this).attr("disabled", true); + $('#btn_delete').attr("disabled", true); + $('#btn_ver_delete').attr("disabled", true); + $('[role="msg"]').css('display', 'inline-block'); + let post_uri = "/items/prepare_delete_item"; + let pid_val = $(this).data('pid-value'); + let community = $(this).data('community'); + let post_data = { + pid_value: pid_val + }; + if (community) { + post_uri = post_uri + "?community=" + community; + } + $.ajax({ + url: post_uri, + method: 'POST', + async: true, + contentType: 'application/json', + data: JSON.stringify(post_data), + success: function (res, status) { + $('[role="msg"]').hide(); + if (0 == res.code) { + let uri = res.data.redirect.replace('api/', '') + document.location.href = uri; + } else { + $('#btn_edit').removeAttr("disabled"); + $('#btn_delete').removeAttr("disabled"); + $('#btn_ver_delete').removeAttr("disabled"); + $('[role="alert"]').css('display', 'inline-block'); + $('[role="alert"]').text(res.msg); + if ("activity_id" in res) { + url = "/workflow/activity/detail/"+res.activity_id; + if (community) { + url = url + "?community=" + community; + } + $('[role="alert"]').append(' ' + res.activity_id + '') } } }, diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html index 62e86619ee..ce66f2421d 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html @@ -375,11 +375,7 @@

{{_('Item')}}

{{_('Edit')}} - + {{_('Delete')}} {% endif %} {% if active_versions|length > 1 %} @@ -453,7 +449,30 @@

{{_('Item')}}

-