diff --git a/modules/invenio-files-rest/invenio_files_rest/admin.py b/modules/invenio-files-rest/invenio_files_rest/admin.py index 639b0091a6..1aa40dc03b 100644 --- a/modules/invenio-files-rest/invenio_files_rest/admin.py +++ b/modules/invenio-files-rest/invenio_files_rest/admin.py @@ -151,15 +151,16 @@ def on_model_change(self, form, model, is_created): model.s3_region_name = model.uri.split('.')[1] parts = model.uri.split('/') model.s3_endpoint_url = ( - parts[0] + '//' + parts[3] + '.' + parts[2] + '/' + parts[0] + '//' + parts[2] + '/' ) else: # ex: https://bucket_name.s3.us-east-1.amazonaws.com/file_name if not model.s3_region_name: model.s3_region_name = model.uri.split('.')[2] parts = model.uri.split('/') + sub_parts = parts[2].split('.') model.s3_endpoint_url = ( - parts[0] + '//' + parts[2] + '/' + parts[0] + '//' + parts[2].replace(sub_parts[0] + '.', '') + '/' ) else: model.s3_default_block_size = None @@ -187,15 +188,16 @@ def on_model_change(self, form, model, is_created): model.s3_region_name = model.uri.split('.')[1] parts = model.uri.split('/') model.s3_endpoint_url = ( - parts[0] + '//' + parts[3] + '.' + parts[2] + '/' + parts[0] + '//' + parts[2] + '/' ) else: # ex: https://bucket_name.s3.us-east-1.amazonaws.com/file_name if not model.s3_region_name: model.s3_region_name = model.uri.split('.')[2] parts = model.uri.split('/') + sub_parts = parts[2].split('.') model.s3_endpoint_url = ( - parts[0] + '//' + parts[2] + '/' + parts[0] + '//' + parts[2].replace(sub_parts[0] + '.', '') + '/' ) else: # local diff --git a/modules/invenio-s3/requirements2.txt b/modules/invenio-s3/requirements2.txt index 3c30c7d8a7..0b8893cc75 100644 --- a/modules/invenio-s3/requirements2.txt +++ b/modules/invenio-s3/requirements2.txt @@ -17,6 +17,7 @@ binaryornot==0.4.4 bleach==3.1.0 blinker==1.4 boto3==1.9.83 +moto botocore==1.12.209 cachelib==0.1 captcha==0.4 diff --git a/modules/weko-admin/tests/test_admin.py b/modules/weko-admin/tests/test_admin.py index 6eaea763d3..8d6f95ef3b 100644 --- a/modules/weko-admin/tests/test_admin.py +++ b/modules/weko-admin/tests/test_admin.py @@ -2373,15 +2373,18 @@ def test_edit_view(self, client, db, users, item_type, flows, tokens, sword_mapp def test_get_query_in_role_ids(self, client, users, db, mocker): login_user_via_session(client,email=users[0]["email"])# sysadmin current_app.config['WEKO_ADMIN_SWORD_API_JSON_LD_FULL_AUTHORITY_ROLE'] = users[0]["id"] - SwordAPIJsonldSettingsView().get_query() + view = SwordAPIJsonldSettingsView(SwordClientModel, db.session) + view.get_query() current_app.config['WEKO_ADMIN_SWORD_API_JSON_LD_FULL_AUTHORITY_ROLE'] = 1 - SwordAPIJsonldSettingsView().get_query() + view.get_query() def test_get_count_query(self, client, users, db, mocker): login_user_via_session(client,email=users[0]["email"])# sysadmin current_app.config['WEKO_ADMIN_SWORD_API_JSON_LD_FULL_AUTHORITY_ROLE'] = users[0]["id"] - SwordAPIJsonldSettingsView().get_count_query() + view = SwordAPIJsonldSettingsView(SwordClientModel, db.session) + view.get_count_query() + # def validate_mapping(self, id): # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::test_validate_mapping -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp @@ -2407,6 +2410,65 @@ def test_validate_mapping(self, client, users, db, sword_mapping, mocker): assert res.status_code == 400 assert json.loads(res.data) == {"error": "Failed to validate jsonld mapping."} + # def delete_data(self): + # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::delete_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp + def test_delete_data(self, app, client, users, db, sword_client, sword_mapping, mocker): + url = url_for("swordapi/jsonld.delete_data") + login_user_via_session(client,email=users[0]["email"]) + + # model is none + res = client.post( + url, + data={ + 'id': '9999', + }, + content_type='application/x-www-form-urlencoded' + ) + assert res.status_code == 404 + + # editable + res = client.post( + url, + data={ + 'id': '1', + }, + content_type='application/x-www-form-urlencoded' + ) + assert res.status_code == 302 + + # editable and error + with patch("weko_admin.admin.SwordClient.remove", side_effect=SQLAlchemyError("test_db_error")): + res = client.post( + url, + data={ + 'id': '2', + }, + content_type='application/x-www-form-urlencoded' + ) + assert res.status_code == 302 + + # not editable + with patch("weko_admin.admin.SwordAPIJsonldSettingsView._is_editable", return_value=False): + res = client.post( + url, + data={ + 'id': '3', + }, + content_type='application/x-www-form-urlencoded' + ) + assert res.status_code == 302 + + + # def _is_editable(self, workflow_id): + # .tox/c1/bin/pytest --cov=weko_admin tests/test_admin.py::TestSwordAPIJsonldSettingsView::_is_editable -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-admin/.tox/c1/tmp + def test_is_editable(self, app, client, users, db, sword_client, sword_mapping, mocker): + login_user_via_session(client,email=users[0]["email"])# sysadmin + current_app.config['WEKO_ADMIN_SWORD_API_JSON_LD_FULL_AUTHORITY_ROLE'] = users[0]["id"] + view = SwordAPIJsonldSettingsView(SwordClientModel, db.session) + view._is_editable(1) + + view._is_editable(None) + # 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 @@ -2444,13 +2506,13 @@ def test_create_view(self, client, users, item_type, db, mocker): # post # Error login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.JsonldMapping.create", side_effect=Exception("test_error")): + with patch("weko_admin.admin.JsonldMapping.create", side_effect=SQLAlchemyError("test_error")): res = client.post(url, data=json.dumps({'name': '1', 'mapping': {}, 'item_type_id': item_type[0]["obj"].id}), content_type='application/json') - assert res.status_code == 400 + assert res.status_code == 500 def test_edit_view(self, client, users, item_type, flows, db, mocker): @@ -2500,6 +2562,12 @@ def test_edit_view(self, client, users, item_type, flows, db, mocker): with patch("weko_admin.admin.WorkActivity.count_waiting_approval_by_workflow_id", return_value=0): 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]): + with patch("weko_admin.admin.JsonldMappingView._is_editable", return_value=False): + with patch("weko_admin.admin.SwordClient.get_clients_by_mapping_id", return_value=True): + res = client.get(url) + assert res.status_code == 200 # post # success registration_type:Direct, active:True @@ -2512,9 +2580,9 @@ def test_edit_view(self, client, users, item_type, flows, db, mocker): assert res.status_code == 200 # post - # error + # not can_edit error 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.JsonldMappingView._is_editable", return_value=False): res = client.post(url, data=json.dumps({'name': '1', 'mapping':{}, @@ -2522,37 +2590,93 @@ def test_edit_view(self, client, users, item_type, flows, db, mocker): content_type='application/json') assert res.status_code == 400 - def test_delte_data(self, client, users, item_type, db, mocker): - url = url_for("jsonld-mapping.delte_data", id=1) + # post + # not can_change_item_type error + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonldMappingView._is_editable", return_value=True): + with patch("weko_admin.admin.SwordClient.get_clients_by_mapping_id", return_value=True): + res = client.post(url, + data=json.dumps({'name': '1', + 'mapping':{}, + 'item_type_id': '0'}), + 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.JsonldMappingView._is_editable", return_value=True): + with patch("weko_admin.admin.db.session.commit", side_effect=SQLAlchemyError ("test_error")): + res = client.post(url, + data=json.dumps({'name': '1', + 'mapping':{}, + 'item_type_id': item_type[0]["obj"].id}), + content_type='application/json') + assert res.status_code == 400 + + def test_delete(self, client, users, item_type, db, mocker, sword_client, sword_mapping): + url = url_for("jsonld-mapping.delete", id=1) #no administrator - res = client.post(url) + res = client.delete(url) assert res.status_code == 302 login_user_via_session(client,email=users[7]["email"]) - res = client.post(url) + res = client.delete(url) assert res.status_code == 403 + # not editable + login_user_via_session(client,email=users[0]["email"])# sysadmin + with patch("weko_admin.admin.JsonldMappingView._is_editable", return_value=False): + res = client.delete(url) + assert res.status_code == 400 + # delete # 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() - login_user_via_session(client,email=users[0]["email"])# sysadmin - res = client.post(url) + with patch("weko_admin.admin.JsonldMappingView._is_editable", return_value=True): + res = client.delete(url) assert res.status_code == 200 # error login_user_via_session(client,email=users[0]["email"])# sysadmin - with patch("weko_admin.admin.JsonldMapping.delete", side_effect=Exception("test_error")): - res = client.post(url) + with patch("weko_admin.admin.JsonldMapping.delete", side_effect=SQLAlchemyError("test_error")): + res = client.delete(url) assert res.status_code == 400 + # model_none + url_none = url_for("jsonld-mapping.delete", id=0) + res = client.delete(url_none) + assert res.status_code == 404 + + def test_get_query(self, client, users, db, mocker): login_user_via_session(client,email=users[0]["email"])# sysadmin - JsonldMappingView.get_query(JsonldMappingView) + view = JsonldMappingView(ItemTypeJsonldMapping, db.session) + view.get_query() + + def test_is_editable(self, app, client, users, db, sword_client, sword_mapping, mocker): + login_user_via_session(client,email=users[0]["email"])# sysadmin + view = JsonldMappingView(ItemTypeJsonldMapping, db.session) + view._is_editable(1) + + def test_validate_mapping(self, app, client, users, db, sword_client, sword_mapping, mocker): + login_user_via_session(client,email=users[0]["email"]) + current_app.config['WEKO_ADMIN_SWORD_API_JSON_LD_FULL_AUTHORITY_ROLE'] = users[0]["id"] + url = url_for("jsonld-mapping.validate_mapping") + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=1): + res = client.post( + url, + data=json.dumps({'itemtype_id': '1', + 'mapping':{}, + 'mapping_id': '1'}), + content_type='application/json') + assert res.status_code == 200 + + with patch("weko_admin.admin.JsonLdMapper.validate", return_value=1): + res = client.post( + url, + data=json.dumps({'itemtype_id': '1', + 'mapping':None, + 'mapping_id': '1'}), + content_type='application/json') + assert res.status_code == 200 diff --git a/modules/weko-admin/weko_admin/admin.py b/modules/weko-admin/weko_admin/admin.py index c6b326bf6d..ef7690f37b 100644 --- a/modules/weko-admin/weko_admin/admin.py +++ b/modules/weko-admin/weko_admin/admin.py @@ -1665,26 +1665,28 @@ def _format_mapping_name(view, context, model, name): } def get_query(self): + """Get query for SWORD API JSON-LD settings.""" + query = super().get_query().order_by(SwordClientModel.id) list_role = [role.name for role in current_user.roles] - if current_app.config["WEKO_ADMIN_PERMISSION_ROLE_SYSTEM"] in list_role: - return super(SwordAPIJsonldSettingsView, self).get_query() - else: - return ( - super(SwordAPIJsonldSettingsView, self).get_query() - .join(Client) + if current_app.config["WEKO_ADMIN_PERMISSION_ROLE_SYSTEM"] not in list_role: + query = ( + query.join(Client) .filter(Client.client_id == SwordClientModel.client_id) .filter(Client.user_id == current_user.get_id()) ) + return query def get_count_query(self): + """Get count query for SWORD API JSON-LD settings.""" + query = super().get_count_query() list_role = [role.name for role in current_user.roles] - if current_app.config["WEKO_ADMIN_PERMISSION_ROLE_SYSTEM"] in list_role: - return super(SwordAPIJsonldSettingsView, self).get_count_query() - else: - return (super(SwordAPIJsonldSettingsView, self).get_count_query() - .join(Client) - .filter(Client.client_id == SwordClientModel.client_id) - .filter(Client.user_id == current_user.get_id())) + if current_app.config["WEKO_ADMIN_PERMISSION_ROLE_SYSTEM"] not in list_role: + query = ( + query.join(Client) + .filter(Client.client_id == SwordClientModel.client_id) + .filter(Client.user_id == current_user.get_id()) + ) + return query @expose("/add/", methods=["GET", "POST"]) def create_view(self): @@ -2025,18 +2027,16 @@ class JsonldMappingView(ModelView): edit_template = WEKO_ADMIN_SWORD_API_JSONLD_MAPPING_TEMPLATE column_filters = ( - "id", "name", + "item_type.item_type_name.name" ) column_list = ( - "id", "name", "item_type", "updated", ) column_details_list = ( - "id", "created", "updated", "name", @@ -2065,20 +2065,21 @@ def _formated_jsonld_mapping(view, context, model, name): "mapping": _formated_jsonld_mapping, } column_sortable_list = ( - "id", "name", ('item_type', 'item_type.item_type_name.name'), "updated", ) column_searchable_list = ( - "id", "name", "item_type.item_type_name.name" ) def get_query(self): - return super().get_query().filter( - ItemTypeJsonldMapping.is_deleted == False + """Get query for JSON-LD mapping.""" + return ( + super().get_query() + .filter(ItemTypeJsonldMapping.is_deleted == False) + .order_by(ItemTypeJsonldMapping.id) ) @expose("/new/", methods=["GET", "POST"]) @@ -2208,7 +2209,7 @@ def edit_view(self, id): return jsonify({"error": msg}), 400 @expose("/delete//", methods=["DELETE"]) - def delte(self, id): + def delete(self, id): """Delete JSON-LD mapping.""" model = self.get_one(id) if model is None: diff --git a/modules/weko-authors/tests/conftest.py b/modules/weko-authors/tests/conftest.py index 731c85026f..6a2732c2d5 100644 --- a/modules/weko-authors/tests/conftest.py +++ b/modules/weko-authors/tests/conftest.py @@ -39,7 +39,7 @@ from invenio_files_rest import InvenioFilesREST from invenio_files_rest.models import Location, FileInstance from invenio_indexer import InvenioIndexer -from invenio_search import InvenioSearch,RecordsSearch +from invenio_search import InvenioSearch,RecordsSearch, current_search_client from weko_authors.config import WEKO_AUTHORS_REST_ENDPOINTS from weko_search_ui import WekoSearchUI from weko_index_tree.models import Index @@ -156,9 +156,9 @@ def base_app(request, instance_path,search_class): CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", WEKO_AUTHORS_IMPORT_CACHE_RESULT_OVER_MAX_FILE_PATH_KEY = "authors_import_result_file_of_over_path", - WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH = "/var/tmp/authors_export", + WEKO_AUTHORS_EXPORT_TMP_DIR = "authors_export", WEKO_AUTHORS_IMPORT_CACHE_USER_TSV_FILE_KEY = 'authors_import_user_file_key', - WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH = "var/tmp/authors_import", + WEKO_AUTHORS_IMPORT_TMP_DIR = "authors_import", WEKO_AUTHORS_FILE_MAPPING_FOR_PREFIX =["scheme", "name", "url", "is_deleted"], WEKO_AUTHORS_IMPORT_TMP_PREFIX = 'authors_import_', WEKO_AUTHORS_IMPORT_BATCH_SIZE = 100, @@ -379,7 +379,7 @@ def base_app2(instance_path,search_class): WEKO_AUTHORS_FILE_MAPPING_FOR_AFFILIATION=WEKO_AUTHORS_FILE_MAPPING_FOR_AFFILIATION, WEKO_AUTHORS_BULK_EXPORT_MAX_RETRY=2, WEKO_AUTHORS_BULK_EXPORT_RETRY_INTERVAL=1, - WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH='/data', + WEKO_AUTHORS_IMPORT_TMP_DIR='/data', WEKO_AUTHORS_CACHE_TTL=100, WEKO_AUTHORS_IMPORT_BATCH_SIZE=2, WEKO_AUTHORS_IMPORT_MAX_RETRY=2, @@ -436,7 +436,6 @@ def client(app): with app.test_client() as client: yield client -from invenio_search import current_search_client @pytest.fixture() def esindex(app): current_search_client.indices.delete(index='test-*') @@ -723,24 +722,31 @@ def file_instance(db): db.session.commit() -# @pytest.fixture() -# def esindex(app2): -# from invenio_search import current_search_client as client -# index_name = app2.config["INDEXER_DEFAULT_INDEX"] -# alias_name = "test-author-alias" +@pytest.fixture() +def esindex2(app2): + index_name = app2.config["INDEXER_DEFAULT_INDEX"] + alias_name = "test-author-alias" -# with open("tests/data/mappings/author-v1.0.0.json","r") as f: -# mapping = json.load(f) + with open("tests/data/mappings/author-v1.0.0.json","r") as f: + mapping = json.load(f) -# with app2.test_request_context(): -# client.indices.create(index=index_name, body=mapping, ignore=[400]) -# client.indices.put_alias(index=index_name, name=alias_name) + with app2.test_request_context(): + current_search_client.indices.create( + index=index_name, body=mapping, ignore=[400] + ) + current_search_client.indices.put_alias( + index=index_name, name=alias_name + ) -# yield client + yield current_search_client -# with app2.test_request_context(): -# client.indices.delete_alias(index=index_name, name=alias_name) -# client.indices.delete(index=index_name, ignore=[400, 404]) + with app2.test_request_context(): + current_search_client.indices.delete_alias( + index=index_name, name=alias_name + ) + current_search_client.indices.delete( + index=index_name, ignore=[400, 404] + ) from invenio_oauth2server.models import Client diff --git a/modules/weko-authors/tests/test_utils.py b/modules/weko-authors/tests/test_utils.py index e5e925a3f7..99e48db1ef 100644 --- a/modules/weko-authors/tests/test_utils.py +++ b/modules/weko-authors/tests/test_utils.py @@ -5,6 +5,7 @@ from mock import patch, MagicMock, Mock from flask import current_app import datetime +import json from unittest.mock import mock_open from invenio_indexer.api import RecordIndexer @@ -426,22 +427,21 @@ def test_getEncode(): assert result == "utf-8" class MockOpen: - def __init__(self, path, encoding=None): + def __init__(self, path, mode, encoding=None): self.path=path self.encoding = encoding def read(self): - if self.encoding!="not_exist_encode": - raise UnicodeDecodeError(self.encoding,b"test",0,1,"test_reason") + return b"test" def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass - def mock_open(path, encoding=None): - return MockOpen(path,encoding) + def mock_open(path, mode, encoding=None): + return MockOpen(path, mode,encoding) with patch("builtins.open",side_effect=mock_open): result = getEncode("test_path") - assert result == "" + assert result == "ascii" # def unpackage_and_check_import_file(file_format, file_name, temp_file, mapping_ids): @@ -1243,8 +1243,6 @@ def test_flatten_authors_mapping(): "is_deleted" ] all,keys = flatten_authors_mapping(data) - print(all) - print(keys) assert all == test_all assert keys == test_keys @@ -1274,7 +1272,6 @@ def test_import_author_to_system(app, mocker): mock_check_weko_id.assert_called_once_with(weko_id, '1') mock_weko_authors.create.assert_called_once() actual_author = mock_weko_authors.create.call_args[0][0] - print(actual_author) assert actual_author == {'pk_id': '1', 'authorNameInfo': [{'familyName': 'テスト', 'firstName': '太郎', 'fullName': 'テスト 太郎'}], 'is_deleted': False, 'authorIdInfo': [{'idType': '1', 'authorId': '1234', 'authorIdShowFlg': 'true'}], 'emailInfo': []} mock_session.commit.assert_called_once() @@ -1402,17 +1399,18 @@ def search(self,index,body): # def count_authors(): # .tox/c1/bin/pytest --cov=weko_authors tests/test_utils.py::test_count_authors -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-authors/.tox/c1/tmp -def test_count_authors(app2, esindex): - import json +def test_count_authors(app2, esindex2): index = app2.config["WEKO_AUTHORS_ES_INDEX_NAME"] doc_type = "author-v1.0.0" def register(i): with open(f"tests/data/test_authors/author{i:02}.json","r") as f: - esindex.index(index=index, doc_type=doc_type, id=f"{i}", body=json.load(f), refresh="true") + esindex2.index(index=index, doc_type=doc_type, + id=f"{i}", body=json.load(f), refresh="true") def delete(i): - esindex.delete(index=index, doc_type=doc_type, id=f"{i}", refresh="true") + esindex2.delete(index=index, doc_type=doc_type, + id=f"{i}", refresh="true") # 3 Register 1 author data register(1) @@ -2353,7 +2351,7 @@ def test_update_cache_data(app): def test_write_tmp_part_file(app): with patch("weko_authors.utils.open") as mock_writer: - update_cache_data(current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH"), "/data/test_over_max") + update_cache_data(current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR"), "/data/test_over_max") write_tmp_part_file(1, [{"key": "value"}], "temp_file_path") mock_writer.assert_called() @@ -2717,7 +2715,7 @@ def test_band_check_file_for_user(authors_affiliation_settings): mock_check.return_value = "test_over_max-check" mock_json.return_value = data result = band_check_file_for_user(1) - assert result == "var/tmp/authors_import/import_author_check_result_{}.tsv".format(datetime.datetime.now().strftime("%Y%m%d%H%M")) + assert result == "/var/tmp/authors_import/import_author_check_result_{}.tsv".format(datetime.datetime.now().strftime("%Y%m%d%H%M")) data_2 = [ {"pk_id": 1, "authorNameInfo":[{"familyName": "a", "firstName": ""}, {"familyName": "b", "firstName": ""}],}, @@ -2729,7 +2727,7 @@ def test_band_check_file_for_user(authors_affiliation_settings): mock_check.return_value = "test_over_max-check" mock_json.return_value = data_2 result = band_check_file_for_user(1) - assert result == "var/tmp/authors_import/import_author_check_result_{}.tsv".format(datetime.datetime.now().strftime("%Y%m%d%H%M")) + assert result == "/var/tmp/authors_import/import_author_check_result_{}.tsv".format(datetime.datetime.now().strftime("%Y%m%d%H%M")) with patch('weko_authors.utils.get_check_base_name') as mock_check,\ patch("weko_authors.utils.open") as mock_open: @@ -3258,4 +3256,3 @@ def test_create_result_file_for_user(app, mocker): current_cache.delete(result_path_key) res = create_result_file_for_user(json) assert res == None - diff --git a/modules/weko-authors/weko_authors/admin.py b/modules/weko-authors/weko_authors/admin.py index ff44d78960..fe65f699f7 100644 --- a/modules/weko-authors/weko_authors/admin.py +++ b/modules/weko-authors/weko_authors/admin.py @@ -134,8 +134,8 @@ def download(self): def check_status(self): """Api check export status.""" - # stop_pointを確認 - stop_point= current_cache.get( + # Get the point where processing paused + stop_point = current_cache.get( current_app.config["WEKO_AUTHORS_EXPORT_CACHE_STOP_POINT_KEY"] ) @@ -161,7 +161,7 @@ def check_status(self): status['filename'] = '' file_instance = FileInstance.get_by_uri(status.get('file_uri', '')) if file_instance: - # export_targetによってfilenameを変更 + # Change filename with export_target export_target = current_cache.get( current_app.config['WEKO_AUTHORS_EXPORT_TARGET_CACHE_KEY'] ) @@ -187,12 +187,11 @@ def export(self): """Process export authors.""" data = request.get_json() export_target = data.get("isTarget", "") - #実行時に以前のexport_urlを削除 + # Remove previous export url. delete_export_url() if export_target == "author_db": - # 一時ファイルの作成 temp_folder_path = current_app.config.get( - "WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH" + "WEKO_AUTHORS_EXPORT_TMP_DIR" ) os.makedirs(temp_folder_path, exist_ok=True) prefix = ( @@ -205,7 +204,7 @@ def export(self): ) as temp_file: temp_file_path = temp_file.name - # redisに一時ファイルのパスを保存 + # cache the temporary file path update_cache_data( current_app.config["WEKO_AUTHORS_EXPORT_CACHE_TEMP_FILE_PATH_KEY"], temp_file_path, @@ -225,7 +224,7 @@ def cancel(self): result = {'status': 'fail'} try: status = get_export_status() - # stop_pointがあるならstop_pointとtemp_file_pathを削除 + # if stop_point is exists, delete stop_point and temp_file_path if current_cache.get( current_app.config["WEKO_AUTHORS_EXPORT_CACHE_STOP_POINT_KEY"] ): @@ -251,14 +250,13 @@ def cancel(self): }) - # 再開用メソッド @author_permission.require(http_exception=403) @expose('/resume', methods=['POST']) def resume(self): """Resume export progress.""" delete_export_url() - temp_folder_path = current_app.config.get("WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH") + temp_folder_path = current_app.config.get("WEKO_AUTHORS_EXPORT_TMP_DIR") os.makedirs(temp_folder_path, exist_ok=True) prefix = ( current_app.config["WEKO_AUTHORS_EXPORT_TMP_PREFIX"] @@ -319,8 +317,9 @@ def check_import_file(self): ) list_import_data = result.get('list_import_data') elif target == 'author_db': - temp_folder_path = current_app.config.get( - "WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH" + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") ) os.makedirs(temp_folder_path, exist_ok=True) prefix = ( @@ -349,7 +348,7 @@ def check_import_file(self): counts = result.get("counts") max_page = result.get("max_page") - # インポートタブのチェック結果一時ファイルがあれば削除 + # Delete temporary files if checked results band_file_path = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_BAND_CHECK_USER_FILE_PATH_KEY"] ) @@ -378,9 +377,11 @@ def check_pagination(self): temp_file_path = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_USER_TSV_FILE_KEY"] ) - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) - # checkファイルパスの作成 base_file_name = os.path.splitext(os.path.basename(temp_file_path))[0] check_file_name = f"{base_file_name}-check" part_check_file_name = f"{check_file_name}-part{page_number}" @@ -393,7 +394,7 @@ def check_pagination(self): @author_permission.require(http_exception=403) @expose('/check_file_download', methods=['POST']) def check_file_download(self): - """インポートチェック画面でダウンロード処理""" + """Download process on the import check screen.""" band_file_path = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_BAND_CHECK_USER_FILE_PATH_KEY"] ) @@ -438,7 +439,7 @@ def import_authors(self) -> jsonify: for affiliation_id in records: group_tasks.append(import_affiliation_id.s(affiliation_id)) elif is_target == "author_db": - # 既にある結果一時ファイルの削除 + # Delete existing result temporary files result_over_max_file_path = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_OVER_MAX_FILE_PATH_KEY"] ) @@ -467,7 +468,7 @@ def import_authors(self) -> jsonify: current_app.logger.error(f"Error deleting {result_file_path}: {e}") traceback.print_exc() - # 前回のimport結果サマリーを削除 + # Delete previous import result summary result_summary = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_SUMMARY_KEY"] ) @@ -478,7 +479,7 @@ def import_authors(self) -> jsonify: max_page_for_import_tab = data.get("max_page") - # フロントの最大表示数分だけrecordsを確保 + # Ensure records for the maximum number of front views records, reached_point, count = prepare_import_data(max_page_for_import_tab) task_ids =[] @@ -511,7 +512,6 @@ def import_authors(self) -> jsonify: }) task_ids.append(task.task_id) - # WEKO_AUTHORS_IMPORT_MAX_NUM_OF_DISPLAYSを超えた分を別のタスクで処理 if count > current_app.config.get("WEKO_AUTHORS_IMPORT_MAX_NUM_OF_DISPLAYS"): update_cache_data( current_app.config.get("WEKO_AUTHORS_IMPORT_CACHE_FORCE_CHANGE_MODE_KEY"), @@ -621,7 +621,7 @@ def check_import_status(self): @author_permission.require(http_exception=403) @expose('/result_download', methods=['POST']) def result_file_download(self): - """インポート結果画面でダウンロード処理""" + """Download process on the import result screen.""" json = request.get_json().get("json") result_file_path = current_cache.get( current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_FILE_PATH_KEY"] diff --git a/modules/weko-authors/weko_authors/api.py b/modules/weko-authors/weko_authors/api.py index 90ce893967..60bf5f1189 100644 --- a/modules/weko-authors/weko_authors/api.py +++ b/modules/weko-authors/weko_authors/api.py @@ -237,7 +237,7 @@ def get_pk_id_by_weko_id(cls, weko_id): } } - # 検索 + # Search indexer = RecordIndexer() result = indexer.client.search( index=current_app.config['WEKO_AUTHORS_ES_INDEX_NAME'], @@ -256,7 +256,7 @@ def get_pk_id_by_weko_id(cls, weko_id): @classmethod def get_weko_id_by_pk_id(cls, pk_id): - """pk_idからweko_idを取得します。 + """Get weko_id from pk_id. Args: pk_id (str): pk_id @@ -399,13 +399,13 @@ def mapping_max_item(cls, mappings, affiliation_mappings, records_count, retrys= ) if not records_count: records_count = cls.get_records_count(False, False) - # 削除されておらず、統合されていない著者の合計を取得 + # Get the total number of authors who are not deleted and not merged affiliation_mappings["max"] = [] - # AUTHOR_EXPORT_BATCH_SIZE分の数だけauthorを取得して、maxを計算する + # Get authors by AUTHOR_EXPORT_BATCH_SIZE and calculate max for i in range(0, records_count, size): authors = cls.get_by_range(i, size, False, False) - # 通常のマッピングの処理 + # Normal mapping processing for mapping in mappings: if mapping.get('child'): if not authors: @@ -421,18 +421,18 @@ def mapping_max_item(cls, mappings, affiliation_mappings, records_count, retrys= if mapping['max'] == 0: mapping['max'] = 1 - # 所属情報のマッピングの処理 + # Affiliation info mapping processing mapping_max = affiliation_mappings["max"] - # 著者DBの所属情報の最大値をそれぞれとる + # Get the maximum value of affiliation info in the author DB for author in authors: json_data = author.json - # affiliation_mappings配下に以下を作る。 - # affiliationInfoの列数だけこれが作られるものとする。 + # Under affiliation_mappings, create the following. + # This is created for each column of affiliationInfo. # max:[ # { - # "identifierInfo" : affiliationInfo[i]identifierInfoの長さ, - # "affiliationNameInfo" : affiliationInfo[i]affiliationNameInfoの長さ, - # "affiliationPeriodInfo" : affiliationInfo[i]affiliationPeriodInfoの長さ + # "identifierInfo" : length of affiliationInfo[i].identifierInfo, + # "affiliationNameInfo" : length of affiliationInfo[i].affiliationNameInfo, + # "affiliationPeriodInfo" : length of affiliationInfo[i].affiliationPeriodInfo # }, # ] for index, affiliation in enumerate(json_data.get(affiliation_mappings["json_id"], [])): @@ -447,7 +447,7 @@ def mapping_max_item(cls, mappings, affiliation_mappings, records_count, retrys= if child_length > mapping_max[index][child["json_id"]]: mapping_max[index][child["json_id"]] = child_length - # 最後にWEKOIDの分を最大値から引く + # Finally, subtract the WEKOID part from the maximum value for mapping in mappings: if mapping['json_id'] == 'authorIdInfo': if mapping['max'] > 1: @@ -582,11 +582,11 @@ def prepare_export_data(cls, mappings, affiliation_mappings, authors, schemes, a else: row.append(json_data.get(mapping['json_id'])) - # 各affilitionInfo + # Process mapping for each affiliationInfo aff_data = json_data.get(affiliation_mappings['json_id'], []) affiliation_idx_start = 0 affiliation_idx_size = len(aff_mapping_max) - # affilaitionのマックスだけ繰り返す + # Repeat for the maximum number of affiliations for i in range(affiliation_idx_start, affiliation_idx_size): for child in affiliation_mappings.get('child'): aff_child_idx_start = 0 @@ -628,7 +628,7 @@ def prepare_export_prefix(cls, target_prefix, prefixes): row_data = [] for prefix in prefixes: - # 著者識別子のプレフィックスはWEKOのものは除外 + # Exclude WEKO's own prefix for author identifier prefixes if target_prefix == "id_prefix" and prefix.scheme == "WEKO": continue row = [prefix.scheme, prefix.name, prefix.url] diff --git a/modules/weko-authors/weko_authors/config.py b/modules/weko-authors/weko_authors/config.py index 40c9b2f7ef..6193a74bc8 100644 --- a/modules/weko-authors/weko_authors/config.py +++ b/modules/weko-authors/weko_authors/config.py @@ -73,7 +73,7 @@ WEKO_AUTHORS_EXPORT_CACHE_TEMP_FILE_PATH_KEY = 'weko_authors_export_temp_file_path_key' WEKO_AUTHORS_EXPORT_CACHE_STOP_POINT_KEY = "weko_authors_export_stop_point" WEKO_AUTHORS_EXPORT_TMP_PREFIX = 'authors_export_' -WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH = "/var/tmp/authors_export" # os.path.join(os.environ.get("TMPDIR"), "authors_export") +WEKO_AUTHORS_EXPORT_TMP_DIR = "authors_export" WEKO_AUTHORS_EXPORT_BATCH_SIZE = 1000 WEKO_AUTHORS_BULK_EXPORT_MAX_RETRY = 5 WEKO_AUTHORS_BULK_EXPORT_RETRY_INTERVAL = 5 @@ -344,7 +344,7 @@ """Template for the import page.""" WEKO_AUTHORS_IMPORT_TMP_PREFIX = 'authors_import_' -WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH = "var/tmp/authors_import"# os.path.join(os.environ.get("TMPDIR"), "authors_import") +WEKO_AUTHORS_IMPORT_TMP_DIR = "authors_import" WEKO_AUTHORS_IMPORT_ENTRYPOINTS = { 'is_import_available': '/admin/authors/import/is_import_available', diff --git a/modules/weko-authors/weko_authors/tasks.py b/modules/weko-authors/weko_authors/tasks.py index 43f22087ed..44b5157107 100644 --- a/modules/weko-authors/weko_authors/tasks.py +++ b/modules/weko-authors/weko_authors/tasks.py @@ -23,6 +23,7 @@ import glob import gc, json, csv import sys +import tempfile import traceback from datetime import datetime, timezone from time import sleep @@ -41,9 +42,11 @@ from weko_authors.config import WEKO_AUTHORS_IMPORT_CACHE_KEY -from .utils import export_authors, import_author_to_system, save_export_url, \ - set_export_status, export_prefix, import_id_prefix_to_system, import_affiliation_id_to_system, \ +from .utils import ( + export_authors, import_author_to_system, save_export_url, set_export_status, + export_prefix, import_id_prefix_to_system, import_affiliation_id_to_system, get_check_base_name, handle_exception, update_cache_data +) @shared_task def export_all(export_target): @@ -68,7 +71,17 @@ def export_all(export_target): @shared_task def import_author(author, force_change_mode, request_info): - """Import Author.""" + """Import Author. + + Args: + author (dict): Author data to import. + force_change_mode (bool): Whether to force change mode. + request_info (dict): Request information for logging. + + Returns: + dict: Result of the import operation. + + """ result = {'start_date': datetime.now().strftime("%Y-%m-%d %H:%M:%S")} retrys = current_app.config["WEKO_AUTHORS_BULK_EXPORT_MAX_RETRY"] interval = current_app.config["WEKO_AUTHORS_BULK_EXPORT_RETRY_INTERVAL"] @@ -78,10 +91,13 @@ def import_author(author, force_change_mode, request_info): del author["weko_id"] del author["current_weko_id"] try: - # コネクションエラー時にリトライ処理を行う + # Retry processing in case of connection error for attempt in range(retrys): try: - import_author_to_system(author, status, weko_id, force_change_mode, request_info=request_info) + import_author_to_system( + author, status, weko_id, force_change_mode, + request_info=request_info + ) result['status'] = states.SUCCESS break except SQLAlchemyError as ex: @@ -106,7 +122,14 @@ def import_author(author, force_change_mode, request_info): @shared_task def import_id_prefix(prefix): - """Import ID Prefix.""" + """Import ID Prefix. + + Args: + prefix (dict): id_prefix metadata from tsv/csv. + + Returns: + dict: Result of the import operation. + """ result = {'start_date': datetime.now().strftime("%Y-%m-%d %H:%M:%S")} try: import_id_prefix_to_system(prefix) @@ -124,7 +147,14 @@ def import_id_prefix(prefix): @shared_task def import_affiliation_id(affiliation_id): - """Import Affiliation ID.""" + """Import Affiliation ID. + + Args: + affiliation_id (dict): affiliation_id metadata from tsv/csv. + + Returns: + dict: Result of the import operation. + """ result = {'start_date': datetime.now().strftime("%Y-%m-%d %H:%M:%S")} try: import_affiliation_id_to_system(affiliation_id) @@ -141,21 +171,28 @@ def import_affiliation_id(affiliation_id): return result @shared_task -def import_author_over_max(reached_point, task_ids ,max_part, request_info=None): +def import_author_over_max(reached_point, task_ids, max_part, request_info=None): """ - WEKO_AUTHORS_IMPORT_MAX_NUM_OF_DISPLAYSを超えた著者をインポートする場合の処理です。 - 先に行っているインポートタスクが終了次第、reached_pointから一時ファイルを用いて - 著者インポートを開始します。 + Processing for importing authors when exceeding max display. + After the preceding import tasks are completed, author import will start + from reached_point using temporary files. + Args: - reached_point: 一時ファイルにおいてmax_displayに達した位置 - part_numberが一時ファイルのpart数で、countが一時ファイルの再開位置 - データ例:{"part_number": 101, "count": 3} - task_ids: 先に行っているmax_diplay分のタスクID. - max_part: パート数の最大値 - request_info: リクエスト情報 + reached_point (dict[str, int]): + The position in the temporary file where max_display was reached. + - part_number: the number of parts in the temporary file, + - count: the restart position in the temporary file. + + Example data: {"part_number": 101, "count": 3} + task_ids (list): List of task IDs that have been executed before max_display. + max_part (int): Maximum number of parts. + request_info (dict): Request information for logging. + + Returns: + dict: Result of the import operation. """ - # task_idsの全てのtaskが終了するまで待つ + # Wait until all tasks in task_ids are finished check_task_end(task_ids) del task_ids gc.collect() @@ -181,17 +218,23 @@ def import_author_over_max(reached_point, task_ids ,max_part, request_info=None) def import_authors_from_temp_files(reached_point, max_part, request_info=None): """ - 一時ファイルから著者データを読み込み、インポートする処理を行います。 + Reads author data from temporary files and imports them. Args: - reached_point: 一時ファイルにおいてmax_displayに達した位置 - part_numberが一時ファイルのpart数で、countが一時ファイルの再開位置 - データ例:{"part_number": 101, "count": 3} - max_part: インポートする最大のpart数 - request_info: リクエスト情報 + reached_point (dict[str, int]): + The position in the temporary file where max_display was reached. + - part_number: the number of parts in the temporary file, + - count: the restart position in the temporary file. + + Example data: {"part_number": 101, "count": 3} + max_part (int): Maximum number of parts. + request_info (dict): Request information for logging. """ - # 結果ファイルのDL用に一時ファイルを作成 - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + # Create a temporary file for downloading the result file + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) result_file_download_name = "{}_{}.{}".format( "import_author_result_for_over_max", datetime.now().strftime("%Y%m%d%H%M"), @@ -206,38 +249,38 @@ def import_authors_from_temp_files(reached_point, max_part, request_info=None): current_app.config["WEKO_AUTHORS_CACHE_TTL"] ) - # すべてのtaskが終了したら、max_display以降のtaskを実行 - # part_numberから始めて、max_partまでのpartをインポートする。 + # After all tasks are finished, execute tasks after max_display. + # Start from part_number and import up to max_part. authors = [] for i in range(1, max_part+1): part_check_file_name = f"{check_file_name}-part{i}" check_file_part_path = os.path.join(temp_folder_path, part_check_file_name) - # iがreached_pointのpart_number以上の時にauthorsを追加 + # Add authors when i is greater than or equal to reached_point's part_number if i >= reached_point.get("part_number"): - #一時ファイルからインポートできるファイルを取得 + # Get importable files from temporary files with open(check_file_part_path, "r", encoding="utf-8-sig") as check_part_file: data = json.load(check_part_file) for index, item in enumerate(data): - # max_display以降のところまで飛ばす + # Skip up to the position after max_display if i == reached_point.get("part_number") and index < reached_point.get("count"): continue check_result = False if item.get("errors", []) else True if check_result: item.pop("warnings", None) authors.append(item) - # authorsが長さWEKO_AUTHORS_IMPORT_BATCH_SIZEを超えた時点でインポート + # Import when authors exceeds WEKO_AUTHORS_IMPORT_BATCH_SIZE if len(authors) >= current_app.config.get("WEKO_AUTHORS_IMPORT_BATCH_SIZE"): import_authors_for_over_max(authors, request_info=request_info) authors = [] - # 一時ファイルの削除 + # Delete temporary file try: os.remove(check_file_part_path) current_app.logger.debug(f"Deleted: {check_file_part_path}") except Exception as e: traceback.print_exc(file=sys.stdout) current_app.logger.error(f"Error deleting {check_file_part_path}: {e}") - # authorsが残っている場合 + # If authors remain if authors: import_authors_for_over_max(authors, request_info=request_info) authors = [] @@ -250,7 +293,7 @@ def import_authors_for_over_max(authors, request_info=None): force_change_mode = current_cache.get(\ current_app.config.get("WEKO_AUTHORS_IMPORT_CACHE_FORCE_CHANGE_MODE_KEY", False) ) - _request_info = request_info or UserActivityLogger.get_summary_from_request() + _request_info = request_info or UserActivityLogger.get_summary_from_request() if not UserActivityLogger.get_log_group_id(request_info): UserActivityLogger.issue_log_group_id(None) _request_info["log_group_id"] = UserActivityLogger.get_log_group_id(request_info) @@ -258,12 +301,12 @@ def import_authors_for_over_max(authors, request_info=None): for author in authors: group_tasks.append(import_author.s(author, force_change_mode, _request_info)) - # group_tasksを実行 + # Execute group_tasks import_task = group(group_tasks).apply_async() import_task.save() for idx, task in enumerate(import_task.children): full_name_info ="" - # フルネーム生成 + # Generate full name for author_name_info in authors[idx].get("authorNameInfo", [{}]): family_name = author_name_info.get("familyName", "") first_name = author_name_info.get("firstName", "") @@ -284,7 +327,7 @@ def import_authors_for_over_max(authors, request_info=None): }) task_ids.append(task.task_id) - # task_idsの全てのtaskが終了するまで待つ + # Wait until all tasks in task_ids are finished check_task_end(task_ids) del task_ids gc.collect() @@ -307,7 +350,7 @@ def import_authors_for_over_max(authors, request_info=None): success_count += 1 elif status == states.FAILURE: failure_count += 1 - # ここにはいる_taskは時間がかかりすぎているもの,何かしらの問題が起きている。 + # _task here is for tasks that took too long or had some problem. else: start_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") end_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -324,11 +367,11 @@ def import_authors_for_over_max(authors, request_info=None): "status": status, "error_id": error_id }) - # 完了した時点でtaskを削除 + # Delete task after completion task.forget() del tasks write_result_temp_file(result) - # インポート結果をサマリーに追加する処理 + # Add import results to summary update_summary(success_count, failure_count) del authors @@ -336,19 +379,24 @@ def import_authors_for_over_max(authors, request_info=None): gc.collect() def write_result_temp_file(result): - """ - 引数resultからtaskの結果を取得し、一時ファイルに書き込む - args: - result: taskの結果とweko_idとfull_nameをまとめたもの - 例:[{ - "start_date": start_date, - "end_date": end_date, - "weko_id": 1, - "full_name": "kimura,shinji", - "type": "new", - "status": SUCCESS, - "error_id": "delete_author_link" + """Write the result of the task to a temporary file. + + Get the result of the task from the argument result and write it to a temporary file. + + Args: + result: Contains the result of the task, weko_id, and full_name. + + Example: + >>> result = [{ + ... "start_date": start_date, + "end_date": end_date, + "weko_id": 1, + "full_name": "kimura,shinji", + "type": "new", + "status": SUCCESS, + "error_id": "delete_author_link" }, ...] + >>> write_result_temp_file(result) """ result_file_path = current_cache.get(current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_OVER_MAX_FILE_PATH_KEY"]) @@ -375,11 +423,11 @@ def write_result_temp_file(result): raise e def update_summary(success_count, failure_count): - """ - インポート結果をサマリーに追加する処理 - args: - success_count: 成功したインポート数 - failure_count: 失敗したインポート数 + """Add import results to summary. + + Args: + success_count: Number of successful imports. + failure_count: Number of failed imports. """ summary = get_cache_data(current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_SUMMARY_KEY"]) if summary: @@ -413,6 +461,14 @@ def prepare_display_status(status, type, error_id): return msg def prepare_success_msg(type): + """Prepare success message based on type. + + Args: + type (str): Type of success message ('new', 'update', 'deleted'). + + Returns: + str: Success message with translation. + """ switcher = { 'new': _('Register Success'), 'update': _('Update Success'), @@ -420,7 +476,7 @@ def prepare_success_msg(type): } return switcher.get(type, '') -# 3秒ごとにtask_idsを確認し、全てのtaskが終了したらループを抜ける +# Check task_ids every 3 seconds, and exit the loop when all tasks are finished def check_task_end(task_ids): length = len(task_ids) sleep_time = current_app.config.get("WEKO_AUTHORS_BULK_IMPORT_RETRY_INTERVAL") @@ -470,34 +526,44 @@ def check_tmp_file_time_for_author(): """Check the storage time of the author temp file.""" # 1日 ttl = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FILE_RETENTION_PERIOD") - author_export_temp_dirc_path = current_app.config.get("WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH") - author_import_temp_dirc_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") - author_export_temp_file_path = os.path.join(author_export_temp_dirc_path, "**") - author_import_temp_file_path = os.path.join(author_import_temp_dirc_path, "**") + export_temp_dir = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_EXPORT_TMP_DIR") + ) + import_temp_dir = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) + export_temp_file = os.path.join(export_temp_dir, "**") + import_temp_file = os.path.join(import_temp_dir, "**") now = datetime.now(timezone.utc) - # 著者エクスポートの一時ファイルの削除 - for d in glob.glob(author_export_temp_file_path): + # Delete temporary files for author export + for d in glob.glob(export_temp_file): tLog = os.path.getmtime(d) if (now - datetime.fromtimestamp(tLog, timezone.utc)).total_seconds() >= ttl: try: os.remove(d) except OSError as e: current_app.logger.error(e) - # ディレクトリが空かどうかを確認し、空の場合はディレクトリを削除 - if os.path.exists(author_export_temp_dirc_path) and \ - not os.listdir(author_export_temp_file_path): - os.rmdir(author_export_temp_file_path) - - # 著者インポートの一時ファイルの削除 - for d in glob.glob(author_import_temp_file_path): + # Check if the directory is empty, and if it is, remove the directory + if ( + os.path.exists(export_temp_dir) + and not os.listdir(export_temp_file) + ): + os.rmdir(export_temp_file) + + # Delete temporary files for author import + for d in glob.glob(import_temp_file): tLog = os.path.getmtime(d) if (now - datetime.fromtimestamp(tLog, timezone.utc)).total_seconds() >= ttl: try: os.remove(d) except OSError as e: current_app.logger.error(e) - # ディレクトリが空かどうかを確認し、空の場合はディレクトリを削除 - if os.path.exists(author_import_temp_dirc_path) and \ - not os.listdir(author_import_temp_file_path): - os.rmdir(author_import_temp_file_path) + # Check if the directory is empty, and if it is, remove the directory + if ( + os.path.exists(import_temp_dir) + and not os.listdir(import_temp_file) + ): + os.rmdir(import_temp_file) diff --git a/modules/weko-authors/weko_authors/utils.py b/modules/weko-authors/weko_authors/utils.py index e76dd8a7d8..a7581d0637 100644 --- a/modules/weko-authors/weko_authors/utils.py +++ b/modules/weko-authors/weko_authors/utils.py @@ -48,19 +48,20 @@ from invenio_db import db from invenio_indexer.api import RecordIndexer -from weko_authors.contrib.validation import validate_by_extend_validator, \ - validate_external_author_identifier, validate_map, validate_required, \ - check_weko_id_is_exits_for_import - +from .contrib.validation import ( + validate_by_extend_validator, validate_external_author_identifier, + validate_map, validate_required, check_weko_id_is_exits_for_import +) from .api import WekoAuthors from .models import AuthorsPrefixSettings, AuthorsAffiliationSettings, Authors def update_cache_data(key: str, value: str, timeout=None): """Create or Update cache data. - :param key: Cache key. - :param value: Cache value. - :param timeout: Cache expired. + Args: + key (str): Cache key. + value (str): Cache value. + timeout (optional): Cache expired. """ if timeout is not None: current_cache.set(key, value, timeout=timeout) @@ -77,14 +78,21 @@ def get_author_prefix_obj(scheme): return None def get_author_prefix_obj_by_id(id): - """Check item Scheme exist in DB.""" + """Check if the item Scheme exists in the DB by ID. + + Args: + id (int): author_prefix record ID. + + Returns: + AuthorsPrefixSettings: The prefix object if exists, else None. + """ try: - return db.session.query(AuthorsPrefixSettings).filter( + obj = AuthorsPrefixSettings.query.filter( AuthorsPrefixSettings.id == id).one_or_none() except Exception as ex: current_app.logger.error(ex) traceback.print_exc() - return None + return obj if isinstance(obj, AuthorsPrefixSettings) else None def get_author_affiliation_obj(scheme): """Check item Scheme exist in DB.""" @@ -97,14 +105,21 @@ def get_author_affiliation_obj(scheme): return None def get_author_affiliation_obj_by_id(id): - """Check item Scheme exist in DB.""" + """Check if the item Scheme exists in the DB by ID. + + Args: + id (int): ID value. + + Returns: + AuthorsAffiliationSettings: The affiliation object if exists, else None. + """ try: - return db.session.query(AuthorsAffiliationSettings).filter( + obj = AuthorsAffiliationSettings.query.filter( AuthorsAffiliationSettings.id == id).one_or_none() except Exception as ex: current_app.logger.error(ex) traceback.print_exc() - return None + return obj if isinstance(obj, AuthorsAffiliationSettings) else None def check_email_existed(email: str): @@ -143,37 +158,43 @@ def check_email_existed(email: str): } def validate_weko_id(weko_id, pk_id = None): - """Validate WEKO ID.""" + """Validate WEKO ID. + + Args: + weko_id (str): WEKO ID. + pk_id (str, optional): Primary key ID. + + Returns: + tuple: (bool, str or None) + """ if not bool(re.fullmatch(r'[0-9]+', weko_id)): return False, "not half digit" - # jsonify(msg=_('The author ID must be the half digit.')), 500 try: result = check_weko_id_is_exists(weko_id, pk_id) except Exception as ex: current_app.logger.error(ex) raise ex - # 存在するならエラーを返す + if result == True: return False, "already exists" - # jsonify(msg=_('The value is already in use as WEKO ID.')), 500 return True, None def check_weko_id_is_exists(weko_id, pk_id = None): + """Check if weko_id exists in Elasticsearch. + + If author_id is the same as pk_id, skip checking. + weko_id is the value of authorId where authorIdInfo.Idtype is 1. + + Args: + weko_id (str): WEKO ID. + pk_id (str, optional): Primary key ID. + + Returns: + bool: True if exists, False otherwise. """ - weko_idが既に存在するかチェック - author_idが同じ場合はスキップする - ※weko_idはauthorIdInfo.Idtypeが1であるAuthorIdの値のことです。 - - args: - weko_id: weko_id - return: - True: weko_idが存在する - False: weko_idが存在しない - """ - # 同じweko_idが存在するかチェック query = { - "_source": ["pk_id", "authorIdInfo"], # authorIdInfoフィールドのみを取得 + "_source": ["pk_id", "authorIdInfo"], # Get Author id info field only "query": { "bool": { "must": [ @@ -191,16 +212,16 @@ def check_weko_id_is_exists(weko_id, pk_id = None): } } - # 検索 + # search from elasticsearch indexer = RecordIndexer() result = indexer.client.search( index=current_app.config['WEKO_AUTHORS_ES_INDEX_NAME'], body=query ) - # 同じweko_idが存在する場合はエラー + # if same weko_id exists, return True for res in result['hits']['hits']: - # 同じauthor_idの場合はスキップ + # if same author_id exists, skip checking if pk_id and pk_id == res['_source']['pk_id']: continue author_id_info_from_es = res['_source']['authorIdInfo'] @@ -213,13 +234,15 @@ def check_weko_id_is_exists(weko_id, pk_id = None): def check_period_date(data): - """ - dataのperiodを確認します。 - args: - data: dict, 著者DBのjsonカラムのデータ - return: - True or False: 期間が正しいかどうか - String: エラーの種別 + """Check period date. + + Args: + data (dict): json data of author DB. + + Returns: + tuple: (bool, str or None) + - bool: True if period date is valid, False otherwise. + - str: Error type if period date is invalid, None otherwise. """ from datetime import datetime if data.get("affiliationInfo"): @@ -285,22 +308,21 @@ def save_export_url(start_time, end_time, file_uri): return data def delete_export_url(): - """Delete exported url.""" + """Delete exported URL from cache.""" current_cache.delete(current_app.config.get("WEKO_AUTHORS_EXPORT_CACHE_URL_KEY")) def handle_exception(ex, attempt, retrys, interval, stop_point=0): - """ - エラーをログで流し、スリープとリトライ回数の管理を行います. - args: - ex(Exception): Exception object - attempt(int): Number of attempts - retrys(int): Number of retries - interval(int): Retry interval - stop_point(int): Stop point + """Manage sleep and retries. + Args: + ex (Exception): Exception object. + attempt (int): Number of attempts. + retrys (int): Number of retries. + interval (int): Retry interval. + stop_point (int, optional): Stop point. Defaults to 0. """ current_app.logger.error(ex) - # 最後のリトライの場合は例外をraise + # Raise the exception for the last retry if attempt == retrys - 1: current_app.logger.info(f"Connection failed, Stop export.") if stop_point != 0: @@ -314,7 +336,11 @@ def handle_exception(ex, attempt, retrys, interval, stop_point=0): sleep(interval) def export_authors(): - """Export all authors.""" + """Export all authors. + + Returns: + str: File URI. + """ from invenio_files_rest.models import FileInstance, Location file_uri = None retrys = current_app.config["WEKO_AUTHORS_BULK_EXPORT_MAX_RETRY"] @@ -330,27 +356,23 @@ def export_authors(): try: - # ある程度の処理をまとめてリトライ処理 for attempt in range(retrys): try: - # マッピングを取得 + # get mapping mappings = deepcopy(current_app.config["WEKO_AUTHORS_FILE_MAPPING"]) affiliation_mappings = deepcopy(current_app.config["WEKO_AUTHORS_FILE_MAPPING_FOR_AFFILIATION"]) - # 著者の数を取得(削除、統合された著者は除く) + # get the number of authors (excluding deleted and merged authors) records_count = WekoAuthors.get_records_count(False, False) - # マッピング上の複数が可能となる項目の最大値を取得 + # Get the maximum value of multiple items on the mapping mappings, affiliation_mappings = \ WekoAuthors.mapping_max_item(mappings, affiliation_mappings, records_count) - # 著者識別子の対応を取得 schemes = WekoAuthors.get_identifier_scheme_info() - - # 所属機関識別子の対応を取得 aff_schemes = WekoAuthors.get_affiliation_identifier_scheme_info() - # 一時ファイルのパスを取得 - temp_file_path=current_cache.get(\ + # Get the path of the temporary file + temp_file_path=current_cache.get( current_app.config["WEKO_AUTHORS_EXPORT_CACHE_TEMP_FILE_PATH_KEY"]) break except SQLAlchemyError as ex: @@ -363,12 +385,12 @@ def export_authors(): traceback.print_exc(file=stdout) handle_exception(ex, attempt, retrys, interval) - # stop_pointがあればstart_pointに代入 + # If stop_point is not None, set start_point to stop_point start_point = stop_point if stop_point else 0 - # 読み込み後削除 - current_cache.delete(\ + + current_cache.delete( current_app.config["WEKO_AUTHORS_EXPORT_CACHE_STOP_POINT_KEY"]) - # 1000ずつ著者を取得し、データを書き込む + # Get authors 1000 at a time and write data for i in range(start_point, records_count, size): current_app.logger.info(f"Export authors start_point:{start_point}") row_header = [] @@ -376,11 +398,11 @@ def export_authors(): row_label_jp = [] row_data = [] - # 著者情報取得のリトライ処理 + # Retry process for obtaining author information for attempt in range(retrys): current_app.logger.info(f"Export authors retry count:{attempt}") try: - # 著者情報をstartからWEKO_EXPORT_BATCH_SIZE分取得 + # Get author information from start in WEKO_EXPORT_BATCH_SIZE units authors = WekoAuthors.get_by_range(i, size, False, False) row_header, row_label_en, row_label_jp, row_data =\ WekoAuthors.prepare_export_data(mappings, affiliation_mappings, authors, schemes, aff_schemes, i, size) @@ -394,9 +416,9 @@ def export_authors(): except TimeoutError as ex: traceback.print_exc(file=stdout) handle_exception(ex, attempt, retrys, interval, stop_point=i) - # 一時ファイルに書き込み + # Write to temporary file write_to_tempfile(i, row_header, row_label_en, row_label_jp, row_data) - # 完成した一時ファイルをファイルインスタンスに保存 + # Save the completed temporary file to file instance with open(temp_file_path, 'rb') as f: reader = io.BufferedReader(f) # save data into location @@ -410,11 +432,12 @@ def export_authors(): file.writable = True file.set_contents(reader) file_uri = file.uri if file else None - # 完了時一時ファイルを削除 + # Delete temporary file after completion os.remove(temp_file_path) db.session.commit() except Exception as ex: db.session.rollback() + # If stop_point is not set, delete the temporary file if not current_cache.get(current_app.config["WEKO_AUTHORS_EXPORT_CACHE_STOP_POINT_KEY"]): os.remove(temp_file_path) current_app.logger.error(ex) @@ -428,12 +451,13 @@ def export_authors(): return file_uri def export_prefix(target): - """ - id_prefixまたはaffiliation_idをエクスポートする - args: - target(str): エクスポート対象 - return: - file_uri(str): ファイルURI + """Export id_prefix or affiliation_id. + + Args: + target (str): Export target. 'id_prefix' or 'affiliation_id'. + + Returns: + str: File URI. """ from invenio_files_rest.models import FileInstance, Location file_uri = None @@ -495,12 +519,14 @@ def export_prefix(target): return file_uri def check_file_name(export_target): - """ - ファイル名を取得する - args: - export_target(str): エクスポート対象 - return: - file_base_name(str): ファイル名 + """Get file name. + + Args: + export_target (str): + Export target. "author_db" or "id_prefix" or "affiliation_id". + + Returns: + str: File base name. """ file_base_name = "" if export_target == "author_db": @@ -512,24 +538,24 @@ def check_file_name(export_target): return file_base_name def write_to_tempfile(start, row_header, row_label_en, row_label_jp, row_data): + """Write data to a temporary file. + + Args: + start (int): Start position of data. + row_header (list): Header. + row_label_en (list): English labels. + row_label_jp (list): Japanese labels. + row_data (list): Data. """ - 一時ファイルにデータを書き込む - args: - start(int): データの開始位置 - row_header(array): ヘッダー - row_label_en(array): ラベル(英語) - row_label_jp(array): ラベル(日本語) - row_data(array): データ - """ - # 一時ファイルのパスを取得 + # Get the path of the temporary file temp_file_path=current_cache.get( \ current_app.config["WEKO_AUTHORS_EXPORT_CACHE_TEMP_FILE_PATH_KEY"]) - # ファイルを開いてデータを書き込む + # Open the file and write data try: with open(temp_file_path, 'a', newline='', encoding='utf-8-sig') as file: writer = csv.writer(file, delimiter='\t', quotechar='"', quoting=csv.QUOTE_MINIMAL) - # 1行目のみヘッダー、ラベルを書き込む + # Write header and labels only on the first line if start == 0: writer.writerow(row_header) writer.writerow(row_label_en) @@ -539,13 +565,14 @@ def write_to_tempfile(start, row_header, row_label_en, row_label_jp, row_data): current_app.logger.error(ex) traceback.print_exc(file=stdout) -def check_import_data(file_name: str): - """Validation importing tsv/csv file. +def check_import_data(file_name): + """Validate importing tsv/csv file. + + Args: + file_name (str): File name. - :argument - file_name(str) -- file name. - :return - return -- check information. + Returns: + dict: Check information. """ result = {} temp_file_path = current_cache.get( @@ -561,7 +588,10 @@ def check_import_data(file_name: str): file_format, file_name, temp_file_path, flat_mapping_ids ) list_import_id=[] - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) base_file_name = os.path.splitext(os.path.basename(temp_file_path))[0] check_file_name = f"{base_file_name}-check" num_total = 0 @@ -623,12 +653,15 @@ def check_import_data(file_name: str): return result def check_import_data_for_prefix(target, file_name: str, file_content: str): - """id_prefixかaffiliation_idのインポート用 tsv/csvファイルをバリデーションチェックする. - :argument - file_name(str) -- file name. - file_content(b64) -- content file. - :return - return -- check information. + """Validate tsv/csv file for id_prefix or affiliation_id import. + + Args: + target (str): Import target. + file_name (str): File name. + file_content (str): File content (base64). + + Returns: + dict: Check information. """ tmp_prefix = current_app.config['WEKO_AUTHORS_IMPORT_TMP_PREFIX'] temp_file = tempfile.NamedTemporaryFile(prefix=tmp_prefix) @@ -655,16 +688,13 @@ def check_import_data_for_prefix(target, file_name: str, file_content: str): return result def getEncode(filepath): - """ - getEncode [summary] - - [extended_summary] + """Detect file encoding. Args: - filepath ([type]): [description] + filepath (str): File path. Returns: - [type]: [description] + str: Detected encoding. """ with open(filepath, mode='rb') as fr: b = fr.read() @@ -672,19 +702,22 @@ def getEncode(filepath): return enc.get('encoding', 'utf-8-sig') def clean_deep(data): - """ - dataをクリーニングします。 - 例えば{'fullname': 'Jane Doe', - 'warnings': None, - 'email': {"test":"","test2":"test2"}, - 'test': [{"test":""},{"test2":"test2"}]} - のようなデータを - {'fullname': 'Jane Doe', 'email': {"test2":"test2"}, - 'test': [{"test2":"test2"}]}に変換します。 - args: - data (dict): data to clean - return: - data (dict): cleaned data + """Clean data recursively. + + Example: + {'fullname': 'Jane Doe', + 'warnings': None, + 'email': {"test":"","test2":"test2"}, + 'test': [{"test":""},{"test2":"test2"}]} + becomes + {'fullname': 'Jane Doe', 'email': {"test2":"test2"}, + 'test': [{"test2":"test2"}]} + + Args: + data (dict): Data to clean. + + Returns: + dict: Cleaned data. """ if isinstance(data, dict): return {k: clean_deep(v) for k, v in data.items() if v is not None and v != ''} @@ -759,27 +792,29 @@ def unpackage_and_check_import_file(file_format, file_name, temp_file_path, mapp if not data_parse_metadata: raise Exception({ - 'error_msg': _('Cannot read {} file correctly.').format(file_format.upper()) + 'error_msg': _('Cannot read {} file correctly.') + .format(file_format.upper()) }) file_data.append(dict(**data_parse_metadata)) - # file_dataがjson_sizeと同じになったら一時ファイルに書き込む + # Write to a temporary file when File data is the same as json size if len(file_data) == json_size: write_tmp_part_file(math.ceil((num-3)/json_size), file_data, temp_file_path) file_data = [] - # 書き込まれていないfile_dataを書き込む + # Write file_data that has not been written yet if len(file_data) != 0: write_tmp_part_file(math.ceil((count-3)/json_size), file_data, temp_file_path) - # ファイルの行数が3行未満の場合エラー + # Error if the number of lines in the file is less than 3 elif not file_data and count <= 3: current_app.logger.error("There is no data to import.") raise Exception({ 'error_msg': _('There is no data to import.') }) except UnicodeDecodeError as ex: - ex.reason = _('{} could not be read. Make sure the file' - + ' format is {} and that the file is' - + ' UTF-8 encoded.').format(file_name, file_format.upper()) + ex.reason = _( + '{} could not be read. Make sure the file format is ' + '{} and that the file is UTF-8 encoded.' + ).format(file_name, file_format.upper()) raise except Exception as ex: raise @@ -788,12 +823,16 @@ def unpackage_and_check_import_file(file_format, file_name, temp_file_path, mapp def write_tmp_part_file(part_num, file_data, temp_file_path): """Write data to temp file for Import. + Args: - part_num(int): count of list - file_data(list): Author data from tsv/csv. - temp_file_path(str): path of basefile + part_num (int): Count of list. + file_data (list): Author data from tsv/csv. + temp_file_path (str): Path of base file. """ - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) base_name = os.path.splitext(os.path.basename(temp_file_path))[0] part_file_name = f"{base_name}-part{part_num}" part_file_path = os.path.join(temp_folder_path, part_file_name) @@ -809,7 +848,8 @@ def validate_import_data(file_format, file_data, mapping_ids, mapping, list_impo mapping_ids (list): List only mapping ids. mapping (list): List mapping. list_import_id (list): List import id. - return: + + Returns: list: Author data after validation. """ authors_prefix = {} @@ -974,7 +1014,7 @@ def unpackage_and_check_import_file_for_prefix(file_format, file_name, temp_file traceback.print_exc() raise Exception({ 'error_msg': _('Cannot read {} file correctly.').format(file_format.upper()) - }) from export_authors + }) from ex file_data.append(tmp_data) except UnicodeDecodeError as ex: ex.reason = _('{} could not be read. Make sure the file' @@ -987,7 +1027,15 @@ def unpackage_and_check_import_file_for_prefix(file_format, file_name, temp_file def handle_check_consistence_with_mapping_for_prefix(keys, header): - """Check consistence with mapping.""" + """Check consistence with mapping. + + Args: + keys (list): Mapping keys. + header (list): Header row. + + Returns: + list: Not consistent items. + """ not_consistent_list = [] for item in header: if item not in keys: @@ -995,23 +1043,27 @@ def handle_check_consistence_with_mapping_for_prefix(keys, header): return not_consistent_list def validate_import_data_for_prefix(file_data, target): - """ - tsvからのデータを以下の観点でチェックする。 - ・キーschemeが空かどうか - ・キーnameが空かどうか - ・urlがURLの記述でない - ・作成か更新か削除か - ・schemeが既に存在する場合、更新 - ・存在しないschemeの場合、作成 - ・is_deletedがDの場合、削除 - ・targetがid_prefixの時、schemeにWEKOが入力がされているか - ・schemeで同じ値が二回出てきているか - ・削除の際に、そのschemeが存在するかどうか - ・削除の際に、その指定されたschemeが使用されているかどうか + """Validate data from tsv for prefix import. + + Checks: + - Whether 'scheme' key is empty. + - Whether 'name' key is empty. + - Whether 'url' is not a valid URL. + - Whether it is create, update, or delete. + if scheme is existing, it is update, + if scheme is not existing, it is create, + if is_deleted is 'D', it is delete. + - For id_prefix, whether 'scheme' is 'WEKO'. + - Whether the same value appears twice 'scheme'. + - For deletion, whether the specified scheme exists. + - For deletion, whether the specified scheme is used. Args: - file_data (json): unpackage_and_check_import_file_for_prefixの戻り値 - target (str): id_prefix or affiliation_id + file_data (list): Data from unpackage_and_check_import_file_for_prefix. + target (str): 'id_prefix' or 'affiliation_id'. + + Returns: + list: Validated data. """ if target == "id_prefix": existed_prefix = WekoAuthors.get_scheme_of_id_prefix() @@ -1028,30 +1080,31 @@ def validate_import_data_for_prefix(file_data, target): name = item.get('name', "") url = item.get('url', "") is_deleted = item.get('is_deleted') - # キーschemeが空かどうか + # Check if the 'scheme' key is empty if not scheme: errors.append(_("Scheme is required item.")) - # targetがid_prefixの時、schemeにWEKOが入力がされているか + # For id_prefix, check if 'scheme' is 'WEKO' if target == "id_prefix" and scheme == "WEKO": errors.append(_("The scheme WEKO cannot be used.")) - # キーnameが空かどうか + # Check if the 'name' key is empty if not name: errors.append(_("Name is required item.")) - # urlがあるとき、urlがURLの記述でない + # If url exists, check if url is not a valid URL if url and not url.startswith("http"): errors.append(_("URL is not URL format.")) if is_deleted == "D": + # For deletion, check if the specified scheme exists if scheme not in existed_prefix: errors.append(_("The specified scheme does not exist.")) else: - # schemeが著者DBで使用されている場合、削除しない + # For deletion, check if the specified scheme is used in author DB if scheme in used_scheme: errors.append(_("The specified scheme is used in the author ID.")) id = [k for k, v in id_type_and_scheme.items() if v == scheme] item['id'] = id[0] item['status'] = 'deleted' else: - # existed_prefixに含まれていれば更新、ないなら作成 + # If scheme exists in existed_prefix, it's update; otherwise, create if scheme in existed_prefix: id = [k for k, v in id_type_and_scheme.items() if v == scheme] item['id'] = id[0] @@ -1059,7 +1112,7 @@ def validate_import_data_for_prefix(file_data, target): else: item['status'] = 'new' - # schemeで同じ値が二回出てきているか + # Check if the same value appears twice in 'scheme' if scheme in list_import_scheme: errors.append(_("The specified scheme is duplicated.")) else: @@ -1070,13 +1123,19 @@ def validate_import_data_for_prefix(file_data, target): return file_data def band_check_file_for_user(max_page): - """ - 分割されているチェック結果をユーザー用に編集した後くっつけます。 - """ + """Merge split check results for user download. + + Args: + max_page (int): Maximum page number. - # checkファイルパスの作成 + Returns: + str: Path to the merged check file. + """ check_file_name = get_check_base_name() - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) check_file_download_name = "{}_{}.{}".format( "import_author_check_result", datetime.datetime.now().strftime("%Y%m%d%H%M"), @@ -1126,7 +1185,14 @@ def band_check_file_for_user(max_page): return check_file_path def get_check_result(entry): - """jsonのstatus,errorsをチェックし、それに合ったラベルを返します。""" + """Check status and errors in JSON and return a label. + + Args: + entry (dict): Entry data. + + Returns: + str: Check result label. + """ errors = entry.get("errors", []) status = entry.get("status", "") @@ -1263,29 +1329,36 @@ def flatten_authors_mapping(mapping, parent_key=None): return result_all, result_keys def prepare_import_data(max_page_for_import_tab): - """ - Prepare import data. + """Prepare import data for display and import. + Args: + max_page_for_import_tab (int): Maximum page number for import tab. + + Returns: + tuple (list, dict, int): """ - # checkファイルパスの作成 + # Create check file path check_file_name = get_check_base_name() max_display = current_app.config.get("WEKO_AUTHORS_IMPORT_MAX_NUM_OF_DISPLAYS") - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") - - # フロント表示用の著者データ + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) + # Author data for front display authors = [] - # インポートが実行される総数用の変数 + # Variable for the total number of imports to be executed count = 0 - # フロントに表示される著者データの最大数に達したポイントを記録 + # Record the point where the maximum number of author data displayed on + # the front is reached reached_point = {} for i in range(1, max_page_for_import_tab+1): part_check_file_name = f"{check_file_name}-part{i}" check_file_part_path = os.path.join(temp_folder_path, part_check_file_name) with open(check_file_part_path, "r", encoding="utf-8-sig") as check_part_file: data = json.load(check_part_file) - # jsonの中で何番目のデータかをカウント + # Count which data in json count_for_json = 0 for item in data: check_result = False if item.get("errors", []) else True @@ -1302,11 +1375,20 @@ def prepare_import_data(max_page_for_import_tab): count_for_json += 1 return authors, reached_point, count -def import_author_to_system(author, status, weko_id, force_change_mode, request_info=None): +def import_author_to_system( + author, status, weko_id, force_change_mode, request_info=None +): """Import author to DB and ES. Args: - author (object): Author metadata from tsv/csv. + author (dict): Author metadata from tsv/csv. + status (str): Import status. + weko_id (str): WEKO ID. + force_change_mode (bool): Force change mode. + request_info (dict, optional): Request info for logging. + + Raises: + Exception: If import fails. """ from weko_logging.activity_logger import UserActivityLogger if author: @@ -1387,13 +1469,20 @@ def import_author_to_system(author, status, weko_id, force_change_mode, request_ raise def create_result_file_for_user(json): + """Create a result file for the user. + The part displayed on the front end and the part managed on the back end + are taken separately and merged. + + Args: + json (list): Author data displayed at the front end. + + Returns: + str: Path to the result file. """ - ユーザー用の結果ファイルを作成します。 - フロントに表示されている分とバックエンドで管理されている部分を別々に持ってきて合体させます。 - args: - json (dict): フロントに表示される著者データ - """ - temp_folder_path = current_app.config.get("WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH") + temp_folder_path = os.path.join( + tempfile.gettempdir(), + current_app.config.get("WEKO_AUTHORS_IMPORT_TMP_DIR") + ) result_over_max_file_path = current_cache.get(\ current_app.config["WEKO_AUTHORS_IMPORT_CACHE_RESULT_OVER_MAX_FILE_PATH_KEY"]) result_file_name = "{}_{}.{}".format( @@ -1409,8 +1498,11 @@ def create_result_file_for_user(json): with open(result_file_path, "w", encoding="utf-8") as result_file: writer = csv.writer(result_file, delimiter='\t') # write header - writer.writerow(["No.", "Start Date", "End Date", "Previous WEKO ID", "New WEKO ID", "full_name", "Status"]) - # まずフロントから送られてきたjsonを書き込む + writer.writerow([ + "No.", "Start Date", "End Date", + "Previous WEKO ID", "New WEKO ID", "full_name", "Status" + ]) + # write the json sent from the front desk. for data in json: number = data.get("No.", "") start_date = data.get("Start Date", "") @@ -1438,7 +1530,14 @@ def create_result_file_for_user(json): return result_file_path def get_count_item_link(pk_id): - """Get count of item link of author.""" + """Get count of item link of author. + + Args: + pk_id (str): Author primary key ID. + + Returns: + int: Count of linked items. + """ count = 0 query_q = { "query": {"term": {"author_link.raw": pk_id}}, @@ -1458,7 +1557,11 @@ def get_count_item_link(pk_id): def count_authors(): - """Count authors from Elasticsearch.""" + """Count authors from Elasticsearch. + + Returns: + dict: Count result. + """ should = [ {'bool': {'must': [{'term': {'is_deleted': {'value': 'false'}}}]}}, {'bool': {'must_not': {'exists': {'field': 'is_deleted'}}}} @@ -1477,10 +1580,13 @@ def count_authors(): return result def import_id_prefix_to_system(id_prefix): - """ - tsv/csvからのid_prefixをDBにインポートする. + """Import id_prefix from tsv/csv to DB. + Args: - id_prefix (object): id_prefix metadata from tsv/csv. + id_prefix (dict): id_prefix metadata from tsv/csv. + + Raises: + Exception: If import fails. """ if id_prefix: retrys = current_app.config["WEKO_AUTHORS_BULK_IMPORT_MAX_RETRY"] @@ -1523,10 +1629,13 @@ def import_id_prefix_to_system(id_prefix): raise def import_affiliation_id_to_system(affiliation_id): - """ - tsv/csvからのaffiliation_idをDBにインポートする. + """Import affiliation_id from tsv/csv to DB. + Args: - affiliation_id (object): affiliation_id metadata from tsv/csv. + affiliation_id (dict): affiliation_id metadata from tsv/csv. + + Raises: + Exception: If import fails. """ if affiliation_id: retrys = current_app.config["WEKO_AUTHORS_BULK_IMPORT_MAX_RETRY"] @@ -1567,46 +1676,57 @@ def import_affiliation_id_to_system(affiliation_id): raise def update_data_for_weko_link(data, weko_link): - """ - authorsテーブルを元にweko_linkを更新し、 - 違いがある場合は、dataをauthorsテーブルのweko_idを元に更新します。 - args: - data: dict メタデータ、特にworkflowactivityのtemp_dataカラムのもの - weko_link: list weko_link + """Update weko_link based on authors table and update data if different. + Args: + data (dict): Metadata, especially from workflowactivity temp_data column. + weko_link (dict): weko_link mapping. """ old_weko_link = weko_link weko_link = copy.deepcopy(old_weko_link) - # weko_linkを新しくする。 + # Update weko_link with new values. for pk_id in weko_link.keys(): author = Authors.get_author_by_id(pk_id) if author: - # weko_idを取得する。 + # Get weko_id. author_id_info = author["authorIdInfo"] for i in author_id_info: - # idTypeが1の場合、weko_idを取得し、weko_linkを更新する。 + # If idType is 1, get weko_id and update weko_link. if i.get('idType') == '1': weko_link[pk_id] = i.get('authorId') break - # weko_linkが変更された場合、メタデータを更新する。 - if weko_link != old_weko_link: - for x_key, x_value in data.items(): - if not isinstance(x_value, list): + if weko_link == old_weko_link: + # If weko_link has not changed, do nothing. + return + # If weko_link has changed, update metadata. + for x_key, x_value in data.items(): + if not isinstance(x_value, list): + continue + for y_index, y in enumerate(x_value, start=0): + if not isinstance(y, dict): continue - for y_index, y in enumerate(x_value, start=0): - if not isinstance(y, dict): + for y_key, y_value in y.items(): + if not y_key == "nameIdentifiers": continue - for y_key, y_value in y.items(): - if not y_key == "nameIdentifiers": + for z_index, z in enumerate(y_value, start=0): + if ( + z.get("nameIdentifierScheme","") != "WEKO" + or z.get("nameIdentifier") not in old_weko_link.values() + ): continue - for z_index, z in enumerate(y_value, start=0): - if z.get("nameIdentifierScheme","") == "WEKO": - if z.get("nameIdentifier") in old_weko_link.values(): - # weko_linkから値がweko_idと一致するpk_idを取得する。 - pk_id = [k for k, v in old_weko_link.items() if v == z.get("nameIdentifier")][0] - data[x_key][y_index][y_key][z_index]["nameIdentifier"] = weko_link.get(pk_id) + # Get pk_id whose value matches weko_id from weko_link. + pk_id = [ + k for k, v in old_weko_link.items() + if v == z.get("nameIdentifier") + ][0] + z["nameIdentifier"] = weko_link.get(pk_id) def get_check_base_name(): + """Get base name for check file. + + Returns: + str: Base file name for check. + """ temp_file_path = current_cache.get(\ current_app.config["WEKO_AUTHORS_IMPORT_CACHE_USER_TSV_FILE_KEY"]) base_file_name = os.path.splitext(os.path.basename(temp_file_path))[0] diff --git a/modules/weko-index-tree/tests/conftest.py b/modules/weko-index-tree/tests/conftest.py index 20b2b6ec1d..5b386f869d 100644 --- a/modules/weko-index-tree/tests/conftest.py +++ b/modules/weko-index-tree/tests/conftest.py @@ -443,6 +443,7 @@ def base_app(instance_path): WEKO_INDEX_TREE_API = "/api/tree/index/", WEKO_THEME_INSTANCE_DATA_DIR="data", WEKO_HANDLE_ALLOW_REGISTER_CNRI=False, + WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY="Community Administrator" ) app_.url_map.converters['pid'] = PIDConverter @@ -912,11 +913,47 @@ def indices_for_api(app, db): updated=datetime(2025, 3, 3, 4, 3, 58, 104842) ) + comm_index = Index( + id=1745385873579, + parent=0, + position=2, + index_name="コミュニティインデックス", + index_name_english="Community Index", + browsing_role="", + contribute_role="", + browsing_group="", + contribute_group="", + public_state=True, + harvest_public_state=True, + owner_user_id=1, + created=datetime(2025, 3, 3, 4, 3, 33, 316001), + updated=datetime(2025, 3, 3, 4, 3, 58, 104842) + ) + + comm_child_index = Index( + id=1745385873580, + parent=1745385873579, + position=0, + index_name="コミュニティ子インデックス", + index_name_english="Community Child Index", + browsing_role="3,4,-98,-99", + contribute_role="3,4,-98,-99", + browsing_group="", + contribute_group="", + public_state=True, + harvest_public_state=True, + owner_user_id=1, + created=datetime(2025, 3, 3, 4, 3, 33, 316001), + updated=datetime(2025, 3, 3, 4, 3, 58, 104842) + ) + db.session.add(sample_index) db.session.add(parent_index) db.session.add(child_index_1) db.session.add(child_index_2) db.session.add(child_index_3) + db.session.add(comm_index) + db.session.add(comm_child_index) return { 'sample_index': sample_index, @@ -924,6 +961,8 @@ def indices_for_api(app, db): 'child_index_1': child_index_1, 'child_index_2': child_index_2, "child_index_3": child_index_3, + "comm_index": comm_index, + "comm_child_index": comm_child_index } @pytest.yield_fixture @@ -1634,7 +1673,7 @@ def create_tokens(client_api, client, users): "contributor": "index:read", "repoadmin": "index:update index:delete index:read index:create", "sysadmin": "index:update index:delete index:read index:create", - "comadmin": "index:read" + "comadmin": "index:update index:delete index:read index:create" } with db_.session.begin_nested(): @@ -1744,3 +1783,16 @@ def index_thumbnail(app,instance_path): return thumbnail_path + +@pytest.fixture() +def community_for_api(db, users, indices_for_api): + comm = Community( + id='comm_for_api', + id_user=users[4]['id'], + title='Test Comm Title', + root_node_id=indices_for_api['comm_index'].id, + id_role=users[4]['obj'].roles[0].id, + group_id=users[4]['obj'].roles[0].id + ) + db.session.add(comm) + db.session.commit() \ No newline at end of file diff --git a/modules/weko-index-tree/tests/test_rest.py b/modules/weko-index-tree/tests/test_rest.py index 7a3cf45508..0fe1ff86fa 100644 --- a/modules/weko-index-tree/tests/test_rest.py +++ b/modules/weko-index-tree/tests/test_rest.py @@ -616,7 +616,7 @@ def count_indices(self): return Index.query.count() # .tox/c1/bin/pytest --cov=weko_index_tree tests/test_rest.py::TestIndexManagementAPI::test_get_v1 -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko_index_tree/.tox/c1/tmp --full-trace -p no:warnings - def test_get_v1(self, app, client_rest, auth_headers_noroleuser, auth_headers_sysadmin, auth_headers_sysadmin_without_scope, create_auth_headers, indices_for_api, mocker): + def test_get_v1(self, app, client_rest, auth_headers_noroleuser, auth_headers_sysadmin, auth_headers_sysadmin_without_scope, create_auth_headers, indices_for_api, mocker, community_for_api): """ インデックス管理API-インデクス取得 - 全インデックス取得: ユーザー権限に応じた取得可否を確認 @@ -630,8 +630,8 @@ def test_get_v1(self, app, client_rest, auth_headers_noroleuser, auth_headers_sy oauth2.after_request(login_oauth2_user) default_indices = [0, 1740974499997, 1740974612379] - com_admin_indices = [0, 1740974499997, 1740974612379] - admin_indices = [0, 1623632832836, 1740974499997, 1740974554289, 1740974612379, 1740974612380] + com_admin_indices = [0, 1740974499997, 1740974612379, 1745385873579, 1745385873580] + admin_indices = [0, 1623632832836, 1740974499997, 1740974554289, 1740974612379, 1740974612380, 1745385873579, 1745385873580] # 全インデックス取得テスト(ユーザー権限に応じた取得可否を確認) self.run_get_all_indices(app, client_rest, auth_headers_noroleuser, 200, expected_indices=default_indices) @@ -758,7 +758,7 @@ def run_get_index_unauthorized(self, app, client_rest): # .tox/c1/bin/pytest --cov=weko_index_tree tests/test_rest.py::TestIndexManagementAPI::test_post_v1 -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko_index_tree/.tox/c1/tmp --full-trace -p no:warnings - def test_post_v1(self, app, db, client_rest, auth_headers_sysadmin, auth_headers_noroleuser, auth_headers_sysadmin_without_scope,create_auth_headers,admin_lang_setting, indices_for_api, mocker): + def test_post_v1(self, app, db, client_rest, auth_headers_sysadmin, auth_headers_noroleuser, auth_headers_sysadmin_without_scope,create_auth_headers,admin_lang_setting, indices_for_api, mocker, community_for_api): """ インデックス管理API-インデックス登録 - 正常系: インデックスの作成が成功するか確認 @@ -806,6 +806,16 @@ def test_post_v1(self, app, db, client_rest, auth_headers_sysadmin, auth_headers if role in allowed_roles: # print(f"{role} should be able to create index (200)") self.run_create_index_success(app, client_rest, headers, json_) + elif role == "comadmin": + # self.run_create_index_permission_error(app, client_rest, headers, json_) + self.run_create_index_forbidden(app, client_rest, headers) + json_comadmin = json_.copy() + json_comadmin["index"]["parent"] = 1745385873579 # コミュニティインデックスの親を指定 + self.run_create_index_success(app, client_rest, headers, json_comadmin) + json_comadmin["index"]["parent"] = 1745385873580 # コミュニティインデックスの子を指定 + self.run_create_index_success(app, client_rest, headers, json_comadmin) + json_comadmin["index"]["parent"] = 1740974554289 # コミュニティ以外の子を指定 + self.run_create_index_forbidden(app, client_rest, headers, json=json_comadmin) else: # print(f"{role} should NOT be able to create index (403)") self.run_create_index_forbidden(app, client_rest, headers) @@ -967,18 +977,20 @@ def run_create_index_unauthorized(self, app, client_rest): assert response.status_code == 401, "認証なしのリクエストが401にならなかった" # print(f"Unauthorizedアクセスエラー: {response.get_data(as_text=True)}") - def run_create_index_forbidden(self, app, client_rest, auth_headers): + def run_create_index_forbidden(self, app, client_rest, auth_headers, json=None): """ 権限のないユーザーが403エラーを受け取るか確認 """ url = "v1/tree/index/" payload = { "index": { - "index_name": "Forbidden Test" + "index_name": "Forbidden Test", + "parent": "0" } } + json = json or payload - response = client_rest.post(url, headers=auth_headers, json=payload) + response = client_rest.post(url, headers=auth_headers, json=json) assert response.status_code == 403, "権限なしユーザーのリクエストが403にならなかった" def run_create_index_server_error(self, app, client_rest, auth_headers): diff --git a/modules/weko-items-autofill/weko_items_autofill/utils.py b/modules/weko-items-autofill/weko_items_autofill/utils.py index 3cf7472d36..4ad14e9c3e 100644 --- a/modules/weko-items-autofill/weko_items_autofill/utils.py +++ b/modules/weko-items-autofill/weko_items_autofill/utils.py @@ -213,7 +213,7 @@ def get_doi_record_data(doi, item_type_id, activity_id): metadata = temp_data if isinstance(temp_data, dict) else json.loads(temp_data) metainfo = metadata.get("metainfo") metainfo_cleaned = remove_empty(metainfo) - doi_with_original = fetch_metadata_by_doi(doi, item_type_id, metainfo) + doi_with_original = fetch_metadata_by_doi(doi, item_type_id, metainfo_cleaned) doi_response = [{k: v} for k, v in doi_with_original.items()] return doi_response @@ -266,21 +266,7 @@ def fetch_metadata_by_doi(doi, item_type_id, original=None, **kwargs): # case: Original. if key == "Original": if isinstance(original, dict): - for k, v in original.items(): - if isinstance(v, dict): - filtered = {kk: vv for kk, vv in v.items() if vv} - if filtered: - response_metadata[k] = filtered - elif isinstance(v, list): - filtered = [ - item for item in v - if item and - (not isinstance(item, dict) or any(item.values())) - ] - if filtered: - response_metadata[k] = filtered - elif v: - response_metadata[k] = v + response_metadata = original else: current_app.logger.info("original metadata is not found.") continue diff --git a/modules/weko-items-ui/tests/test_utils.py b/modules/weko-items-ui/tests/test_utils.py index d99bb0c3ee..dfb02a0362 100644 --- a/modules/weko-items-ui/tests/test_utils.py +++ b/modules/weko-items-ui/tests/test_utils.py @@ -48,7 +48,6 @@ from weko_items_ui.utils import ( __sanitize_string, _custom_export_metadata, - _export_file, _export_item, _get_max_export_items, check_approval_email, @@ -8190,50 +8189,6 @@ def test_export_items_issue32943(app,db_itemtype,db_itemtype2,db_records,users,d assert res.status_code == 200 -# def _export_file(record_id, data_path=None): -# .tox/c1/bin/pytest --cov=weko_items_ui tests/test_utils.py::test__export_file -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-items-ui/.tox/c1/tmp -def test__export_file(app,db_records,mocker): - _, _, _, _, record, _ = db_records[0] - data_path= "./tests/" - with app.test_request_context(): - record_return_data = MagicMock() - record_data_1 = MagicMock() - record_data_1.info.return_value = {"accessrole": "close_restricted"} - # Mock file.obj.file.storage() - storage_mock = MagicMock() - record_data_1.obj.file.storage.return_value = storage_mock - record_data_1.obj.basename = "test.txt" - # Mock open() - open_mock = MagicMock() - open_mock.read.return_value = b"mocked file content" - storage_mock.open.return_value.__enter__.return_value = open_mock - - record_return_data.files = [record_data_1] - mocker.patch("weko_items_ui.utils.WekoRecord.get_record_by_pid", return_value=record_return_data) - with patch("weko_items_ui.utils.check_file_download_permission", return_value=True): - with patch("builtins.open", mock_open(read_data="data")) as mock_file: - mock_file.return_value = MagicMock() - mock_file.return_value.write.return_value = "data" - _export_file(record.id, data_path) - mock_file.assert_called_once_with("./tests/test.txt", "wb") - - with patch("weko_items_ui.utils.check_file_download_permission", return_value=False): - with patch("builtins.open", mock_open(read_data="data")) as mock_file: - mock_file.return_value = MagicMock() - mock_file.return_value.write.return_value = "data" - _export_file(record.id, data_path) - mock_file.assert_not_called() - - record_data_2 = MagicMock() - record_data_2.info.return_value = {"accessrole": "open_restricted"} - record_return_data.files = [record_data_2] - with patch("weko_items_ui.utils.check_file_download_permission", return_value=True): - with patch("builtins.open", mock_open(read_data="data")) as mock_file: - mock_file.return_value = MagicMock() - mock_file.return_value.write.return_value = "data" - _export_file(record.id, data_path) - mock_file.assert_not_called() - # def export_rocrate(post_data): # .tox/c1/bin/pytest --cov=weko_items_ui tests/test_utils.py::test_export_rocrate -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-items-ui/.tox/c1/tmp def test_export_rocrate(app,client,db_itemtype,db_records,users,mocker): @@ -10757,7 +10712,7 @@ def test_get_file_download_data(app, client, records): record = results[5]["record"] with pytest.raises(AvailableFilesNotFoundRESTError): get_file_download_data(record.id, record, filenames) - + # def get_weko_link(metadata): # .tox/c1/bin/pytest --cov=weko_items_ui tests/test_utils.py::test_get_weko_link -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-items-ui/.tox/c1/tmp def test_get_weko_link(app, client, users, db_records, mocker): @@ -10824,15 +10779,15 @@ def test_get_weko_link(app, client, users, db_records, mocker): } ) assert res == {} - + # not isinstance(x, list) is true res = get_weko_link({"metainfo": {"field1": "string_value"}}) assert res == {} - + # not isinstance(y, dict) is true res = get_weko_link({"metainfo": {"field1": ["string_value"]}}) assert res == {} - + # not key == "nameIdentifiers" is true res = get_weko_link({"metainfo": {"field1": [{"field2": {}}]}}) assert res == {} @@ -10851,27 +10806,27 @@ def test_check_duplicate(app, users,db_records3): # metadata format NG res, [], [] = check_duplicate({"metainfo":123},False) assert res == False - + # first_item not dict res, [], [] = check_duplicate({"subitem_identifier_uri":[{"subitem_identifier_uri"}]},True) assert res == False - + # subitem_identifier_uri NG res, [], [] = check_duplicate({"subitem_identifier_uri":[{"subitem_identifier_uri":"noexists"}]},True) assert res == False - + # subitem_identifier_uri OK res, recid_list, item_links = check_duplicate({"subitem_identifier_uri":[{"subitem_identifier_uri":"http://localhost"}]},True) assert recid_list[0] == 8 - + # subitem_title NG res, [], [] = check_duplicate({"subitem_title":[{"subitem_title":"title"}]},True) assert res == False - + # subitem_title:T resource_type:T res, [], [] = check_duplicate({"subitem_title":[{"subitem_title":"タイトル"}],"resourcetype":{"resourcetype":"Resource Type"}},True) assert res == False - + # creatorNames NG res, [], [] = check_duplicate({"creatorNames":[{"creatorNames":[{"creatorName":"test"}]}]},True) assert res == False @@ -11361,4 +11316,4 @@ def test_get_notification_targets_approver_db_error(app, db, users, db_workflow, with patch("weko_items_ui.utils.User.query") as mock_user_query: mock_user_query.filter.return_value.all.side_effect = SQLAlchemyError("DB Error") res = get_notification_targets_approver(activity) - assert res == {} \ No newline at end of file + assert res == {} diff --git a/modules/weko-items-ui/weko_items_ui/api.py b/modules/weko-items-ui/weko_items_ui/api.py index 81e37aa93c..e89d47cc05 100644 --- a/modules/weko-items-ui/weko_items_ui/api.py +++ b/modules/weko-items-ui/weko_items_ui/api.py @@ -16,7 +16,6 @@ """API for item login.""" from flask import current_app, json, session, url_for -from weko_accounts.utils import login_required_customize from weko_authors.utils import update_data_for_weko_link from weko_records.api import ItemTypes from weko_records.utils import find_items @@ -33,7 +32,7 @@ def item_login(item_type_id: int = 0): Returns: _type_: _description_ - """ + """ activity_id = None allow_multi_thumbnail = False endpoints = {} @@ -61,7 +60,7 @@ def item_login(item_type_id: int = 0): if activity_id: activity = WorkActivity() metadata = activity.get_activity_metadata(activity_id) - if metadata: + if metadata: item_json = json.loads(metadata) if "metainfo" in item_json: record = item_json.get("metainfo") @@ -77,7 +76,7 @@ def item_login(item_type_id: int = 0): if "weko_link" in item_json: weko_link = item_json.get("weko_link") update_data_for_weko_link(item_json.get("metainfo"), weko_link) - need_file, need_billing_file = is_schema_include_key(item_type.schema) + need_file, need_billing_file = is_schema_include_key(item_type.schema) if "subitem_thumbnail" in json.dumps(item_type.schema): need_thumbnail = True diff --git a/modules/weko-items-ui/weko_items_ui/utils.py b/modules/weko-items-ui/weko_items_ui/utils.py index af413d769d..fb95520843 100644 --- a/modules/weko-items-ui/weko_items_ui/utils.py +++ b/modules/weko-items-ui/weko_items_ui/utils.py @@ -27,12 +27,9 @@ import os import re import shutil -import sys import tempfile -import time import traceback import unicodedata -import secrets import pytz from collections import OrderedDict from datetime import date, datetime, timedelta, timezone @@ -50,7 +47,6 @@ from jsonschema import SchemaError, ValidationError from invenio_accounts.models import Role, userrole -from invenio_cache import current_cache from invenio_db import db from invenio_i18n.ext import current_i18n from invenio_indexer.api import RecordIndexer @@ -95,7 +91,6 @@ Activity, FlowAction, FlowActionRole, FlowDefine, ActionStatusPolicy as ASP ) from weko_workflow.utils import IdentifierHandle -from weko_admin.models import ApiCertificate def get_list_username(): @@ -1924,7 +1919,7 @@ def make_stats_file(item_type_id, recids, list_item_role, export_path=""): NameError: name '_' is not defined """ - from weko_records_ui.views import escape_newline, escape_str + from weko_records_ui.views import escape_newline item_type = ItemTypes.get_by_id(item_type_id) if item_type: @@ -2501,9 +2496,20 @@ def check_item_type_name(name): def export_items(post_data): - """Gather all the item data and export and return as a JSON or BIBTEX. + """Gather all the item data and export + + Export items in the selected format, such as a TSV, BIBTEX or RO-CRATE. + + Args: + post_data (dict): Data from the export form. + - export_file_contents_radio (str): Whether to include file contents. + - export_format_radio (str): The format to export (e.g., JSON, BIBTEX). + - record_ids (list): List of record IDs to export. + - invalid_record_ids (list): List of invalid record IDs. + - record_metadata (dict): Metadata for the records. - :return: JSON, BIBTEX + Returns: + Response: A file download response with the exported data. """ current_app.logger.debug("post_data:{}".format(post_data)) include_contents = ( @@ -2538,46 +2544,46 @@ def export_items(post_data): datetime_now = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") export_path = os.path.join(temp_path.name, datetime_now) - if export_format == "ROCRATE": - make_rocrate(record_ids, export_path) - else: # Double check for limits - for record_id in record_ids: - record_path = export_path + '/recid_' + str(record_id) - os.makedirs(record_path, exist_ok=True) - exported_item, list_item_role = _export_item( - record_id, - export_format, - include_contents, - record_path, - record_metadata.get(str(record_id)) - ) - result['items'].append(exported_item) + for record_id in record_ids: + record_path = os.path.join(export_path, f"recid_{record_id}") + os.makedirs(record_path, exist_ok=True) + exported_item, list_item_role = _export_item( + record_id, + export_format, + include_contents, + record_path, + record_metadata.get(str(record_id)) + ) + result['items'].append(exported_item) - item_type_id = exported_item.get('item_type_id') - item_type = ItemTypes.get_by_id(item_type_id) - if not item_types_data.get(item_type_id): - item_type_name = check_item_type_name( - item_type.item_type_name.name - ) - item_types_data[item_type_id] = { - 'item_type_id': item_type_id, - 'name': f'{item_type_name}({item_type_id})', - 'root_url': request.url_root, - 'jsonschema': 'items/jsonschema/' + item_type_id, - 'keys': [], - 'labels': [], - 'recids': [], - 'data': {}, - } - item_types_data[item_type_id]['recids'].append(record_id) + item_type_id = exported_item.get('item_type_id') + item_type = ItemTypes.get_by_id(item_type_id) + if not item_types_data.get(item_type_id): + item_type_name = check_item_type_name( + item_type.item_type_name.name + ) + item_types_data[item_type_id] = { + 'item_type_id': item_type_id, + 'name': f'{item_type_name}({item_type_id})', + 'root_url': request.url_root, + 'jsonschema': 'items/jsonschema/' + item_type_id, + 'keys': [], + 'labels': [], + 'recids': [], + 'data': {}, + } + item_types_data[item_type_id]['recids'].append(record_id) - # Create export info file - if export_format == 'BIBTEX': - write_bibtex_files(item_types_data, export_path) - else: - write_files(item_types_data, export_path, list_item_role) + # Create export info file + if export_format == 'BIBTEX': + write_bibtex_files(item_types_data, export_path) + elif export_format == 'ROCRATE': + write_rocrate(item_types_data, export_path, list_item_role) + else: + write_files(item_types_data, export_path, list_item_role) + if export_format != 'ROCRATE': # Create bag bagit.make_bag(export_path) # Create download file @@ -2587,40 +2593,38 @@ def export_items(post_data): export_path.split("/")[-2] + "-" + export_path.split("/")[-1] ) shutil.make_archive(zip_path, 'zip', export_path) + resp = send_file( + zip_path+".zip", + as_attachment=True, + attachment_filename='export.zip' + ) except Exception: current_app.logger.error("Failed to export items.") traceback.print_exc() flash(_('Error occurred during item export.'), 'error') - return redirect(url_for('weko_items_ui.export')) - resp = send_file( - zip_path+".zip", - as_attachment=True, - attachment_filename='export.zip' - ) + resp = redirect(url_for('weko_items_ui.export')) os.remove(zip_path+".zip") temp_path.cleanup() return resp -def make_rocrate(record_ids, export_path): +def write_rocrate(item_types_data, export_path, list_item_role): """Make RO-Crate BagIt for export. Args: - record_ids (list): List of record IDs to export. + item_types_data (dict): Item types data for export. export_path (str): Path to export the RO-Crate. + list_item_role (dict): List of item roles for export. """ + all_record_ids = [ + str(recid) + for data in item_types_data.values() + for recid in data['recids'] + ] # Get Metadata from ElasticSearch - metadata_dict = _get_metadata_dict_in_es(record_ids) + metadata_dict = _get_metadata_dict_in_es(all_record_ids) - for record_id in record_ids: - # crate each record directory - record_path = os.path.join(export_path, f"recid_{record_id}") - os.makedirs(record_path, exist_ok=True) - cannot_export = _export_file(record_id, record_path) - # Metadata - metadata, extracted_files = metadata_dict.get(str(record_id), ({}, [])) - - item_type_id = metadata.get("item_type_id") + for item_type_id, item_type_data in item_types_data.items(): mappings = JsonldMapping.get_by_itemtype_id(item_type_id) mapper = JsonLdMapper(item_type_id, None) for m in mappings: @@ -2630,31 +2634,41 @@ def make_rocrate(record_ids, export_path): if mapper.json_mapping is None: raise Exception("No valid mapping found for item type") - for key, value in metadata.items(): - if not isinstance(value, dict) or value.get("attribute_type") != "file": - continue - value["attribute_value_mlt"] = [ - file_metadata for file_metadata in value.get("attribute_value_mlt", []) - if file_metadata.get("filename") not in cannot_export - ] - - # Create RO-Crate info file - rocrate = mapper.to_rocrate_metadata( - metadata, extracted_files=extracted_files - ) - jsonld_path = os.path.join( - record_path, ROCRATE_METADATA_FILE.split("/")[-1] + record_ids = item_type_data['recids'] + headers, records = make_stats_file( + item_type_id, item_type_data['recids'], list_item_role, ) - with open(jsonld_path, "w", encoding="utf8") as f: - json.dump( - rocrate.metadata.generate(), f, indent=2, sort_keys=True, - ensure_ascii=False + + for record_id in record_ids: + # crate each record directory + record_path = os.path.join(export_path, f"recid_{record_id}") + os.makedirs(record_path, exist_ok=True) + title, extracted_files = metadata_dict.get(str(record_id), ("", [])) + + row_metadata = { + "recid": str(record_id), + "item_title": title, + "header": headers[0][2:], + "value": records[record_id], + } + + # Create RO-Crate info file + rocrate = mapper.to_rocrate_metadata( + tsv_row_metadata=row_metadata, extracted_files=extracted_files ) + jsonld_path = os.path.join( + record_path, ROCRATE_METADATA_FILE.split("/")[-1] + ) + with open(jsonld_path, "w", encoding="utf8") as f: + json.dump( + rocrate.metadata.generate(), f, indent=2, + ensure_ascii=False + ) - bagit.make_bag(record_path, checksums=["sha256"]) + bagit.make_bag(record_path, checksums=["sha256"]) - shutil.make_archive(record_path, "zip", record_path) - shutil.rmtree(record_path) + shutil.make_archive(record_path, "zip", record_path) + shutil.rmtree(record_path) # Create README.md file readme_path = os.path.join(export_path, "README.md") @@ -2678,19 +2692,20 @@ def _get_metadata_dict_in_es(record_ids): .filter("terms", control_number=record_ids) .filter("term", relation_version_is_last=True) .sort("control_number") - .source(["_item_metadata", "content"]) + .source(["title", "content"]) .params(from_=0, size=100) ) search_result = search.execute().to_dict() record_list = search_result.get("hits", {}).get("hits", []) for record in record_list: [key] = record.get("sort") - metadata = record.get("_source", {}).get("_item_metadata", {}) + title = record.get("_source", {}).get("title", [""])[0] content = record.get("_source", {}).get("content", []) extraction_file_list = [ file_content.get("filename") for file_content in content + if file_content.get("attachment") ] - metadata_dict.update({key: (metadata, extraction_file_list)}) + metadata_dict.update({key: (title, extraction_file_list)}) except NotFoundError as e: current_app.logger.error("Index do not exist yet: ", str(e)) @@ -2805,56 +2820,13 @@ def del_hide_sub_metadata(keys, metadata): exported_item['files'].append(file.info()) # TODO: Then convert the item into the desired format if file: - file_buffered = file.obj.file.storage().open() - temp_file = open( - tmp_path + '/' + file.obj.basename, 'wb') - temp_file.write(file_buffered.read()) - temp_file.close() + with file.obj.file.storage().open() as file_buffered, \ + open(tmp_path + '/' + file.obj.basename, 'wb') as temp_file: + temp_file.write(file_buffered.read()) return exported_item, list_item_role -def _export_file(record_id, data_path): - """Exports files for record according to view permissions. - - Args: - record_id (int): Record ID to export files from. - data_path (str): Path to save exported files. - - Returns: - list(str): - List of files that cannot be exported due to permissions. - """ - cannot_export = [] - record = WekoRecord.get_record_by_pid(record_id) - if record: - # Get files - for file in record.files: - if check_file_download_permission(record, file.info()): - accessrole = file.info().get("accessrole") - if accessrole == "open_restricted": - continue - if accessrole == "open_date": - file_date = file.info().get("date", {}) - date_value = ( - file_date[0].get("dateValue") - if isinstance(file_date, list) and file_date - else file_date.get("dateValue") - if isinstance(file_date, dict) - else None - ) - open_date = datetime.strptime(date_value, "%Y-%m-%d") - if open_date.date() > datetime.now().date(): - continue - with file.obj.file.storage().open() as file_buffered: - tmp_path = os.path.join(data_path, file.obj.basename) - with open(tmp_path, "wb") as temp_file: - temp_file.write(file_buffered.read()) - else: - cannot_export.append(file.info().get("filename", "Unknown file")) - return cannot_export - - def _custom_export_metadata(record_metadata: dict, hide_item: bool = True, replace_license: bool = True): """Custom export metadata. @@ -5464,14 +5436,20 @@ def send_mail_item_deleted(pid_value, deposit, user_id, shared_id=-1): Returns: int: The total number of successfully sent emails. """ - record_url = request.host_url + f"records/{pid_value}" - current_app.logger.debug(f"[send_mail_item_deleted] pid_value: {pid_value}, user_id: {user_id}") - return send_mail_from_notification_info( - get_info_func=lambda obj: get_notification_targets(obj, user_id, shared_id), - context_obj=deposit, - content_creator=create_item_deleted_data, - record_url=record_url - ) + try: + record_url = request.host_url + f"records/{pid_value}" + current_app.logger.debug(f"[send_mail_item_deleted] pid_value: {pid_value}, user_id: {user_id}") + return send_mail_from_notification_info( + get_info_func=lambda obj: get_notification_targets(obj, user_id, shared_id), + context_obj=deposit, + content_creator=create_item_deleted_data, + record_url=record_url + ) + except Exception as e: + current_app.logger.exception( + "Unexpected error occurred while sending mail for item deletion" + ) + return def send_mail_direct_registered(pid_value, user_id, share_id=-1): @@ -5486,12 +5464,18 @@ def send_mail_direct_registered(pid_value, user_id, share_id=-1): Returns: int: The total number of successfully sent emails. """ - deposit = WekoRecord.get_record_by_pid(pid_value) - record_url = request.host_url + f"records/{pid_value}" - current_app.logger.debug(f"[send_mail_direct_registered] pid_value: {pid_value}, user_id: {user_id}") - return send_mail_from_notification_info( - get_info_func=lambda obj: get_notification_targets(obj, user_id, share_id), - context_obj=deposit, - content_creator=create_direct_registered_data, - record_url=record_url - ) + try: + deposit = WekoRecord.get_record_by_pid(pid_value) + record_url = request.host_url + f"records/{pid_value}" + current_app.logger.debug(f"[send_mail_direct_registered] pid_value: {pid_value}, user_id: {user_id}") + return send_mail_from_notification_info( + get_info_func=lambda obj: get_notification_targets(obj, user_id, share_id), + context_obj=deposit, + content_creator=create_direct_registered_data, + record_url=record_url + ) + except Exception as e: + current_app.logger.exception( + "Unexpected error occurred while sending mail for item registration" + ) + return diff --git a/modules/weko-items-ui/weko_items_ui/views.py b/modules/weko-items-ui/weko_items_ui/views.py index 42fd62b384..040323697e 100644 --- a/modules/weko-items-ui/weko_items_ui/views.py +++ b/modules/weko-items-ui/weko_items_ui/views.py @@ -1064,7 +1064,7 @@ def prepare_edit_item(id=None, community=None): recid.object_uuid ) 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( diff --git a/modules/weko-itemtypes-ui/weko_itemtypes_ui/admin.py b/modules/weko-itemtypes-ui/weko_itemtypes_ui/admin.py index f58055186b..ef0d1ed297 100644 --- a/modules/weko-itemtypes-ui/weko_itemtypes_ui/admin.py +++ b/modules/weko-itemtypes-ui/weko_itemtypes_ui/admin.py @@ -24,39 +24,44 @@ import io import traceback -from flask import abort, current_app, flash, json, jsonify, redirect, \ - request, session, url_for, make_response, send_file +from flask import ( + abort, current_app, flash, json, jsonify, redirect, + request, session, url_for, send_file +) from sqlalchemy.sql.expression import null from flask_admin import BaseView, expose from flask_babelex import gettext as _ from flask_login import current_user +from zipfile import ZipFile, ZIP_DEFLATED +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema + from invenio_db import db from invenio_i18n.ext import current_i18n from weko_admin.models import AdminSettings, BillingPermission, AdminLangSettings from weko_logging.activity_logger import UserActivityLogger -from weko_logging.models import UserActivityLog -from weko_records.api import ItemsMetadata, ItemTypeEditHistory, \ - ItemTypeNames, ItemTypeProps, ItemTypes, Mapping +from weko_records.api import ( + ItemsMetadata, ItemTypeEditHistory, ItemTypeNames, + ItemTypeProps, ItemTypes, JsonldMapping, Mapping +) +from weko_records.models import ItemType, ItemTypeName, ItemTypeMapping, ItemTypeProperty from weko_records.serializers.utils import get_mapping_inactive_show_list from weko_records_ui.models import RocrateMapping -from weko_records.api import JsonldMapping from weko_schema_ui.api import WekoSchema from weko_search_ui.utils import get_key_by_property from weko_search_ui.tasks import is_import_running +from weko_swordserver.api import SwordClient from weko_workflow.api import WorkFlow -from .config import WEKO_BILLING_FILE_ACCESS, WEKO_BILLING_FILE_PROP_ATT, \ +from .config import ( + WEKO_BILLING_FILE_ACCESS, WEKO_BILLING_FILE_PROP_ATT, WEKO_ITEMTYPES_UI_DEFAULT_PROPERTIES_ATT +) from .permissions import item_type_permission -from .utils import check_duplicate_mapping, fix_json_schema, \ - has_system_admin_access, remove_xsd_prefix, \ +from .utils import ( + check_duplicate_mapping, fix_json_schema, + has_system_admin_access, remove_xsd_prefix, update_required_schema_not_exist_in_form, update_text_and_textarea -from zipfile import ZipFile, ZIP_DEFLATED -from marshmallow import fields, missing, post_dump, Schema -from weko_records.models import ItemType, ItemTypeName, ItemTypeMapping, ItemTypeProperty -from flask_marshmallow import Marshmallow, sqla -from marshmallow_sqlalchemy import ModelSchema, SQLAlchemyAutoSchema -from invenio_files_rest.models import FileInstance +) class ItemTypeMetaDataView(BaseView): """ItemTypeMetaDataView.""" @@ -128,95 +133,93 @@ def delete_itemtype(self, item_type_id=0): flash(_('Item type cannot be deleted becase import is in progress.'), 'error') return jsonify(code=-1) - if not item_type_id > 0: - flash(_('An error has occurred.'), 'error') - return jsonify(code=-1) - - record = ItemTypes.get_record(id_=item_type_id) - if record is not None: - # Check harvesting_type - if record.model.harvesting_type: - flash(_('Cannot delete Item type for Harvesting.'), 'error') - return jsonify(code=-1) - # Get all versions - all_records = ItemTypes.get_records_by_name_id( - name_id=record.model.name_id - ) - # Check that item type is already registered to an item or not - for item in all_records: - items = ItemsMetadata.get_registered_item_metadata( - item_type_id=item.id) - if len(items) > 0: - flash( - _('Cannot delete due to child existing item types.'), - 'error' - ) - return jsonify(code=-1) - # Check that item type is used SWORD API - jsonld_mappings = JsonldMapping.get_by_itemtype_id(item_type_id) - for jsonld_mapping in jsonld_mappings: - sword_clients = jsonld_mapping.sword_clients.all() - if sword_clients: - current_app.logger.info("Item type is used SWORD API.") - flash( - _('Cannot delete due to SWORD API is using this item types.'), - 'error' - ) - return jsonify(code=-1) - - # Get item type name - item_type_name = ItemTypeNames.get_record( - id_=record.model.name_id) - if all_records and item_type_name: - try: - # Delete item type name - ItemTypeNames.delete(item_type_name) - # Delete item typea - for k in all_records: - k.delete() - db.session.commit() - current_app.logger.info( - f"Item type deleted: {item_type_name.name}" - ) - except Exception: - db.session.rollback() - exec_info = sys.exc_info() - tb_info = traceback.format_tb(exec_info[2]) - current_app.logger.error( - "Unexpected error: {}".format(exec_info)) - UserActivityLogger.error( - operation="ITEM_TYPE_DELETE", - target_key=item_type_id, - remarks=tb_info[0] - ) - traceback.print_exc() - flash(_('Failed to delete Item type.'), 'error') + if item_type_id > 0: + record = ItemTypes.get_record(id_=item_type_id) + if record is not None: + # Check harvesting_type + if record.model.harvesting_type: + flash(_('Cannot delete Item type for Harvesting.'), 'error') return jsonify(code=-1) - + # Get all versions + all_records = ItemTypes.get_records_by_name_id( + name_id=record.model.name_id + ) + # Check that item type is already registered to an item or not + for item in all_records: + items = ItemsMetadata.get_registered_item_metadata( + item_type_id=item.id) + if len(items) > 0: + flash( + _('Cannot delete due to child existing item types.'), + 'error' + ) + return jsonify(code=-1) + # Check that item type is used SWORD API + jsonld_mappings = JsonldMapping.get_by_itemtype_id(item_type_id) for jsonld_mapping in jsonld_mappings: + if SwordClient.get_clients_by_mapping_id(jsonld_mapping.id): + current_app.logger.info("Item type is used SWORD API.") + flash( + _('Cannot delete due to SWORD API is using this item types.'), + 'error' + ) + return jsonify(code=-1) + + # Get item type name + item_type_name = ItemTypeNames.get_record( + id_=record.model.name_id) + if all_records and item_type_name: try: - # Delete itemtype JOSN-LD mapping - JsonldMapping.delete(jsonld_mapping.id) + # Delete item type name + ItemTypeNames.delete(item_type_name) + # Delete item typea + for k in all_records: + k.delete() db.session.commit() current_app.logger.info( - f"JSON-LD mapping deleted: {jsonld_mapping.name}" + f"Item type deleted: {item_type_name.name}" ) except Exception: db.session.rollback() + exec_info = sys.exc_info() + tb_info = traceback.format_tb(exec_info[2]) current_app.logger.error( - "Failed to delete Item type JSON-LD mapping: {}" - .format(jsonld_mapping.name) + "Unexpected error: {}".format(exec_info)) + UserActivityLogger.error( + operation="ITEM_TYPE_DELETE", + target_key=item_type_id, + remarks=tb_info[0] ) traceback.print_exc() + flash(_('Failed to delete Item type.'), 'error') + return jsonify(code=-1) + + for jsonld_mapping in jsonld_mappings: + try: + # Delete itemtype JOSN-LD mapping + JsonldMapping.delete(jsonld_mapping.id) + db.session.commit() + current_app.logger.info( + f"JSON-LD mapping deleted: {jsonld_mapping.name}" + ) + except Exception: + db.session.rollback() + current_app.logger.error( + "Failed to delete Item type JSON-LD mapping: {}" + .format(jsonld_mapping.name) + ) + traceback.print_exc() - current_app.logger.debug( - 'Itemtype delete: {}'.format(item_type_id)) - UserActivityLogger.info( - operation="ITEM_TYPE_DELETE", - target_key=item_type_id - ) - flash(_('Deleted Item type successfully.')) - return jsonify(code=0) + current_app.logger.debug( + 'Itemtype delete: {}'.format(item_type_id)) + UserActivityLogger.info( + operation="ITEM_TYPE_DELETE", + target_key=item_type_id + ) + flash(_('Deleted Item type successfully.')) + return jsonify(code=0) + flash(_('An error has occurred.'), 'error') + return jsonify(code=-1) @expose('/register', methods=['POST']) diff --git a/modules/weko-notifications/tests/test_utils.py b/modules/weko-notifications/tests/test_utils.py index 1280ef6db7..152af873c1 100644 --- a/modules/weko-notifications/tests/test_utils.py +++ b/modules/weko-notifications/tests/test_utils.py @@ -27,8 +27,8 @@ def test_inbox_url(app): assert inbox_url() == "http://inbox:8080/inbox" assert inbox_url(_external=True) == f"{app.config['THEME_SITEURL']}/inbox" - assert inbox_url("/test") == "http://inbox:8080/test" - assert inbox_url("/test", _external=True) == f"{app.config['THEME_SITEURL']}/test" + assert inbox_url(endpoint="/test") == "http://inbox:8080/test" + assert inbox_url(endpoint="/test", _external=True) == f"{app.config['THEME_SITEURL']}/test" # def user_uri(): diff --git a/modules/weko-notifications/tests/test_views.py b/modules/weko-notifications/tests/test_views.py index e0610c1c70..d61ad78672 100644 --- a/modules/weko-notifications/tests/test_views.py +++ b/modules/weko-notifications/tests/test_views.py @@ -35,8 +35,8 @@ def test_init_push_templates(app, mocker): init_push_templates() assert mock_post.call_count == 2 mock_post.assert_has_calls([ - mocker.call(inbox_url("/push-template"), json={"template": "test1"}), - mocker.call(inbox_url("/push-template"), json={"template": "test2"}) + mocker.call(inbox_url(endpoint="/push-template"), json={"template": "test1"}), + mocker.call(inbox_url(endpoint="/push-template"), json={"template": "test2"}) ]) mock_post.reset_mock() @@ -92,7 +92,7 @@ def test_user_settings(app, users, client, mocker): response = client.post("/account/settings/notifications/", data=form) assert response.status_code == 200 - mock_inbox_url.assert_called_once_with("/subscribe") + mock_inbox_url.assert_called_once_with(endpoint="/subscribe") mock_post.assert_not_called() mock_flash.assert_called_with("Failed to update push subscription.", category="error") @@ -111,7 +111,7 @@ def test_user_settings(app, users, client, mocker): response = client.post("/account/settings/notifications/", data=form) assert response.status_code == 200 - mock_inbox_url.assert_called_once_with("/unsubscribe") + mock_inbox_url.assert_called_once_with(endpoint="/unsubscribe") mock_post.assert_called_once() mock_flash.reset_mock() diff --git a/modules/weko-notifications/weko_notifications/config.py b/modules/weko-notifications/weko_notifications/config.py index 8b4df71c79..bdc56e3c4c 100644 --- a/modules/weko-notifications/weko_notifications/config.py +++ b/modules/weko-notifications/weko_notifications/config.py @@ -33,5 +33,8 @@ ] """COAR-Notify context.""" +COAR_NOTIFY_LINK_REL = "http://www.w3.org/ns/ldp#inbox" +"""COAR-Notify link relation type.""" + WEKO_NOTIFICATIONS_PUSH_TEMPLATE_PATH = "" """Path to the push template.""" diff --git a/modules/weko-notifications/weko_notifications/ext.py b/modules/weko-notifications/weko_notifications/ext.py index e23034529c..e17c8859db 100644 --- a/modules/weko-notifications/weko_notifications/ext.py +++ b/modules/weko-notifications/weko_notifications/ext.py @@ -12,6 +12,7 @@ from flask_babelex import gettext as _ from . import config +from .utils import inbox_url from .views import blueprint_ui @@ -39,6 +40,19 @@ def init_app(self, app): if app.config["WEKO_NOTIFICATIONS"]: app.register_blueprint(blueprint_ui) + @app.after_request + def inbox_link(response): + """Add inbox link to the response headers.""" + inbox_link = inbox_url(app=app, _external=True) + links = [ + link.strip() + for link in response.headers.get("Link", "").split(",") + if link + ] + [f'<{inbox_link}>; rel="{config.COAR_NOTIFY_LINK_REL}"'] + response.headers["Link"] = ", ".join(links) + + return response + def init_config(self, app): """Initialize configuration. diff --git a/modules/weko-notifications/weko_notifications/utils.py b/modules/weko-notifications/weko_notifications/utils.py index ec80c25cf7..acac2e56b0 100644 --- a/modules/weko-notifications/weko_notifications/utils.py +++ b/modules/weko-notifications/weko_notifications/utils.py @@ -24,25 +24,27 @@ from .client import NotificationClient from .config import WEKO_NOTIFICATIONS_USERS_URI -def inbox_url(endpoint=None, _external=False): +def inbox_url(app=None, endpoint=None, _external=False): """Return the inbox URL. Args: + app (Flask | None): The Flask application. Defaults to current_app. endpoint (str | None): The endpoint to append to the URL. _external (bool): Whether to return the URL with the full domain. Returns: str: The inbox URL. """ + app = app or current_app url = ( - current_app.config["THEME_SITEURL"] + app.config["THEME_SITEURL"] if _external - else current_app.config["WEKO_NOTIFICATIONS_INBOX_ADDRESS"] + else app.config["WEKO_NOTIFICATIONS_INBOX_ADDRESS"] ) if endpoint is not None: url += endpoint if endpoint.startswith("/") else f"/{endpoint}" else: - url += current_app.config["WEKO_NOTIFICATIONS_INBOX_ENDPOINT"] + url += app.config["WEKO_NOTIFICATIONS_INBOX_ENDPOINT"] return str(url) diff --git a/modules/weko-notifications/weko_notifications/views.py b/modules/weko-notifications/weko_notifications/views.py index 85b36d53c3..35dc9bebd2 100644 --- a/modules/weko-notifications/weko_notifications/views.py +++ b/modules/weko-notifications/weko_notifications/views.py @@ -68,7 +68,7 @@ def init_push_templates(): for template in templates: try: requests.post( - inbox_url("/push-template"), + inbox_url(endpoint="/push-template"), json=template ) except requests.RequestException as ex: @@ -134,8 +134,8 @@ def user_settings(): current_user.id, endpoint[:24] + "..." + endpoint[-8:] ) - requests.post(inbox_url("/subscribe"), json=subscripsion) - requests.post(inbox_url("/userprofile"), json=userprofile) + requests.post(inbox_url(endpoint="/subscribe"), json=subscripsion) + requests.post(inbox_url(endpoint="/userprofile"), json=userprofile) else: current_app.logger.info( "Unsubscribing push subscription for user %s: %s", @@ -143,7 +143,7 @@ def user_settings(): endpoint[:24] + "..." + endpoint[-8:] ) requests.post( - inbox_url("/unsubscribe"), json={"endpoint": endpoint} + inbox_url(endpoint="/unsubscribe"), json={"endpoint": endpoint} ) except Exception as ex: traceback.print_exc() diff --git a/modules/weko-records-ui/tests/conftest.py b/modules/weko-records-ui/tests/conftest.py index 0b055d1a17..887cd002f7 100644 --- a/modules/weko-records-ui/tests/conftest.py +++ b/modules/weko-records-ui/tests/conftest.py @@ -104,6 +104,7 @@ from weko_index_tree.models import Index, IndexStyle from weko_items_ui import WekoItemsUI from weko_items_ui.config import WEKO_ITEMS_UI_MS_MIME_TYPE,WEKO_ITEMS_UI_FILE_SISE_PREVIEW_LIMIT +from weko_logging.audit import WekoLoggingUserActivity from weko_records import WekoRecords from weko_records.api import ItemsMetadata, FilesMetadata from weko_records.models import ItemType, ItemTypeMapping, ItemTypeName, SiteLicenseInfo, FeedbackMailList,SiteLicenseIpAddress, RequestMailList @@ -330,6 +331,7 @@ def base_app(instance_path): # app_.register_blueprint(rest_blueprint) WekoDeposit(app_) WekoDepositREST(app_) + WekoLoggingUserActivity(app_) current_assets = LocalProxy(lambda: app_.extensions["invenio-assets"]) current_assets.collect.collect() diff --git a/modules/weko-records-ui/tests/test_api.py b/modules/weko-records-ui/tests/test_api.py index e30b6606dc..ab9963e09f 100644 --- a/modules/weko-records-ui/tests/test_api.py +++ b/modules/weko-records-ui/tests/test_api.py @@ -1,11 +1,12 @@ from smtplib import SMTPException from mock import patch import pytest -from flask import current_app +from flask import url_for,current_app,make_response import uuid from pytest_mock import mocker from sqlalchemy.exc import SQLAlchemyError +from invenio_accounts.testutils import login_user_via_session, create_test_user from weko_records_ui.api import send_request_mail, create_captcha_image, validate_captcha_answer from weko_records_ui.api import send_request_mail, create_captcha_image, get_s3_bucket_list, copy_bucket_to_s3, get_file_place_info, replace_file_bucket from weko_records_ui.errors import AuthenticationRequiredError, ContentsNotFoundError, InternalServerError, InvalidCaptchaError, InvalidEmailError, RequiredItemNotExistError @@ -249,13 +250,6 @@ def test_get_s3_bucket_list(app, db, users, client): # login for using currentuser login(client,obj=users[0]["obj"]) - mock_response = { - 'Buckets': [ - {'Name': 'bucket1'}, - {'Name': 'bucket2'}, - {'Name': 'bucket3'} - ] - } with db.session.begin_nested(): p = UserProfile() p.user_id = '1' @@ -272,13 +266,49 @@ def test_get_s3_bucket_list(app, db, users, client): result_list = get_s3_bucket_list() # success + mock_response_success = { + 'ResponseMetadata': { + 'HTTPStatusCode': 200 + }, + 'Buckets': [ + {'Name': 'bucket1'}, + {'Name': 'bucket2'}, + {'Name': 'bucket3'} + ] + } with patch('boto3.client') as mock_client: instance = mock_client.return_value - instance.list_buckets.return_value = mock_response + instance.list_buckets.return_value = mock_response_success result_list = get_s3_bucket_list() assert result_list == ['bucket1', 'bucket2', 'bucket3'] + # success no buckets + mock_response_success_no_buckets = { + 'ResponseMetadata': { + 'HTTPStatusCode': 200 + } + } + with patch('boto3.client') as mock_client: + instance = mock_client.return_value + instance.list_buckets.return_value = mock_response_success_no_buckets + + result_list = get_s3_bucket_list() + assert result_list == [] + + # response_error + mock_response_error = { + 'ResponseMetadata': { + 'HTTPStatusCode': 500 + } + } + with patch('boto3.client') as mock_client: + with pytest.raises(Exception): + instance = mock_client.return_value + instance.list_buckets.return_value = mock_response_error + + result_list = get_s3_bucket_list() + # no profile with pytest.raises(Exception): result_list = get_s3_bucket_list() @@ -314,23 +344,25 @@ def test_copy_bucket_to_s3(app, db, users, client, records): p.user_id = '1' p.access_key = '1' p.secret_key = '1' - p.s3_endpoint_url = '1' + p.s3_endpoint_url = 'https://s3.ap-southeast-2.amazonaws.com/' db.session.add(p) user_profile = p + # local to s3 with patch("weko_user_profiles.models.UserProfile.get_by_userid", return_value=user_profile): with patch('boto3.client') as mock_client: instance = mock_client.return_value - instance.create_bucket.return_value = mock_response instance.get_bucket_location.return_value = mock_response - instance.upload_file.return_value = mock_response pid = PersistentIdentifier.query.filter_by( pid_type="recid", pid_value='1' ).first() records_buckets = RecordsBuckets.query.filter_by( record_id=pid.object_uuid).first() + org_bucket = Bucket.query.get(records_buckets.bucket_id) + org_obj = ObjectVersion.get(bucket=org_bucket, key='helloworld.pdf') + org_fileinstance = FileInstance.get(org_obj.file_id) # success create uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', @@ -344,12 +376,27 @@ def test_copy_bucket_to_s3(app, db, users, client, records): checked='select', bucket_name='sample1') assert uri - # file not exists - with patch("os.path.exists", return_value=False): - with pytest.raises(Exception): - uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', - org_bucket_id=records_buckets.bucket_id, - checked='create', bucket_name='sample1') + with pytest.raises(Exception): + # bucket create error + instance.create_bucket.side_effect = Exception("error") + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='create', bucket_name='sample1') + + with pytest.raises(Exception): + # upload file error + instance.upload_file.side_effect = Exception("error") + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='select', bucket_name='sample1') + + with pytest.raises(Exception): + # get bucket location error + instance.get_bucket_location.side_effect = Exception("error") + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='select', bucket_name='sample1') + # no profile with pytest.raises(Exception): @@ -357,6 +404,51 @@ def test_copy_bucket_to_s3(app, db, users, client, records): org_bucket_id=records_buckets.bucket_id, checked='create', bucket_name='sample1') + # s3 to s3 + with patch("weko_user_profiles.models.UserProfile.get_by_userid", return_value=user_profile): + with patch('boto3.client') as mock_client: + pid = PersistentIdentifier.query.filter_by( + pid_type="recid", pid_value='1' + ).first() + records_buckets = RecordsBuckets.query.filter_by( + record_id=pid.object_uuid).first() + org_bucket = Bucket.query.get(records_buckets.bucket_id) + org_obj = ObjectVersion.get(bucket=org_bucket, key='helloworld.pdf') + org_fileinstance = FileInstance.get(org_obj.file_id) + loc_s3 = Location( + name="testloc-s3", + uri="s3://bucket-name/", + default=False, + type="s3", + s3_endpoint_url="https://s3.us-west-2.amazonaws.com/", + ) + db.session.add(loc_s3) + parts = loc_s3.uri.split('/') + org_fileinstance.uri = 's3://bucket-name/' + "/".join(parts[2:]) + instance = mock_client.return_value + instance.get_bucket_location.return_value = mock_response + + # success create + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='create', bucket_name='sample1') + assert uri + + with pytest.raises(Exception): + # copy error + instance.copy.side_effect = Exception("error") + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='select', bucket_name='sample1') + + with pytest.raises(Exception): + # head_object error + instance.head_object.side_effect = Exception("error") + uri = copy_bucket_to_s3(pid=1, filename='helloworld.pdf', + org_bucket_id=records_buckets.bucket_id, + checked='select', bucket_name='sample1') + + # profile exists but no access_key with db.session.begin_nested(): @@ -364,7 +456,7 @@ def test_copy_bucket_to_s3(app, db, users, client, records): p2.user_id = '2' p2.access_key = '' p2.secret_key = '2' - p2.s3_endpoint_url = '2' + p2.s3_endpoint_url = 'https://s3.us-west-2.amazonaws.com/' db.session.add(p2) user_profile2 = p2 @@ -380,7 +472,7 @@ def test_copy_bucket_to_s3(app, db, users, client, records): p3.user_id = '3' p3.access_key = '3' p3.secret_key = '3' - p3.s3_endpoint_url = '3' + p3.s3_endpoint_url = 'https://s3.us-west-2.amazonaws.com/' p3.s3_region_name = 'us-west-2' db.session.add(p3) @@ -498,35 +590,53 @@ def test_replace_file_bucket(app, db, users, client, records): mock_response = 'http:return_url' - with db.session.begin_nested(): - - # location weko local - # success - - with patch("weko_search_ui.utils.check_replace_file_import_items", return_value={}): - with patch("weko_search_ui.utils.import_items_to_system", return_value={'success': True}): - - result = replace_file_bucket(org_pid='1', org_bucket_id=records_buckets.bucket_id, - file=file, - file_name='helloworld.pdf', file_size=2000, - file_checksum=None, - new_bucket_id=None, new_version_id=None) - assert result - - # # location type:s3 - l2=Location.get_default() - l2.uri='s3://test/' - l2.access_key='access_key' - l2.secret_key='secret_key' - l2.type='s3' - l2.s3_endpoint_url='https://test.s3.ap-southeast-2.amazonaws.com/' - - with patch("invenio_pidstore.models.PersistentIdentifier.get", return_value=pid): - - result = replace_file_bucket(org_pid='1', org_bucket_id=records_buckets.bucket_id, - file=None, - file_name='helloworld.pdf', file_size=100, - file_checksum='86266081366d3c950c1cb31fbd9e1c38e4834fa52b568753ce28c87bc31252cd', - new_bucket_id=records_buckets.bucket_id, new_version_id=file_obj.version_id) - assert result + url = url_for("weko_records_ui.replace_file") + login_user_via_session(client,email=users[0]["email"]) + + with patch("weko_search_ui.utils.check_replace_file_import_items", return_value={}): + with patch("weko_search_ui.utils.import_items_to_system", return_value={'success': True}): + + # location weko local + res = client.post( + url, + data={ + 'pid': '1', + 'bucket_id': records_buckets.bucket_id, + 'file_name': 'helloworld.pdf', + 'file_size': 2000, + 'file_checksum': None, + 'new_bucket_id': None, + 'new_version_id': None, + 'file': file, + 'return_file_place': 'local', + }, + ) + assert res.status_code == 200 + + with patch("invenio_pidstore.models.PersistentIdentifier.get", return_value=pid): + + with db.session.begin_nested(): + # location type:s3 + l2=Location.get_default() + l2.uri='s3://test/' + l2.access_key='access_key' + l2.secret_key='secret_key' + l2.type='s3' + l2.s3_endpoint_url='https://s3.ap-southeast-2.amazonaws.com/' + + res = client.post( + url, + data={ + 'return_file_place': 'S3', + 'pid': '1', + 'bucket_id': records_buckets.bucket_id, + 'file_name': 'helloworld.pdf', + 'file_size': 100, + 'file_checksum': '86266081366d3c950c1cb31fbd9e1c38e4834fa52b568753ce28c87bc31252cd', + 'new_bucket_id': records_buckets.bucket_id, + 'new_version_id': file_obj.version_id, + 'file': None, + }, + ) + assert res.status_code == 200 diff --git a/modules/weko-records-ui/tests/test_permissions.py b/modules/weko-records-ui/tests/test_permissions.py index c3154b797a..ff7bc31dcb 100644 --- a/modules/weko-records-ui/tests/test_permissions.py +++ b/modules/weko-records-ui/tests/test_permissions.py @@ -31,7 +31,8 @@ file_permission_factory, page_permission_factory, is_open_restricted, - is_owners_or_superusers + is_owners_or_superusers, + has_comadmin_permission ) @@ -671,6 +672,88 @@ def test_check_created_id(app, users, index, status): assert check_created_id(record) == status +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_permissions.py::test_check_created_id_comadmin -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp +def test_check_created_id_comadmin(app, users, db): + record = { + "_oai": {"id": "oai:weko3.example.org:00000001", "sets": ["1657555088462"]}, + "path": ["1657555088462"], + "owner": "1", + "recid": "1", + "title": ["a"], + "pubdate": {"attribute_name": "PubDate", "attribute_value": "2022-07-12"}, + "_buckets": {"deposit": "35004d51-8938-4e77-87d7-0c9e176b8e7b"}, + "_deposit": { + "id": "1", + "pid": {"type": "depid", "value": "1", "revision_id": 0}, + "owner": "1", + "owners": [1], + "status": "published", + "created_by": 1, + "owners_ext": { + "email": "wekosoftware@nii.ac.jp", + "username": "", + "displayname": "", + }, + }, + "item_title": "a", + "author_link": [], + "item_type_id": "15", + "publish_date": "2022-07-12", + "publish_status": "0", + "weko_shared_id": 2, + "item_1617186331708": { + "attribute_name": "Title", + "attribute_value_mlt": [ + {"subitem_1551255647225": "a", "subitem_1551255648112": "ja"} + ], + }, + "item_1617258105262": { + "attribute_name": "Resource Type", + "attribute_value_mlt": [ + { + "resourceuri": "http://purl.org/coar/resource_type/c_5794", + "resourcetype": "conference paper", + } + ], + }, + "relation_version_is_last": True, + } + with patch("flask_login.utils._get_user", return_value=users[3]["obj"]), \ + patch("weko_records_ui.permissions.has_comadmin_permission", return_value=True): + assert check_created_id(record) == True + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_permissions.py::test_has_comadmin_permission -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp +def test_has_comadmin_permission(app, users, mocker): + mocker.patch("flask_login.utils._get_user", return_value=users[3]["obj"]) + mock_get_repos = mocker.patch("invenio_communities.models.Community.get_repositories_by_user") + mock_get_children = mocker.patch("weko_index_tree.api.Indexes.get_child_list_recursive") + + # with permmission + mock_get_repos.return_value = [MagicMock(root_node_id=1)] + mock_get_children.return_value = ["12345", "67890"] + record = {"path": ["12345"]} + result = has_comadmin_permission(record) + assert result is True + + #without permission + record = {"path": ["11111"]} + result = has_comadmin_permission(record) + assert result is False + + # with no community + mock_get_repos.return_value = [] + record = {"path": ["12345"]} + result = has_comadmin_permission(record) + assert result is False + + # with no index + mock_get_repos.return_value = [MagicMock(root_node_id=1)] + record = {"path": []} + result = has_comadmin_permission(record) + assert result is False + + # def check_usage_report_in_permission(permission): def test_check_usage_report_in_permission(app): data1 = MagicMock() diff --git a/modules/weko-records-ui/weko_records_ui/api.py b/modules/weko-records-ui/weko_records_ui/api.py index 004e57e9c5..13cbdf4b12 100644 --- a/modules/weko-records-ui/weko_records_ui/api.py +++ b/modules/weko-records-ui/weko_records_ui/api.py @@ -281,7 +281,7 @@ def copy_bucket_to_s3( # create bucket if checked == 'create': try: - if region_name is None: + if region_name is None or region_name == 'us-east-1': s3_client = boto3.client( 's3', aws_access_key_id=access_key, @@ -381,12 +381,12 @@ def copy_bucket_to_s3( # ex: https://bucket_name.s3.us-east-1.amazonaws.com/ parts = endpoint_url.split('/') sub_parts = parts[2].split('.') - if len(sub_parts) > 3: - end_uri = ".".join(sub_parts[3:]) + if len(sub_parts) > 2: + end_uri = ".".join(sub_parts[2:]) else: - end_uri = sub_parts[3] + end_uri = sub_parts[2] - uri = parts[0] + '//' + bucket_name + '.' + sub_parts[1] + '.' + bucket_region + '.' + end_uri +'/' + uri = parts[0] + '//' + bucket_name + '.' + sub_parts[0] + '.' + bucket_region + '.' + end_uri +'/' current_app.logger.info(f'location: {location}') @@ -420,7 +420,7 @@ def copy_bucket_to_s3( # S3のバケット名を取得 org_bucket_name = location.uri.split('/')[2] base = location.uri.split('/')[0] + '//' + org_bucket_name - file_path = org_bucket_name + org_fileinstance.uri.replace(base, '') + file_path = org_fileinstance.uri.replace(base + '/', '') elif location.type == "s3_vh": if location.uri.startswith('https://s3.'): # ex: https://s3.us-east-1.amazonaws.com/bucket_name/file_name @@ -428,8 +428,7 @@ def copy_bucket_to_s3( org_bucket_name = parts[3] base = parts[0] + '//' + parts[2] + '/' + org_bucket_name file_path = ( - org_bucket_name - + org_fileinstance.uri.replace(base, '') + org_fileinstance.uri.replace(base + '/', '') ) else: # ex: https://bucket_name.s3.us-east-1.amazonaws.com/file_name @@ -438,8 +437,7 @@ def copy_bucket_to_s3( org_bucket_name = sub_parts[0] base = parts[0] + '//' + parts[2] file_path = ( - org_bucket_name - + org_fileinstance.uri.replace(base, '') + org_fileinstance.uri.replace(base + '/', '') ) current_app.logger.info(f'org_bucket_name: {org_bucket_name}') @@ -459,7 +457,7 @@ def copy_bucket_to_s3( try: s3_client.head_object(Bucket=source_bucket, Key=source_key) - except s3_client.exceptions.NoSuchKey: + except Exception as e: traceback.print_exc() raise Exception(_('The source file cannot be found.')) try: @@ -588,6 +586,8 @@ def get_file_place_info(org_pid, org_bucket_id, file_name): if len(sub_parts) >= 3: pre = '/'.join(parts[3:]) + '/' * (not '/'.join(parts[3:]).endswith('/')) directory_path = pre + directory_path + if directory_path.startswith('/'): + directory_path = directory_path[1:] if region is None: s3 = boto3.client('s3', @@ -600,11 +600,12 @@ def get_file_place_info(org_pid, org_bucket_id, file_name): region_name=region,) try: + current_app.logger.info(f'directory_path: {directory_path}') # 署名付きURLの生成 url = s3.generate_presigned_url( ClientMethod = 'put_object', Params = {'Bucket' : bucket_name, - 'Key' : bucket_name + "/" + directory_path + "/" + 'data', + 'Key' : directory_path + "/" + 'data', 'ContentType' : 'binary/octet-stream'}, ExpiresIn = 300, HttpMethod = 'PUT' diff --git a/modules/weko-records/weko_records/api.py b/modules/weko-records/weko_records/api.py index 0d835d23c1..b04aa9200b 100644 --- a/modules/weko-records/weko_records/api.py +++ b/modules/weko-records/weko_records/api.py @@ -2782,8 +2782,14 @@ def __init__(self, recid: str): def get_item_link_info(cls, recid): """Get item link info of recid. - :param recid: Record Identifier. - :return ret: List destination records. + Args: + recid (str): Record Identifier. + + Returns: + list(dict): Destination records info. + - item_links: Destination item PID + - item_title: Title of the destination item + - value: Reference type """ from weko_deposit.api import WekoRecord diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 8b7b01436b..0d6381ed6c 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -1318,6 +1318,8 @@ class JsonLdMapper(JsonMapper): """JsonLdMapper.""" AT_TYPE_MAP = { + "date": "Date", + "URL": "URL", "File": "File", "Contributor": "Person", "Creator": "Person", @@ -1491,14 +1493,14 @@ def _set_metadata(parent, meta_props, prop_props): interim = list(schema.keys())[0] if parent.get(prop_props[0]) is None: parent[prop_props[0]] = [ - {interim: meta_value} + {interim: META_VALUE} ] else: parent[prop_props[0]].append( - {interim: meta_value} + {interim: META_VALUE} ) else: - parent.update({prop_props[0]: meta_value}) + parent.update({prop_props[0]: META_VALUE}) return full_props = PROP_PATH.split(".") @@ -1564,53 +1566,53 @@ def _set_metadata(parent, meta_props, prop_props): parent.update({prop_props[0]: sub_prop_array}) return - for META_KEY in metadata: + for META_KEY, META_VALUE in metadata.items(): if not isinstance(META_KEY, str): continue META_PATH = re.sub(r"\[\d+\]", "", META_KEY) # metadata for system if "wk:index" in META_PATH: path = mapped_metadata.get("path", []) - path.append(int(metadata.get(META_KEY))) + path.append(int(META_VALUE)) mapped_metadata["path"] = path elif "wk:publishStatus" in META_PATH: - mapped_metadata["publish_status"] = metadata.get(META_KEY) + mapped_metadata["publish_status"] = META_VALUE elif "wk:editMode" in META_PATH: - mapped_metadata["edit_mode"] = metadata.get(META_KEY) + mapped_metadata["edit_mode"] = META_VALUE elif "wk:feedbackMail" in META_PATH: feedback_mail_list = mapped_metadata.get( "feedback_mail_list", []) feedback_mail_list.append( - {"email": metadata.get(META_KEY), "author_id": ""} + {"email": META_VALUE, "author_id": ""} ) mapped_metadata["feedback_mail_list"] = feedback_mail_list elif "wk:requestMail" in META_PATH: request_mail_list = mapped_metadata.get( "request_mail_list", []) request_mail_list.append( - {"email": metadata.get(META_KEY), "author_id": ""} + {"email": META_VALUE, "author_id": ""} ) mapped_metadata["request_mail_list"] = request_mail_list elif META_PATH not in properties_mapping: - missing_metadata[META_KEY] = metadata[META_KEY] + if not META_KEY.endswith("@id"): + missing_metadata[META_KEY] = META_VALUE else: # item metadata meta_props = META_KEY.split(".") PROP_PATH = properties_mapping[META_PATH] prop_props = PROP_PATH.split(".") - meta_value = metadata.get(META_KEY) - if meta_value is None or meta_value == "": + if META_VALUE is None or META_VALUE == "": # If the value is None or empty, skip setting metadata continue - if not isinstance(meta_value, (str, int, float, bool)): - meta_value = str(meta_value) + if not isinstance(META_VALUE, (str, int, float, bool)): + META_VALUE = str(META_VALUE) # META_KEY="dc:type.@id", meta_props=["dc:type","@id"], # PROP_PATH=item_30001_resource_type11.resourceuri, prop_props=["item_30001_resource_type11","resourceuri"] try: _set_metadata(mapped_metadata, meta_props, prop_props) except Exception as ex: current_app.logger.warning( - f"Failed to set metadata for {META_KEY}: {meta_value}" + f"Failed to set metadata for {META_KEY}: {META_VALUE}" ) traceback.print_exc() @@ -1628,7 +1630,6 @@ def _set_metadata(parent, meta_props, prop_props): else: mapped_metadata[item_map.get("Extra")] = str(missing_metadata) - files_info = [] for v in item_map.values(): if not v.endswith(".filename"): @@ -1793,13 +1794,13 @@ def _resolve_link(parent, key, value): if not isinstance(grant, dict): continue if grant.get("jpcoar:identifier") == "HDL": - system_info["cnri"] = grant.get("@id") + system_info["cnri"] = grant.get("@id").lstrip("#") 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"] = grant.get("@id").lstrip("#") system_info["doi_ra"] = grant.get("jpcoar:identifierRegistration") break @@ -1866,11 +1867,18 @@ def _deconstructer(metadata, parent, key, value): return return_data - def to_rocrate_metadata(self, metadata, **kwargs): + def to_rocrate_metadata( + self, record_metadata=None, tsv_row_metadata=None, **kwargs + ): """Map to RO-Crate format. + It requires eather record_metadata or tsv_row_metadata to be provided. + Args: - metadata (dict): metadata with item type format. + record_metadata (dict): + metadata from record_metadata or elasticsearch. + tsv_row_metadata (dict): + metadata with tsv row format. it has header as metadata value. Returns: dict: metadata with RO-Crate format. """ @@ -1966,7 +1974,7 @@ def ensure_entity_list_size(parent, key, at_type, size): append_entity(parent, key, at_id, at_type) return - def extract_list_indices(meta_props, prop_props, property_map): + def extract_list_indices(meta_props, prop_props): """Exteract list indices.""" list_index = [] for prop in meta_props: @@ -1980,7 +1988,7 @@ def extract_list_indices(meta_props, prop_props, property_map): for i, meta_key in enumerate(meta_props): definition_key += re.sub(r'\[\d+\]$', '', meta_key) - if definition_key not in property_map: + if definition_key not in properties_mapping: list_non_corresponding.append(i) definition_key += "." list_index = [ @@ -1991,7 +1999,7 @@ def extract_list_indices(meta_props, prop_props, property_map): elif len(list_index) < len(prop_props): list_non_corresponding = [] definition_key = "" - reversed_map = {v: k for k, v in property_map.items()} + reversed_map = {v: k for k, v in properties_mapping.items()} for i, prop_key in enumerate(prop_props): definition_key += prop_key @@ -2002,25 +2010,18 @@ def extract_list_indices(meta_props, prop_props, property_map): list_index.insert(i, None) return list_index - def gen_type(meta_path): + def gen_type(): """Generate "@type" of entity by using AT_TYPE_MAP.""" - for k, v in item_map.items(): - if v == meta_path: - meta_key = k - break - for k, v in self.AT_TYPE_MAP.items(): - if k in meta_key: + if k.lower() in META_KEY.lower(): return v return "PropertyValue" def _set_rocrate_metadata( - parent, record_key, meta_props, prop_props + parent, meta_props, prop_props ): # Get list_index - list_index = extract_list_indices( - meta_props, prop_props, properties_mapping - ) + list_index = extract_list_indices(meta_props, prop_props) # case: prop_props = [] if len(prop_props) == 0: raise Exception("Unexpected error: prop_props is empty.") @@ -2029,44 +2030,44 @@ def _set_rocrate_metadata( if len(prop_props) == 1: index = list_index[0] if list_index else None prop = prop_props[0] - at_type = gen_type(META_PATH) + at_type = gen_type() # dict if index is None: # If prop is "@id", do nothing. if prop != "@id": - parent[prop] = deconstructed[record_key] + parent[prop] = META_VALUE # If prop is under root, add property directly. return # list ensure_entity_list_size(parent, prop, at_type, index + 1) - parent[prop][index] = deconstructed[record_key] + parent[prop][index] = META_VALUE return _set_child_rocrate_metadata( - parent, record_key, meta_props, prop_props, list_index + parent, meta_props, prop_props, list_index ) return def _set_child_rocrate_metadata( - parent, record_key, meta_props, prop_props, list_index + parent, meta_props, prop_props, list_index ): if len(prop_props) == 0: raise Exception(f"Unexpected error: prop_props is empty.") prop = prop_props[0] index = list_index[0] if list_index else None - at_type = gen_type(META_PATH) + at_type = gen_type() # dict if index is None: if len(prop_props) == 1: if prop != "@id": - parent[prop] = deconstructed[record_key] + parent[prop] = META_VALUE rocrate.add(parent) return if "@id" in prop_props: - at_id = deconstructed[record_key] + at_id = str(META_VALUE) add_entity(parent, prop, at_id, at_type) return if prop not in parent: @@ -2075,8 +2076,7 @@ def _set_child_rocrate_metadata( parent, prop, at_id, at_type ) _set_child_rocrate_metadata( - parent[prop], record_key, - meta_props[1:], prop_props[1:], list_index[1:], + parent[prop], meta_props[1:], prop_props[1:], list_index[1:], ) return # list @@ -2089,21 +2089,19 @@ def _set_child_rocrate_metadata( list_val.extend( ["" for _ in range(index - len(list_val) + 1)] ) - list_val[index] = deconstructed[record_key] + list_val[index] = META_VALUE parent[prop] = list_val rocrate.add(parent) return ensure_entity_list_size(parent, prop, at_type, index + 1) - if isinstance(parent[prop], list): - _set_child_rocrate_metadata( - parent[prop][index], record_key, - meta_props[1:], prop_props[1:], list_index[1:] - ) - else: + if not isinstance(parent[prop], list): raise Exception( f"Unexpected structure for prop {prop} at index {index}" ) + _set_child_rocrate_metadata( + parent[prop][index], meta_props[1:], prop_props[1:], list_index[1:] + ) item_map = self._create_item_map(detail=True) # e.g. { "Title.タイトル": "item_30001_title0.subitem_title" } @@ -2115,116 +2113,168 @@ def _set_child_rocrate_metadata( if prop_name in item_map } + column_metadata = { + k.lstrip(".").replace("metadata.", ""): v + for k, v in zip(tsv_row_metadata["header"], tsv_row_metadata["value"]) + if isinstance(k, str) and isinstance(v, (str, int)) + } if tsv_row_metadata else {} + rocrate = ROCrate() rocrate.metadata.extra_terms.update(wk=self.WK_CONTEXT) # metadata for system - rocrate.name = metadata["item_title"] - rocrate.description = ( - "Item metadata for Item ID: {}. Title: {}." - .format(metadata["control_number"], metadata["item_title"]) - ) - rocrate.datePublished = metadata["publish_date"] - rocrate.root_dataset["identifier"] = metadata["control_number"] - rocrate.root_dataset["uri"] = url_for( - "invenio_records_ui.recid", - pid_value=metadata["control_number"], _external=True - ) - rocrate.root_dataset["wk:publishStatus"] = ( - "public" if metadata["publish_status"] == "0" else "private" - ) - rocrate.root_dataset["wk:index"] = metadata.get("path", []) - # wk:feedbackMail - pid = PersistentIdentifier.get("recid", metadata["control_number"]) - feedback_mail_list = FeedbackMailList.get_mail_list_by_item_id( - pid.object_uuid - ) - feedback_mail_list = [mail.get("email") for mail in feedback_mail_list] - rocrate.root_dataset["wk:feedbackMail"] = feedback_mail_list - # # wk:requestMail - request_mail_list = RequestMailList.get_mail_list_by_item_id( - pid.object_uuid - ) - request_mail_list = [mail.get("email") for mail in request_mail_list] - rocrate.root_dataset["wk:requestMail"] = request_mail_list - rocrate.root_dataset["wk:editMode"] = "Keep" - - # wk:itemLinks - list_item_link_info = ItemLink.get_item_link_info( - metadata["control_number"] - ) - list_link_data = [] - list_at_id = [] - for item_link_info in list_item_link_info: - dict_item_link = { - "identifier": url_for( - "invenio_records_ui.recid", - pid_value=item_link_info["item_links"], _external=True - ), - "value": item_link_info["value"] - } - list_link_data.append(dict_item_link) - list_at_id.append(gen_id("itemLinks")) - add_list_entity( - rocrate.root_dataset, "wk:itemLinks", list_at_id, "PropertyValue", - list_link_data - ) - - # wk:metadaAutoFill - rocrate.root_dataset["wk:metadataAutoFill"] = False - - deconstructed = self._deconstruct_dict(metadata) + if record_metadata: + recid = record_metadata["control_number"] + rocrate.name = record_metadata["item_title"] + rocrate.description = ( + "Item metadata for Item ID: {}. Title: {}." + .format(recid, record_metadata["item_title"]) + ) + rocrate.root_dataset["identifier"] = recid + rocrate.root_dataset["uri"] = url_for( + "invenio_records_ui.recid", + pid_value=recid, _external=True + ) + rocrate.root_dataset["wk:index"] = record_metadata.get("path", []) + rocrate.root_dataset["wk:publishStatus"] = ( + "public" if record_metadata["publish_status"] == "0" else "private" + ) + # wk:feedbackMail + pid = PersistentIdentifier.get("recid", recid) + feedback_mail_list = FeedbackMailList.get_mail_list_by_item_id( + pid.object_uuid + ) + feedback_mail_list = [mail.get("email") for mail in feedback_mail_list] + rocrate.root_dataset["wk:feedbackMail"] = feedback_mail_list + # # wk:requestMail + request_mail_list = RequestMailList.get_mail_list_by_item_id( + pid.object_uuid + ) + request_mail_list = [mail.get("email") for mail in request_mail_list] + rocrate.root_dataset["wk:requestMail"] = request_mail_list + rocrate.root_dataset["wk:editMode"] = "Keep" + rocrate.datePublished = record_metadata["publish_date"] + else: + recid = tsv_row_metadata["recid"] + rocrate.name = tsv_row_metadata["item_title"] + rocrate.description = ( + "Item metadata for Item ID: {}. Title: {}." + .format(recid, tsv_row_metadata["item_title"]) + ) + rocrate.root_dataset["identifier"] = recid + rocrate.root_dataset["uri"] = url_for( + "invenio_records_ui.recid", + pid_value=recid, _external=True + ) + rocrate.root_dataset["wk:index"] = [ + str(v) for k, v in column_metadata.items() + if k.startswith("path") + ] + rocrate.root_dataset["wk:publishStatus"] = column_metadata["publish_status"] + rocrate.root_dataset["wk:feedbackMail"] = [ + v for k, v in column_metadata.items() + if k.startswith("feedback_mail") and v + ] + rocrate.root_dataset["wk:requestMail"] = [ + v for k, v in column_metadata.items() + if k.startswith("request_mail") and v + ] + grant_id = [] + grant_data = [] + if column_metadata["cnri"]: + grant_id.append(column_metadata["cnri"]) + grant_data.append({"jpcoar:identifier": "HDL"}) + if column_metadata["doi"]: + grant_id.append(column_metadata["doi"]) + grant_data.append({ + "jpcoar:identifier": "DOI", + "jpcoar:identifierRegistration": column_metadata["doi_ra"] + }) + add_list_entity( + rocrate.root_dataset, "wk:grant", grant_id, + "PropertyValue", grant_data + ) + rocrate.root_dataset["wk:editMode"] = "Keep" + rocrate.datePublished = column_metadata["pubdate"] + + metadata_to_map = { + record_key.replace(".attribute_value_mlt", "") + .replace(".attribute_value", ""): record_value + for record_key, record_value in + self._deconstruct_dict(record_metadata).items() + if isinstance(record_key, str) + if isinstance(record_value, (str, int)) + if "attribute_value" in record_key + } if record_metadata else column_metadata # item metadata - for record_key in deconstructed: - if ( - not isinstance(record_key, str) - or "attribute_value" not in record_key - ): - continue - - META_KEY = ( - record_key - .replace(".attribute_value_mlt", "") - .replace(".attribute_value", "") - ) + for META_KEY, META_VALUE in metadata_to_map.items(): META_PATH = re.sub(r"\[\d+\]", "", META_KEY) meta_props = META_KEY.split(".") if META_PATH not in properties_mapping: continue - PROP_PATH = properties_mapping[META_PATH] # attribute_value + PROP_PATH = properties_mapping[META_PATH] + if PROP_PATH.startswith("$"): + continue prop_props = PROP_PATH.split(".") + if META_VALUE == "": + # If the value is empty, skip setting metadata + continue try: _set_rocrate_metadata( - rocrate.root_dataset, record_key, - meta_props, prop_props + rocrate.root_dataset, meta_props, prop_props ) except Exception as ex: current_app.logger.warning( "Failed to set metadata for {}: {}" - .format(META_KEY, deconstructed[record_key]) + .format(META_KEY, META_VALUE) ) traceback.print_exc() + def dereference(keys, initial_entity=None): + """Dereference a chained key in the rocrate. + + Args: + keys (list[str]): List of keys to dereference. + initial_entity (ContextEntity | None): + Initial entity to start dereferencing from. + Returns: + object: + The dereferenced value or None if not found. + """ + if ( + initial_entity is not None + and not isinstance(initial_entity, ContextEntity) + ): + raise TypeError( + "Unexpected type for dereference base entity: {}" + .format(type(initial_entity)) + ) + value = reduce( + lambda acc, key: ( + rocrate.dereference(acc[key]["@id"]) + if isinstance(acc, ContextEntity) and acc.get(key) + and isinstance(acc[key], ContextEntity) + else acc.get(key) + if isinstance(acc, ContextEntity) and acc.get(key) + else None + ), + keys, initial_entity or rocrate.root_dataset + ) + return value + # files entity reconstruction # "@id" in files entity is format like "data/sample.txt" filename_mapping = "" - file_url_mapping = "" file_url_url_mapping = "" - for k, v in properties_mapping.items(): + for k, m in properties_mapping.items(): if k.endswith(".filename"): - filename_mapping = v + filename_mapping = m break - for k, v in properties_mapping.items(): + for k, m in properties_mapping.items(): if k.endswith(".url.url"): - continue - if v.endswith(".url"): - file_url_mapping = v - break - for k, v in properties_mapping.items(): - if k.endswith(".url.url"): - file_url_url_mapping = v + file_url_url_mapping = m break file_key = filename_mapping.split(".")[0] @@ -2232,55 +2282,24 @@ def _set_child_rocrate_metadata( if file_key == "hasPart" and files_entity: del rocrate.root_dataset["hasPart"] + extracted_files = kwargs.get("extracted_files", []) for entity in files_entity: file_metadata = entity._jsonld - entity.delete() del file_metadata["@id"] del file_metadata["@type"] - filename = reduce( - lambda acc, key: acc.get(key) if isinstance(acc, dict) else None, - filename_mapping.split('.')[1:], file_metadata - ) + filename = dereference(filename_mapping.split(".")[1:], entity) + url = dereference(file_url_url_mapping.split(".")[1:], entity) + entity.delete() - if filename: + host_url = current_app.config["THEME_SITEURL"] + if isinstance(url, str) and host_url not in url: + rocrate.add_file(url, properties=file_metadata) + else: + file_metadata["wk:textExtraction"] = filename in extracted_files rocrate.add_file( dest_path=f"data/{filename}", properties=file_metadata ) - else: - url = file_metadata - for url_key in file_url_mapping.split(".")[1:]: - if url: - url = url.get(url_key) - if url: - url_id = url["@id"] - url = rocrate.dereference(url_id)._jsonld - file_url_url_mapping = ( - file_url_url_mapping - .split(".")[len(file_url_mapping.split(".")):] - ) - for url_key in file_url_url_mapping: - if url: - url = url.get(url_key) - if isinstance(url, str): - rocrate.add_file(url, properties=file_metadata) - - # wk:textExtraction - list_k_file = None - for k, v in properties_mapping.items(): - if k.endswith(".filename"): - list_k_file = v.split(".")[1:] - break - - if list_k_file is not None: - extracted_files = kwargs.get("extracted_files", []) - for file in rocrate.root_dataset.get("hasPart", []): - for k_file in list_k_file[:-1]: - if file: - file = file.get(k_file) - if file and file.get(list_k_file[-1]) not in extracted_files: - file["wk:textExtraction"] = False - rocrate.add(file) # Extra if "Extra" in item_map: @@ -2288,13 +2307,12 @@ def _set_child_rocrate_metadata( # case: "Extra" is list # If not list, pass this process. extra_key_head = item_map["Extra"] - extra_key = extra_key + ".attribute_value" - if not deconstructed.get(extra_key): + if not metadata_to_map.get(extra_key): extra_schema = self.itemtype.schema["properties"].get( extra_key_head).get("items").get("properties") interim = list(extra_schema.keys())[0] - extra_key = extra_key_head + ".attribute_value_mlt[0]." + interim - str_extra_dict = deconstructed.get(extra_key) + extra_key = extra_key_head + "[0]." + interim + str_extra_dict = metadata_to_map.get(extra_key) extra_entity = { "description": "Metadata which is not able to be mapped", "value": str_extra_dict @@ -2304,4 +2322,25 @@ def _set_child_rocrate_metadata( "PropertyValue", extra_entity ) + # wk:itemLinks + list_item_link_info = ItemLink.get_item_link_info(recid) + list_link_data = [] + list_at_id = [] + for item_link_info in list_item_link_info: + dict_item_link = { + "identifier": url_for( + "invenio_records_ui.recid", + pid_value=item_link_info["item_links"], _external=True + ), + "value": item_link_info["value"] + } + list_link_data.append(dict_item_link) + list_at_id.append(gen_id("itemLinks")) + add_list_entity( + rocrate.root_dataset, "wk:itemLinks", list_at_id, "PropertyValue", + list_link_data + ) + # wk:metadaAutoFill + rocrate.root_dataset["wk:metadataAutoFill"] = False + return rocrate diff --git a/modules/weko-search-ui/weko_search_ui/tasks.py b/modules/weko-search-ui/weko_search_ui/tasks.py index 6ba2b93891..e6374154b8 100644 --- a/modules/weko-search-ui/weko_search_ui/tasks.py +++ b/modules/weko-search-ui/weko_search_ui/tasks.py @@ -100,6 +100,11 @@ def check_rocrate_import_items_task(file_path, is_change_identifier: bool, lang (str): Language code(default is "en"). Returns: dict: Check Result. + - start_date (str): Start date of the check. + - end_date (str): End date of the check. + - data_path (str): Path to the data. + - list_record (list): List of records with their metadata. + - error (str): Error message if any error occurs. """ result = {"start_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")} with current_app.test_request_context( @@ -159,7 +164,7 @@ def remove_temp_dir_task(path): @shared_task def delete_task_id_cache_on_revoke(task_id, cache_key): """delete admin_cache_KEY_EXPORT_ALL_{user_id} from redis - + Delete the export task ID cache from Redis if the task has been revoked. This task checks if the given cache key in Redis matches the provided task ID. @@ -228,7 +233,7 @@ def write_files_task(self, export_path, pickle_file_name , user_id): name=_file_create_config, user_id=user_id ) - + # update task_id in redis _task_id_config = \ current_app.config["WEKO_SEARCH_UI_BULK_EXPORT_TASK"] @@ -237,7 +242,7 @@ def write_files_task(self, export_path, pickle_file_name , user_id): user_id=user_id ) reset_redis_cache(_task_id_key, str(self.request.id)) - + item_type_id = pickle_file_name.split(".")[1] part = pickle_file_name.split(".")[2] json_data = json.loads(get_redis_cache(_file_create_key)) @@ -263,7 +268,7 @@ def _update_redis_status(json_data, file_name, status,item_type_id): _update_redis_status(json_data, import_datas['name'], 'started',import_datas['item_type_id']) with open(pickle_path, 'rb') as f: import_datas = pickle.load(f) - + result = write_files(import_datas, export_path, user_id, 0) json_data = json.loads(get_redis_cache(_file_create_key)) if result: diff --git a/modules/weko-signposting/weko_signposting/api.py b/modules/weko-signposting/weko_signposting/api.py index 989f1f2366..39bc0d991d 100644 --- a/modules/weko-signposting/weko_signposting/api.py +++ b/modules/weko-signposting/weko_signposting/api.py @@ -45,14 +45,14 @@ def requested_signposting(pid, record, template=None, **kwargs): permalink = get_record_permalink(recid) if permalink is not None: - links.append('<{url}> ; rel="cite-as"'.format(url=permalink)) + links.append('<{url}>; rel="cite-as"'.format(url=permalink)) links.append( - '<{url}> ; rel="describedby" ; type="application/json"' + '<{url}>; rel="describedby"; type="application/json"' .format(url=f'{record_link}/export/json') ) links.append( - '<{url}> ; rel="describedby" ; type="application/x-bibtex"' + '<{url}>; rel="describedby"; type="application/x-bibtex"' .format(url=f'{record_link}/export/bibtex') ) @@ -64,12 +64,12 @@ def requested_signposting(pid, record, template=None, **kwargs): f'&identifier={record["_oai"]["id"]}' ) links.append( - f'<{url}> ; rel="describedby" ; ' + f'<{url}>; rel="describedby"; ' f'type="application/xml" ; formats="{_object["namespace"]}"' ) resp = Response() - resp.headers['Link'] = ','.join(links) + resp.headers['Link'] = ', '.join(links) return resp diff --git a/modules/weko-swordserver/tests/test_utils.py b/modules/weko-swordserver/tests/test_utils.py index 7959e9dd45..4dec54544c 100644 --- a/modules/weko-swordserver/tests/test_utils.py +++ b/modules/weko-swordserver/tests/test_utils.py @@ -17,7 +17,9 @@ check_import_items, get_shared_id_from_on_behalf_of, is_valid_file_hash, - update_item_ids + update_item_ids, + check_deletion_type, + delete_item_directly ) from .helpers import json_data from weko_swordserver.errors import ErrorType, WekoSwordserverException @@ -197,7 +199,6 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie 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}}) @@ -215,7 +216,6 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie 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 @@ -233,7 +233,6 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie 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 @@ -251,7 +250,6 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie 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() @@ -263,6 +261,16 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie check_result = check_import_items(file_content, "TSV/CSV", True, 3) assert check_result["error"] == "Workflow not found for item type ID." + # tsv, registration 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.reduce_workflows_for_registration", return_value=[]): + check_result = check_import_items(file_content, "TSV/CSV", True, 3) + assert check_result["error"] == "No workflow 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 @@ -288,7 +296,6 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie 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}}) @@ -316,6 +323,14 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie assert e.value.errorType == ErrorType.BadRequest assert e.value.message == "Workflow not found for registration your item." + # xml, registration workflow not found + file_content = BytesIO() + with patch("weko_swordserver.utils.WorkFlows.reduce_workflows_for_registration", return_value=[]): + 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 is not for item registration." + # jsonld, sword_client not found client_id = "invalid_client_id" file_content = BytesIO() @@ -334,6 +349,13 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie assert e.value.errorType == ErrorType.BadRequest assert e.value.message == "Workflow not found for registration your item." + # jsonld, registration workflow not found + with patch("weko_swordserver.utils.WorkFlows.reduce_workflows_for_registration", return_value=[]): + 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 is not for item registration." + # invalid file format file_content = BytesIO() with pytest.raises(WekoSwordserverException) as e: @@ -344,109 +366,180 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie # 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 関数の動作をテストする。 すべての条件分岐やエッジケースをカバーする。 """ # list_record が空のリストの場合 - assert update_item_ids([], "new_id") == [] - - # list_record に dict 以外の要素が含まれている場合 - # list_record_3 = [1, 2, 3] - # assert update_item_ids(list_record_3, "new_id") == list_record_3 + assert update_item_ids([], "new_id", "old_id") == [] # list_record の要素に metadata がない場合 list_record_4 = [{"key": "value"}] - assert update_item_ids(list_record_4, "new_id") == list_record_4 + assert update_item_ids(list_record_4, "new_id", "old_id") == list_record_4 # metadata に id がない場合 list_record_5 = [{"metadata": {}}] - assert update_item_ids(list_record_5, "new_id") == list_record_5 + assert update_item_ids(list_record_5, "new_id", "old_id") == list_record_5 # metadata に link_data がない場合 list_record_6 = [{"metadata": {"id": "123"}}] - assert update_item_ids(list_record_6, "new_id") == list_record_6 + assert update_item_ids(list_record_6, "new_id", "old_id") == list_record_6 metadata = {"test": "test"} + # link_data の要素が 辞書でない場合 + list_record_7 = [{"metadata": metadata, "id": "123", "link_data": ["not_a_dict"]}] + assert update_item_ids(list_record_7, "new_id", "old_id") == list_record_7 + # link_data の要素に item_id と sele_id がない場合 list_record_9 = [{"metadata": metadata, "id": "123", "link_data": [{"key": "value"}]}] - assert update_item_ids(list_record_9, "new_id") == list_record_9 + assert update_item_ids(list_record_9, "new_id", "old_id") == list_record_9 - # link_data の要素に item_id があるが、sele_id が "isSupplementedBy" でない場合 + # link_data の要素に item_id があるが、old_idがない場合 _id = "123" - link_data = [{"item_id": "456", "sele_id": "isSupplementTo"}] + link_data = [{"item_id": "456"}] list_record = [{"metadata": metadata, "_id": _id, "link_data": link_data}] new_id = "new_id" + old_id = "old_id" - result = update_item_ids(list_record, new_id) + result = update_item_ids(list_record, new_id, old_id) assert result[0]["link_data"][0]["item_id"] == "456" - assert result[0]["link_data"][0]["sele_id"] == "isSupplementTo" - # link_data の要素に item_id があり、sele_id が "isSupplementedBy" の場合 + # link_data の要素に item_id があり、old_idがある場合 _id = "123" - link_data = [{"item_id": "123", "sele_id": "isSupplementedBy"}] + link_data = [{"item_id": "123"}] list_record = [{"metadata": metadata, "_id": _id, "link_data": link_data}] new_id = "new_id" + old_id = "123" - result = update_item_ids(list_record, new_id) - + result = update_item_ids(list_record, new_id, old_id) assert result[0]["link_data"][0]["item_id"] == new_id - assert link_data == [{"item_id": "new_id", "sele_id": "isSupplementedBy"}] # 複数の ITEM が含まれる場合 _id1 = "123" - link_data1 = [{"item_id": "789", "sele_id": "isSupplementTo"}] + link_data1 = [{"item_id": "789"}] _id2 = "789" - link_data2 = [{"item_id": "123", "sele_id": "isSupplementedBy"}] + link_data2 = [{"item_id": "123"}] list_record = [ {"metadata": metadata, "_id": _id1, "link_data": link_data1}, {"metadata": metadata, "_id": _id2, "link_data": link_data2} ] new_id = "new_id" + old_id = "123" - result = update_item_ids(list_record, new_id) + result = update_item_ids(list_record, new_id, old_id) assert result[0]["link_data"][0]["item_id"] == "789" assert result[1]["link_data"][0]["item_id"] == new_id -# def get_record_by_client_id(client_id): -# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_get_record_by_client_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_record_by_client_id(app,mocker): -# # No 1 -# client_id = "valid_client_id" -# expected_client = SwordClientModel(client_id="client_id", mapping_id="mapping_id") -# expected_mapping = SwordItemTypeMappingModel(id="mapping_id") - -# with patch.object(SwordClient, 'get_client_by_id', return_value=expected_client): -# with patch.object(SwordItemTypeMapping, 'get_mapping_by_id', return_value=expected_mapping): -# client, mapping = get_record_by_client_id(client_id) -# assert client == expected_client -# assert mapping == expected_mapping - -# # No 2 -# invalid_client_id = "invalid_client_id" - -# with patch.object(SwordClient, 'get_client_by_id', return_value=None): -# with patch.object(SwordItemTypeMapping, 'get_mapping_by_id', return_value=expected_mapping): -# client, mapping = get_record_by_client_id(invalid_client_id) -# assert client == None -# assert mapping == expected_mapping - -# # No 3 -# valid_client_id = "valid_client_id" -# expected_client = SwordClientModel(client_id="client_id", mapping_id="invalid_mapping_id") -# with patch.object(SwordClient, 'get_client_by_id', return_value=expected_client): -# with patch.object(SwordItemTypeMapping, 'get_mapping_by_id', return_value=None): -# client, mapping = get_record_by_client_id(valid_client_id) -# assert client == expected_client -# assert mapping == None +# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_check_deletion_type -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace +@pytest.mark.parametrize("register_type, workflow_exists, workflow_deleted, delete_flow_id, expected", [ + # Workflow type, workflow exists, not deleted, has delete_flow_id + ("Workflow", True, False, 123, {"deletion_type": "Workflow", "workflow_id": 2, "delete_flow_id": 123}), + # Workflow type, workflow exists, not deleted, no delete_flow_id + ("Workflow", True, False, None, {"deletion_type": "Direct"}), + # Workflow type, workflow exists, deleted, has delete_flow_id + ("Workflow", True, True, 456, None), + # Workflow type, workflow does not exist + ("Workflow", False, None, None, None), + # Direct type + ("Direct", None, None, None, {"deletion_type": "Direct"}), +]) +def test_check_deletion_type(app, mocker, register_type, workflow_exists, workflow_deleted, delete_flow_id, expected): + mock_sword_client = MagicMock() + mock_sword_client.registration_type = register_type + mock_sword_client.workflow_id = 2 + + mocker.patch("weko_swordserver.utils.SwordClient.get_client_by_id", return_value=mock_sword_client) + + if register_type == "Workflow": + if workflow_exists: + workflow = MagicMock() + workflow.is_deleted = workflow_deleted + workflow.delete_flow_id = delete_flow_id + mocker.patch("weko_swordserver.utils.WorkFlows.get_workflow_by_id", return_value=workflow) + else: + mocker.patch("weko_swordserver.utils.WorkFlows.get_workflow_by_id", return_value=None) + client_id = "test_client_id" + if register_type == "Workflow" and (not workflow_exists or workflow_deleted): + with pytest.raises(WekoSwordserverException) as e: + check_deletion_type(client_id) + assert e.value.errorType == ErrorType.BadRequest + assert e.value.message == "Workflow not found for registration your item." + else: + result = check_deletion_type(client_id) + for k, v in expected.items(): + assert result[k] == v + + +# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_check_deletion_type_no_sword_client -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace +def test_check_deletion_type_no_sword_client(app, mocker): + mocker.patch("weko_swordserver.utils.SwordClient.get_client_by_id", return_value=None) + with pytest.raises(WekoSwordserverException) as e: + check_deletion_type("notfound") + assert e.value.errorType == ErrorType.BadRequest + assert e.value.message == "No SWORD API setting found for client ID that you are using." + + +# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_check_deletion_type_invalid_registration_type -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace +def test_check_deletion_type_invalid_registration_type(app, mocker): + mock_sword_client = MagicMock() + mock_sword_client.registration_type = "InvalidType" + mocker.patch("weko_swordserver.utils.SwordClient.get_client_by_id", return_value=mock_sword_client) + with pytest.raises(WekoSwordserverException) as e: + check_deletion_type("invalidtype") + assert e.value.errorType == ErrorType.ServerError + assert e.value.message == "Invalid registration type: InvalidType" + + +# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_utils.py::test_delete_item_directly -v -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp --full-trace +@pytest.mark.parametrize( + "recid, resolve_return, locked, being_edited, raises, error_type, message", + [ + ("recid123", (MagicMock(), MagicMock()), False, False, None, None, None), + ("recid_not_found", (MagicMock(), None), None, None, + WekoSwordserverException, ErrorType.NotFound, "Record not found."), + ("recid_locked", (MagicMock(), MagicMock()), True, None, + WekoSwordserverException, ErrorType.BadRequest, "Item cannot be deleted because it is in import progress."), + ("recid_editing", (MagicMock(), MagicMock()), False, True, + WekoSwordserverException, ErrorType.BadRequest, "Item cannot be deleted because it is being edited."), + ] +) +def test_delete_item_directly( + app, mocker, recid, resolve_return, locked, being_edited, raises, error_type, message +): + resolver_mock = mocker.patch("weko_swordserver.utils.Resolver") + resolver_instance = resolver_mock.return_value + resolver_instance.resolve.return_value = resolve_return + + latest_pid = MagicMock() + latest_pid.object_uuid = "uuid" + pid_versioning_mock = mocker.patch("weko_swordserver.utils.PIDVersioning") + pid_versioning_mock.return_value.last_child = latest_pid + + work_activity_mock = mocker.patch("weko_swordserver.utils.WorkActivity") + work_activity_instance = work_activity_mock.return_value + work_activity_instance.get_workflow_activity_by_item_id.return_value = "latest_activity" + + mocker.patch("weko_swordserver.utils.check_an_item_is_locked", + return_value=locked if locked is not None else False) + mocker.patch("weko_swordserver.utils.check_item_is_being_edit", + return_value=being_edited if being_edited is not None else False) + soft_delete_mock = mocker.patch("weko_swordserver.utils.soft_delete") + + if raises: + with pytest.raises(raises) as e: + delete_item_directly(recid) + assert e.value.errorType == error_type + assert e.value.message == message + else: + delete_item_directly(recid) + soft_delete_mock.assert_called_once_with(recid) diff --git a/modules/weko-swordserver/tests/test_views.py b/modules/weko-swordserver/tests/test_views.py index 8bf42f40b5..76d1cb007f 100644 --- a/modules/weko-swordserver/tests/test_views.py +++ b/modules/weko-swordserver/tests/test_views.py @@ -20,6 +20,7 @@ from weko_search_ui.utils import handle_check_date, handle_check_exist_record, import_items_to_system from weko_workflow.models import Activity +from weko_workflow.errors import WekoWorkflowException from weko_swordserver.errors import * from weko_swordserver.views import _get_status_workflow_document, blueprint, _get_status_document,_create_error_document,post_service_document @@ -93,21 +94,22 @@ def update_location_size(): 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.status_code == 201 assert result.json.get("recid") == "2000001" assert not os.path.exists("/var/tmp/test"), os.rmdir("/var/tmp/test") # Workflow registration, duplicate check login_user_via_session(client=client, email=users[1]["email"]) app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True + 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", - "On-Behalf-Of": "test_on_behalf_of" + "On-Behalf-Of": "test_on_behalf_of", + "Digest": "SHA-256={}".format(calculate_hash(storage)), } - 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") @@ -122,7 +124,7 @@ def update_location_size(): 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.status_code == 201 assert result.json.get("recid") == "2000001" @@ -158,6 +160,7 @@ def update_location_size(): # Workflow registration, not have activity sqope 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", @@ -288,19 +291,19 @@ def update_location_size(): } 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." + assert result.status_code == 400 + assert result.json.get("error") == "Failed to import item; Failed to import to system." # unexpected error in import to system - login_user_via_session(client=client, email=users[0]["email"]) + login_user_via_session(client=client, email=users[1]["email"]) headers = { - "Authorization": "Bearer {}".format(token_direct), + "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) - mocker.patch("weko_swordserver.views.check_import_file_format", return_value="TSV/CSV") + 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_check_item = mocker.patch("weko_swordserver.views.check_import_items") mocker_check_item.return_value = { @@ -308,6 +311,14 @@ def update_location_size(): "register_type": "Workflow", "list_record": [{"status": "new", "metadata": {},}] } + with patch("weko_swordserver.views.update_item_ids", side_effect=Exception("test 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") == ( + "Failed to import item; Unexpected error " \ + "Please open the following URL to continue with the remaining operations: " \ + "http://test_server.localdomain/workflow/activity/detail/A-TEST-00001." + ) # jsonid format login_user_via_session(client=client, email=users[0]["email"]) @@ -331,7 +342,7 @@ def update_location_size(): } result = client.post(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) - assert result.status_code == 200 + assert result.status_code == 201 assert result.json.get("recid") == "2000001" # digest mismatch @@ -354,6 +365,427 @@ def update_location_size(): assert result.json.get("error") == "Request body and digest verification failed." +# def put_object(recid): +# .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test_put_object -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp +def test_put_object( + app, client, db, users, make_crate, esindex, location, index, make_zip, + tokens, item_type, doi_identifier, mocker, sword_mapping, sword_client, es_records +): + 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 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 + + tokens[0]["token"]._scopes = "deposit:write deposit:actions item:update" + tokens[1]["token"]._scopes = "deposit:write deposit:actions item:update user:activity" + db.session.commit() + + url = url_for("weko_swordserver.put_object", recid=1) + + # Direct registration + 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", + "On-Behalf-Of": "test_on_behalf_of", + } + 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", "id": "1"}] + } + mocker.patch("weko_swordserver.views.import_items_to_system", return_value={"success": True, "recid": 1}) + mocker.patch("weko_items_ui.utils.send_mail_direct_registered") + mocker.patch("weko_swordserver.views.lock_item_will_be_edit", return_value=True) + os.makedirs("/var/tmp/test", exist_ok=True) + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 200 + assert result.json.get("recid") == "1" + assert not os.path.exists("/var/tmp/test"), os.rmdir("/var/tmp/test") + + # workflow registration, + login_user_via_session(client=client, email=users[1]["email"]) + app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = True + 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", + "On-Behalf-Of": "test_on_behalf_of", + "Digest": "SHA-256={}".format(calculate_hash(storage)), + } + 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": "keep", "id": "1", "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"), "1", "end_action", None)) + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 200 + assert result.json.get("recid") == "1" + + # 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.put(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.put(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 scope + 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", + } + 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": "keep", "metadata": {}}], + "duplicate_check": True + } + + result = client.put(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.put(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": "keep", "errors": ["Item check error."]}], + } + + result = client.put(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." + + # 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": "new", "item_title": "test_title"}], + } + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 400 + assert result.json.get("error") == "This item is not registered yet: test_title" + + # item_id mismatch + 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", "id": "invalid", "item_title": "test_title"}], + } + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 400 + assert result.json.get("error") == "Item id does not match. item: invalid, request: 1" + + # 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": "keep", "id": "1", "metadata": {},}], + "duplicate_check": True + } + mocker.patch("weko_items_ui.utils.check_duplicate", return_value=(True, ["2000001"], ["/records/2000001"])) + + result = client.put(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']." + + # being edited + 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", "id": "1", "metadata": {},}], + } + mocker.patch("weko_items_ui.utils.check_duplicate", return_value=(False, [], [])) + with patch("weko_swordserver.views.lock_item_will_be_edit", return_value=False): + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 400 + assert result.json.get("error") == "Item 1 is being edited." + + # 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": "keep", "id": "1", "metadata": {},}] + } + with patch("weko_swordserver.views.import_items_to_system", return_value={"success": False, "error_id": "Failed to import to system"}): + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 400 + assert result.json.get("error") == "Failed to update item 1: Failed to import to system." + + # failed to import to activity + login_user_via_session(client=client, email=users[1]["email"]) + app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = False + 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) + 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": "keep", "id": "1", "metadata": {}}], + } + mocker.patch("weko_swordserver.views.import_items_to_activity", + return_value=("sample_url", "1", "end_action", "Failed to import to activity")) + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 400 + assert result.json.get("error") == "Failed to update item 1: Failed to import to activity. " \ + "Please open the following URL to continue with the remaining operations: sample_url." + + # failed to import to activity without url + login_user_via_session(client=client, email=users[1]["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) + 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": "keep", "id": "1", "metadata": {}}], + } + mocker.patch("weko_swordserver.views.import_items_to_activity", + return_value=("", "1", "end_action", "Failed to import to activity")) + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 500 + assert result.json.get("error") == "Unexpected error: Failed to import to activity." + + # 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) + 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)), + } + 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": "keep", "id": "1", "metadata": {}}], + } + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 200 + assert result.json.get("recid") == "1" + + # 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_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.put(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." + + # invalid registration type + login_user_via_session(client=client, email=users[0]["email"]) + app.config["WEKO_SWORDSERVER_DIGEST_VERIFICATION"] = False + 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", + } + 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_check_item = mocker.patch("weko_swordserver.views.check_import_items") + mocker_check_item.return_value = { + "data_path": "/var/tmp/test", + "register_type": "invalid_type", + "list_record": [{"status": "keep", "id": "1", "metadata": {}}], + } + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 500 + assert result.json.get("error") == "Invalid register format has been set for admin setting" + + # invalid registration type with data_path exists + zip, _ = make_crate() + storage = FileStorage(filename="payload.zip", stream=zip) + os.makedirs("/var/tmp/test", exist_ok=True) + + result = client.put(url, data={"file": storage}, content_type="multipart/form-data", headers=headers) + assert result.status_code == 500 + assert result.json.get("error") == "Invalid register format has been set for admin setting" + assert not os.path.exists("/var/tmp/test"), os.rmdir("/var/tmp/test") + + # def get_status_document(recid): # .tox/c1/bin/pytest --cov=weko_swordserver tests/test_views.py::test__get_status_document -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-swordserver/.tox/c1/tmp def test_get_status_document(client, users, tokens): @@ -552,17 +984,19 @@ def test_delete_item(app, client, db, tokens, sword_client, users,es_records, mo 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"]) - tokens[0]["token"]._scopes = "deposit:write item:delete" - tokens[1]["token"]._scopes = "deposit:write item:delete user:activity" + tokens[0]["token"]._scopes = "deposit:write deposit:actions item:delete" + tokens[1]["token"]._scopes = "deposit:write deposit:actions item:delete user:activity" db.session.commit() + # direct deletion + login_user_via_session(client=client,email=users[0]["email"]) 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") + mocker.patch("weko_swordserver.views.check_deletion_type", return_value={"deletion_type": "Direct"}) + mock_delete_item_directly = mocker.patch("weko_swordserver.views.delete_item_directly") + mocker.patch("weko_swordserver.views.lock_item_will_be_edit", return_value=True) + mocker.patch("weko_swordserver.views.UserActivityLogger") url = url_for("weko_swordserver.delete_object", recid=2000001) headers = { @@ -571,11 +1005,25 @@ def test_delete_item(app, client, db, tokens, sword_client, users,es_records, mo res = client.delete(url, headers=headers) assert res.status_code == 204 - mock_soft_delete.assert_called_once_with("2000001") + expected = { + "remote_addr": request.remote_addr, + "referrer": request.referrer, + "hostname": request.host, + "user_id": users[0]["id"], + "shared_id": -1, + "action": "DELETE" + } + mock_delete_item_directly.assert_called_once_with("2000001", request_info=expected) + + # direct deletion, being edited + mocker.patch("weko_swordserver.views.lock_item_will_be_edit", return_value=False) + res = client.delete(url, headers=headers) + assert res.status_code == 400 + assert res.json.get("error") == "Item 2000001 is being edited." # 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"}) + mocker.patch("weko_swordserver.views.check_deletion_type", return_value={"deletion_type": "Workflow"}) url = url_for("weko_swordserver.delete_object", recid=2000001) headers = { @@ -585,17 +1033,48 @@ def test_delete_item(app, client, db, tokens, sword_client, users,es_records, mo assert res.status_code == 403 assert res.json.get("error") == "Not allowed operation in your token scope." - # workflow deletion, have activity scope + # workflow deletion, have activity scope, approval login_user_via_session(client=client,email=users[1]["email"]) mock_delete_with_activity = mocker.patch("weko_swordserver.views.delete_items_with_activity") + mock_delete_with_activity.return_value = ("url", "approval") 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 + mock_delete_with_activity.assert_called_once() + + # workflow deletion, have activity scope, end_action + mock_delete_with_activity = mocker.patch("weko_swordserver.views.delete_items_with_activity") + mock_delete_with_activity.return_value = ("url", "end_action") + + res = client.delete(url, headers=headers) + assert res.status_code == 204 + mock_delete_with_activity.assert_called_once() + + # raise WekoWorkflowException + with patch("weko_swordserver.views.delete_items_with_activity") as mock_delete_with_activity: + mock_delete_with_activity.side_effect = WekoWorkflowException("test error") + + res = client.delete(url, headers=headers) + assert res.status_code == 400 + assert res.json.get("error") == "Failed to delete item: test error" + + # raise unexpected Exception + with patch("weko_swordserver.views.delete_items_with_activity") as mock_delete_with_activity: + mock_delete_with_activity.side_effect = Exception("test error") + + res = client.delete(url, headers=headers) + assert res.status_code == 400 + assert res.json.get("error") == "Unexpected error occurred during deletion: test error" + + # item with doi + mock_record.pid_doi = "10.1234/test.00001" + with patch("weko_swordserver.views.WekoRecord.get_record_by_pid", return_value=mock_record): + res = client.delete(url, headers=headers) + assert res.status_code == 400 + assert res.json.get("error") == "Cannot delete item with DOI." # def _create_error_document(type, error): diff --git a/modules/weko-swordserver/weko_swordserver/config.py b/modules/weko-swordserver/weko_swordserver/config.py index 16baa08940..2cc4f40bdd 100644 --- a/modules/weko-swordserver/weko_swordserver/config.py +++ b/modules/weko-swordserver/weko_swordserver/config.py @@ -82,7 +82,7 @@ WEKO_SWORDSERVER_SERVICEDOCUMENT_ON_BEHALF_OF = True """ Does the server support deposit on behalf of other users (mediation) """ -WEKO_SWORDSERVER_SERVICEDOCUMENT_DIGEST = ["SHA-256", "SHA", "MD5"] +WEKO_SWORDSERVER_SERVICEDOCUMENT_DIGEST = ["SHA-256"] """ The list of digest formats that the server will accept. """ WEKO_SWORDSERVER_SERVICEDOCUMENT_AUTHENTICATION = ["OAuth"] diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index 4352d81347..b815c8b24c 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -346,14 +346,14 @@ def check_import_items( errorType=ErrorType.BadRequest ) - if not WorkFlows().reduce_workflows_for_registration([workflow]): - current_app.logger.error( - f"Workflow is not for item registration: {workflow_id}" - ) - raise WekoSwordserverException( - "Workflow is not for item registration.", - errorType=ErrorType.BadRequest - ) + if not WorkFlows().reduce_workflows_for_registration([workflow]): + current_app.logger.error( + f"Workflow is not for item registration: {workflow_id}" + ) + raise WekoSwordserverException( + "Workflow is not for item registration.", + errorType=ErrorType.BadRequest + ) check_result.update( check_jsonld_import_items( @@ -398,15 +398,6 @@ def update_item_ids(list_record, new_id, old_id): Raises: ValueError: If list_record is not a list. """ - # Create a dictionary to map identifiers to their respective items - for item in list_record: - if not isinstance(item, dict): - continue - - metadata = item.get("metadata") - if not metadata or not item.get("_id"): - continue # Skip if metadata is missing or doesn't have "_id" - # Iterate through each ITEM in list_record for item in list_record: metadata = item.get("metadata") diff --git a/modules/weko-swordserver/weko_swordserver/views.py b/modules/weko-swordserver/weko_swordserver/views.py index a4b5a58afc..f1ee3b45cd 100644 --- a/modules/weko-swordserver/weko_swordserver/views.py +++ b/modules/weko-swordserver/weko_swordserver/views.py @@ -274,10 +274,10 @@ def post_service_document(): or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) ): current_app.logger.error( - "Request body and digest verification failed." + "Failed to verify request body and digest." ) raise WekoSwordserverException( - "Request body and digest verification failed.", + "Failed to verify request body and digest.", ErrorType.DigestMismatch ) @@ -371,49 +371,49 @@ def process_item(item, request_info): - str: Record ID if applicable, otherwise None. - str: Error message if any, otherwise None. """ - activity_id, recid, error = None, None, None + activity_id, recid, action, error = None, None, None, None if register_type == "Direct": import_result = import_items_to_system( item, request_info=request_info ) if not import_result.get("success"): current_app.logger.error( - f"Error in import_items_to_system: {item.get('error_id')}" + f"Error in import_items_to_system: {import_result.get('error_id')}" ) - error = str(item.get('error_id')) + error = str(import_result.get('error_id')) else: recid = str(import_result.get("recid")) notify_item_imported( current_user.id, recid, current_user.id, shared_id=shared_id ) - send_mail_direct_registered(recid, current_user.id) + send_mail_direct_registered(recid, current_user.id, shared_id) elif register_type == "Workflow": - url, recid, _ , error = import_items_to_activity( + url, recid, action , error = import_items_to_activity( item, request_info=request_info ) activity_id = str(url.split("/")[-1]) - return activity_id, recid, error + return activity_id, recid, action, error - response = {} warns = [] activity_id = None recid = None + action = None # Process and register items for item in check_result["list_record"]: item["root_path"] = os.path.join(data_path, "data") try: - activity_id, recid, error = process_item(item, request_info) + activity_id, recid, action, error = process_item(item, request_info) if error: - warns.append((error, activity_id, recid)) + warns.append((activity_id, recid, error)) if file_format == "JSON": update_item_ids( check_result["list_record"], recid, item.get("_id")) except Exception as ex: current_app.logger.error(f"Unexpected error: {ex}") traceback.print_exc() - warns.append(("Unexpected error: {ex}", activity_id, recid)) + warns.append((activity_id, recid, "Unexpected error")) continue # Skip to the next iteration # Clean up temporary directory @@ -421,40 +421,43 @@ def process_item(item, request_info): shutil.rmtree(data_path) TempDirInfo().delete(data_path) - current_app.logger.info( - f"Items imported via SWORD api by {request.oauth.client.name} (recid={recid})" - ) + response = {} if len(warns) > 0: if register_type == "Direct": - raise WekoSwordserverException( - "Failed to import item.", ErrorType.ServerError) + message = ", ".join([error for _, _, error in warns if error]) else: - error_messages = "; ".join( + message = "; ".join( [ "{error} Please open the following URL to continue " - "with the remaining operations.{url}: Item id: {recid}." + "with the remaining operations: {url}." .format( error=error, url=url_for( "weko_workflow.display_activity", activity_id=activity_id, _external=True - ), - recid=recid + ) ) - for error, activity_id, recid in warns + for activity_id, recid, error in warns ] ) - - response = jsonify( - _create_error_document( - ErrorType.ContentMalformed.type, error_messages - ) + current_app.logger.error( + "Failed to import item; {}. via SWORD api by {}." + .format(message, request.oauth.client.name) ) + raise WekoSwordserverException( + f"Failed to import item; {message}", ErrorType.BadRequest + ) + + current_app.logger.info( + "Items imported via SWORD api by {} (recid={})" + .format(request.oauth.client.name, recid) + ) + if register_type == "Direct": + response = jsonify(_get_status_document(recid)), 201 else: - if register_type == "Direct": - response = jsonify(_get_status_document(recid)) - elif register_type == "Workflow": - response = jsonify(_get_status_workflow_document(activity_id,recid)) + response = jsonify( + _get_status_workflow_document(activity_id, recid) + ), 201 if action == "end_action" else 202 return response @@ -571,10 +574,10 @@ def put_object(recid): or not is_valid_file_hash(digest.split("SHA-256=")[-1], file) ): current_app.logger.error( - "Request body and digest verification failed." + "Failed to verify request body and digest." ) raise WekoSwordserverException( - "Request body and digest verification failed.", + "Failed to verify request body and digest.", ErrorType.DigestMismatch ) @@ -607,6 +610,14 @@ def put_object(recid): ErrorType.ContentMalformed ) + if len(check_result.get("list_record", [])) > 1: + msg = ( + "Multiple items found in import file. " + "Only one item is allowed for PUT requests." + ) + current_app.logger.error(msg) + raise WekoSwordserverException(msg, ErrorType.ContentMalformed) + # only first item item = (check_result.get("list_record") or [{}])[0] if not item or item.get("errors"): @@ -669,44 +680,51 @@ def put_object(recid): if register_type == "Direct": # Check cache if the item is being edited if not lock_item_will_be_edit(recid): - current_app.logger.error(f"Item {recid} is being edited.") - raise WekoSwordserverException( - f"Item {recid} is being edited.", - ErrorType.BadRequest - ) + msg = f"Item {recid} will be edited by another process." + current_app.logger.error(msg) + raise WekoSwordserverException(msg, ErrorType.BadRequest) + import_result = import_items_to_system(item, request_info=request_info) if not import_result.get("success"): current_app.logger.error( - f"Error in import_items_to_system: {item.get('error_id')}" + "Failed to update item {}: {}; via SWORD api by {}." + .format(recid, item.get("error_id"), request.oauth.client.name) ) raise WekoSwordserverException( - f"Item import error:: {import_result.get('error_id')}", - ErrorType.ServerError + "Failed to update item {}: {}." + .format(recid, import_result.get("error_id")), + ErrorType.BadRequest ) - send_mail_direct_registered(recid, current_user.id) + send_mail_direct_registered(recid, current_user.id, shared_id) notify_item_imported( current_user.id, recid, current_user.id, shared_id=shared_id ) - response = jsonify(_get_status_document(recid)) + response = jsonify(_get_status_document(recid)), 200 elif register_type == "Workflow": - url, _, _, error = import_items_to_activity( + url, _, action, error = import_items_to_activity( item, request_info=request_info ) activity_id = url.split("/")[-1] if error and url: + current_app.logger.error( + "Failed to update item {recid}: {error}; via SWORD api by {name}." + .format(recid=recid, error=error, name=request.oauth.client.name) + ) 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), + "Failed to update item {recid}: {error}. Please open the " + "following URL to continue with the remaining operations: {url}." + .format(recid=recid, error=error, url=url), ErrorType.BadRequest ) if error: raise WekoSwordserverException( f"Unexpected error: {error}.", ErrorType.ServerError ) - response = jsonify(_get_status_workflow_document(activity_id, recid)) + response = jsonify( + _get_status_workflow_document(activity_id, recid) + ), 200 if action == "end_action" else 202 else: if os.path.exists(data_path): shutil.rmtree(data_path) @@ -721,7 +739,8 @@ def put_object(recid): TempDirInfo().delete(data_path) current_app.logger.info( - f"Item updated via SWORD api by {request.oauth.client.name} (recid={recid})" + "Item updated via SWORD api by {} (recid={})" + .format(request.oauth.client.name, recid) ) return response @@ -765,9 +784,13 @@ def get_status_document(recid): return jsonify(status_document) def _get_status_document(recid): - """ - :param recid: Record Identifier. - :returns: A :class:`sword3common.StatusDocument` instance. + """Generate a Status Document for a given record. + + Args: + recid (str): Item identifier (recid). + + Returns: + dict: A Status Document. """ # Get record @@ -816,11 +839,11 @@ def _get_status_document(recid): "deleteObject" : True, }, "eTag" : str(record.revision_id), - "fileSet" : { + "fileSet" : { # Not implemented # "@id" : "", # "eTag" : "" }, - "metadata" : { + "metadata" : { # Not implemented # "@id" : "", # "eTag" : "" }, @@ -996,11 +1019,10 @@ def delete_object(recid): else: # Check cache if the item is being edited if not lock_item_will_be_edit(recid): - current_app.logger.error(f"Item {recid} is being edited.") - raise WekoSwordserverException( - f"Item {recid} is being edited.", - ErrorType.BadRequest - ) + msg = f"Item {recid} will be edited by another process." + current_app.logger.error(msg) + raise WekoSwordserverException(msg, ErrorType.BadRequest) + delete_item_directly(recid, request_info=request_info) notify_item_deleted( current_user.id, recid, current_user.id, shared_id=shared_id diff --git a/modules/weko-user-profiles/weko_user_profiles/forms.py b/modules/weko-user-profiles/weko_user_profiles/forms.py index 587b9c7173..9cf00a2693 100644 --- a/modules/weko-user-profiles/weko_user_profiles/forms.py +++ b/modules/weko-user-profiles/weko_user_profiles/forms.py @@ -77,16 +77,6 @@ def check_length_100_characters(form, field): if len(field.data) > 100: raise ValidationError(_("Text field must be less than 100 characters.")) -def check_s3_endpoint_url(form, field): - """Check s3_endpoint_url. - - :param form: - :param field: - """ - if field.data.startswith('https://s3.'): - raise ValidationError(_("Endpoint URL must not begin with https://s3.")) - - def check_other_position(form, field): """Check other position. @@ -178,8 +168,7 @@ class ProfileForm(FlaskForm): _('endpoint url'), # NOTE: Form field help text description=_('Please enter if you use your own S3 Bucket.'), - validators=[check_length_100_characters, - check_s3_endpoint_url], + validators=[check_length_100_characters], filters=[strip_filter], ) diff --git a/modules/weko-user-profiles/weko_user_profiles/views.py b/modules/weko-user-profiles/weko_user_profiles/views.py index 7a81e0208c..4160ec6fdb 100644 --- a/modules/weko-user-profiles/weko_user_profiles/views.py +++ b/modules/weko-user-profiles/weko_user_profiles/views.py @@ -145,7 +145,7 @@ def profile(): from weko_notifications.utils import create_userprofile, inbox_url create_userprofile(current_userprofile) requests.post( - inbox_url("/userprofile"), + inbox_url(endpoint="/userprofile"), json=create_userprofile(current_userprofile) ) except Exception as ex: diff --git a/modules/weko-workflow/tests/test_api.py b/modules/weko-workflow/tests/test_api.py index efe856255c..a557bbd0c3 100644 --- a/modules/weko-workflow/tests/test_api.py +++ b/modules/weko-workflow/tests/test_api.py @@ -1,17 +1,16 @@ -from datetime import datetime -from unittest.mock import MagicMock, patch -from sqlalchemy.exc import SQLAlchemyError - -from flask_login.utils import login_user import json import uuid +from datetime import datetime +from unittest.mock import MagicMock, call, patch -from flask_login.utils import login_user import pytest -from unittest.mock import patch -from weko_notifications import Notification +from flask_login.utils import login_user +from marshmallow import ValidationError +from requests import HTTPError +from sqlalchemy.exc import SQLAlchemyError -from weko_workflow.api import Flow, WorkActivity, WorkFlow, GetCommunity +from weko_notifications.notifications import Notification +from weko_workflow.api import Flow, GetCommunity, WorkActivity, WorkFlow # .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_Flow_create_flow -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_Flow_create_flow(app, client, users, db, action_data): @@ -437,6 +436,147 @@ def test_workactivity_get_params_for_registrant(app, users, db_register, db_reco assert actor_name == db_user_profile.username +@pytest.fixture +def mock_create_item_registered(mocker): + return mocker.patch.object( + Notification, "create_item_registered", + return_value=Notification() + ) + + +@pytest.fixture +def mock_send(mocker): + return mocker.patch.object(Notification, "send") + + +@pytest.fixture +def mock_inbox_url(mocker): + return mocker.patch( + "weko_workflow.api.inbox_url", + return_value="http://example.com/inbox" + ) + + +@pytest.fixture +def mock_logger(mocker): + return mocker.patch("weko_workflow.api.current_app.logger") + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_success -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_success( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = _Activity(activity_id=1, title="test") + recid = MagicMock(pid_value="123.4") + getter = MagicMock(return_value=({1, 2}, recid, 1, "actor_name")) + expected_calls = [ + call(1, "123", 1, context_id=1, actor_name="actor_name", object_name="test"), + call(2, "123", 1, context_id=1, actor_name="actor_name", object_name="test"), + ] + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + + assert mock_create_item_registered.call_args_list == expected_calls + assert mock_send.call_count == 2 + mock_logger.info.assert_called_once_with( + "2 notification(s) sent for test_case: 1" + ) + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_sql_error -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_sql_error( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = _Activity(activity_id=1, title="test") + getter = MagicMock(side_effect=SQLAlchemyError) + + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + + mock_logger.error.assert_called_once_with( + "Failed to get notification parameters for activity: 1" + ) + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_valid_error -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_valid_error( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = _Activity(activity_id=1, title="test") + recid = MagicMock(pid_value="123.4") + getter = MagicMock(return_value=({1, 2}, recid, 1, "actor_name")) + mock_create_item_registered.side_effect = ValidationError("Invalid data") + + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + mock_logger.error.assert_called_once_with( + "Failed to send notification for test_case: 1" + ) + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_http_error -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_http_error( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = _Activity(activity_id=1, title="test") + recid = MagicMock(pid_value="123.4") + getter = MagicMock(return_value=({1, 2}, recid, 1, "actor_name")) + mock_send.side_effect = HTTPError("HTTP error occurred") + + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + + mock_logger.error.assert_called_once_with( + "Failed to send notification for test_case: 1" + ) + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_exception -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_exception( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = _Activity(activity_id=1, title="test") + recid = MagicMock(pid_value="123.4") + getter = MagicMock(return_value=({1, 2}, recid, 1, "actor_name")) + mock_create_item_registered.side_effect = Exception("Unexpected error") + + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + + mock_logger.error.assert_called_once_with( + "Unexpected error had occurred during sending notification for activity: 1" + ) + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_notify_about_activity_wiht_case_invalid_activity -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_workactivity_notify_about_activity_wiht_case_invalid_activity( + app, mock_create_item_registered, mock_send, mock_inbox_url, mock_logger +): + activity = MagicMock() + recid = MagicMock(pid_value="123.4") + getter = MagicMock(return_value=({1, 2}, recid, 1, "actor_name")) + + instance = WorkActivity() + instance._notify_about_activity_wiht_case( + activity, "test_case", getter, Notification.create_item_registered + ) + + mock_create_item_registered.assert_not_called() + mock_send.assert_not_called() + mock_logger.info.assert_not_called() + mock_logger.error.assert_not_called() + + # .tox/c1/bin/pytest --cov=weko_workflow tests/test_api.py::test_workactivity_get_params_for_approver -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_workactivity_get_params_for_approver(app, users, db, db_register, mocker, db_records, db_user_profile): from invenio_communities.models import Community diff --git a/modules/weko-workspace/tests/conftest.py b/modules/weko-workspace/tests/conftest.py index 5a1327d13b..e5350015d8 100644 --- a/modules/weko-workspace/tests/conftest.py +++ b/modules/weko-workspace/tests/conftest.py @@ -80,7 +80,9 @@ from weko_workspace.views import blueprint_itemapi as weko_workspace_blueprint_itemapi from weko_workspace.models import WorkspaceDefaultConditions,WorkspaceStatusManagement -from weko_workspace.config import WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM, WEKO_WORKSPACE_OA_STATUS_MAPPING +from weko_workspace.config import WEKO_WORKSPACE_CINII_REQUIRED_ITEM,\ + WEKO_WORKSPACE_JALC_REQUIRED_ITEM, WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM,\ + WEKO_WORKSPACE_OA_STATUS_MAPPING from weko_redis.redis import RedisConnection sys.path.append(os.path.dirname(__file__)) @@ -452,7 +454,9 @@ def base_app(instance_path, search_class, cache_config): WEKO_WORKFLOW_ACTION_GUARANTOR=WEKO_WORKFLOW_ACTION_GUARANTOR, WEKO_WORKFLOW_ACTION_ADVISOR=WEKO_WORKFLOW_ACTION_ADVISOR, WEKO_WORKFLOW_ACTION_ADMINISTRATOR=WEKO_WORKFLOW_ACTION_ADMINISTRATOR, - WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM=WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM, + WEKO_WORKSPACE_CINII_REQUIRED_ITEM=WEKO_WORKSPACE_CINII_REQUIRED_ITEM, + WEKO_WORKSPACE_JALC_REQUIRED_ITEM=WEKO_WORKSPACE_JALC_REQUIRED_ITEM, + WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM=WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM, WEKO_WORKFLOW_GAKUNINRDM_DATA=[ { 'workflow_id': -1, @@ -556,23 +560,30 @@ def mocker_itemtype(mocker): item_type.render = render filepath = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "data/item_type/15_render.json" + os.path.dirname(os.path.realpath(__file__)), "data/item_type/15_schema.json" ) with open(filepath, encoding="utf-8") as f: schema = json.load(f) item_type.schema = schema filepath = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "data/item_type/15_render.json" + os.path.dirname(os.path.realpath(__file__)), "data/item_type/15_form.json" ) with open(filepath, encoding="utf-8") as f: form = json.load(f) item_type.form = form + filepath = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "data/item_type/item_type_mapping.json" + ) + with open(filepath, encoding="utf-8") as f: + item_type_mapping = json.load(f) + item_type.item_type_name.name = "デフォルトアイテムタイプ(フル)" item_type.item_type_name.item_type.first().id = 15 mocker.patch("weko_records.api.ItemTypes.get_by_id", return_value=item_type) + mocker.patch('weko_records.api.Mapping.get_record', return_value=item_type_mapping) @pytest.yield_fixture() @@ -796,12 +807,12 @@ def oa_status(db): """Create OA status data.""" oa_status_1 = OaStatus( oa_article_id=1, - oa_status='Processed Fulltext Registered (Embargo)', + oa_status='Processed Fulltext Registered (Embargo)', weko_item_pid='1' ) oa_status_2 = OaStatus( oa_article_id=2, - oa_status='Processing Metadata Registered', + oa_status='Processing Metadata Registered', weko_item_pid='2' ) diff --git a/modules/weko-workspace/tests/data/test_convert_xml_to_dict.xml b/modules/weko-workspace/tests/data/test_convert_xml_to_dict.xml new file mode 100644 index 0000000000..192cb8613f --- /dev/null +++ b/modules/weko-workspace/tests/data/test_convert_xml_to_dict.xml @@ -0,0 +1,50 @@ + + + test-identifier + + Test Title + Dummy Title + + + Test Creator 1 + Test Creator 2 + + + Test Type 1 + Test Type 2 + + en + Test Publisher + + Test Description 1 + Test Description 2 + + + Test Organization 1 + Test Organization 2 + + Test Publication Name + 1234-5678 + 8765-4321 + + 978-3-16-148410-0 + 978-1-23-456789-0 + + 1 + 2 + 10 + 10-20 + + 2023-01-01 + 2023-02-01 + + + Test Keyword 1 + Test Keyword 2 + + 10.1234/test-doi + + 2023-01-15 + 2023-02-15 + + \ No newline at end of file diff --git a/modules/weko-workspace/tests/data/test_get_autofill_key_tree.json b/modules/weko-workspace/tests/data/test_get_autofill_key_tree.json new file mode 100644 index 0000000000..5dc45e6b9d --- /dev/null +++ b/modules/weko-workspace/tests/data/test_get_autofill_key_tree.json @@ -0,0 +1,276 @@ +{ + "title": { + "@value": "subitem_1551255647225", + "@attributes": { + "xml:lang": "subitem_1551255648112" + }, + "model_id": "item_1617186331708" + }, + "alternative": { + "@value": "subitem_1551255720400", + "@attributes": { + "xml:lang": "subitem_1551255721061" + }, + "model_id": "item_1617186385884" + }, + "creator": { + "givenName": { + "@value": "givenNames.givenName", + "@attributes": { + "xml:lang": "givenNames.givenNameLang" + } + }, + "familyName": { + "@value": "familyNames.familyName", + "@attributes": { + "xml:lang": "familyNames.familyNameLang" + } + }, + "affiliation": { + "nameIdentifier": { + "@value": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifier", + "@attributes": { + "nameIdentifierURI": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierURI", + "nameIdentifierScheme": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierScheme" + } + }, + "affiliationName": { + "@value": "creatorAffiliations.affiliationNames.affiliationName", + "@attributes": { + "xml:lang": "creatorAffiliations.affiliationNames.affiliationNameLang" + } + } + }, + "creatorName": { + "@value": "creatorNames.creatorName", + "@attributes": { + "xml:lang": "creatorNames.creatorNameLang" + } + }, + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "creatorAlternative": { + "@value": "creatorAlternatives.creatorAlternative", + "@attributes": { + "xml:lang": "creatorAlternatives.creatorAlternativeLang" + } + }, + "model_id": "item_1617186419668" + }, + "contributor": { + "givenName": { + "@value": "givenNames.givenName", + "@attributes": { + "xml:lang": "givenNames.givenNameLang" + } + }, + "familyName": { + "@value": "familyNames.familyName", + "@attributes": { + "xml:lang": "familyNames.familyNameLang" + } + }, + "@attributes": { + "contributorType": "contributorType" + }, + "affiliation": { + "nameIdentifier": { + "@value": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationNameIdentifier", + "@attributes": { + "nameIdentifierURI": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationURI", + "nameIdentifierScheme": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationScheme" + } + }, + "affiliationName": { + "@value": "contributorAffiliations.contributorAffiliationNames.contributorAffiliationName", + "@attributes": { + "xml:lang": "contributorAffiliations.contributorAffiliationNames.contributorAffiliationNameLang" + } + } + }, + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "contributorName": { + "@value": "contributorNames.contributorName", + "@attributes": { + "xml:lang": "contributorNames.lang" + } + }, + "contributorAlternative": { + "@value": "contributorAlternatives.contributorAlternative", + "@attributes": { + "xml:lang": "contributorAlternatives.contributorAlternativeLang" + } + }, + "model_id": "item_1617349709064" + }, + "description": { + "@value": "subitem_description", + "@attributes": { + "xml:lang": "subitem_description_language", + "descriptionType": "subitem_description_type" + }, + "model_id": "item_1617186626617" + }, + "subject": { + "@value": "subitem_1523261968819", + "@attributes": { + "xml:lang": "subitem_1522299896455", + "subjectURI": "subitem_1522300048512", + "subjectScheme": "subitem_1522300014469" + }, + "model_id": "item_1617186609386" + }, + "sourceTitle": [ + { + "sourceTitle": { + "@value": "subitem_1522650091861", + "@attributes": { + "xml:lang": "subitem_1522650068558" + }, + "model_id": "item_1617186941041" + } + }, + { + "sourceTitle": { + "@value": "bibliographic_titles.bibliographic_title", + "@attributes": { + "xml:lang": "bibliographic_titles.bibliographic_titleLang" + }, + "model_id": "item_1617187056579" + } + } + ], + "volume": [ + { + "volume": { + "@value": "subitem_1551256328147", + "model_id": "item_1617186959569" + } + }, + { + "volume": { + "@value": "bibliographicVolumeNumber", + "model_id": "item_1617187056579" + } + } + ], + "issue": [ + { + "issue": { + "@value": "subitem_1551256294723", + "model_id": "item_1617186981471" + } + }, + { + "issue": { + "@value": "bibliographicIssueNumber", + "model_id": "item_1617187056579" + } + } + ], + "pageStart": [ + { + "pageStart": { + "@value": "subitem_1551256198917", + "model_id": "item_1617187024783" + } + }, + { + "pageStart": { + "@value": "bibliographicPageStart", + "model_id": "item_1617187056579" + } + } + ], + "pageEnd": [ + { + "pageEnd": { + "@value": "subitem_1551256185532", + "model_id": "item_1617187045071" + } + }, + { + "pageEnd": { + "@value": "bibliographicPageEnd", + "model_id": "item_1617187056579" + } + } + ], + "numPages": [ + { + "numPages": { + "@value": "subitem_1551256248092", + "model_id": "item_1617186994930" + } + }, + { + "numPages": { + "@value": "bibliographicNumberOfPages", + "model_id": "item_1617187056579" + } + } + ], + "date": [ + { + "date": { + "@value": "subitem_1522300722591", + "@attributes": { + "dateType": "subitem_1522300695726" + }, + "model_id": "item_1617186660861" + } + }, + { + "date": { + "@value": "bibliographicIssueDates.bibliographicIssueDate", + "@attributes": { + "dateType": "bibliographicIssueDates.bibliographicIssueDateType" + }, + "model_id": "item_1617187056579" + } + } + ], + "publisher": { + "@value": "subitem_1522300316516", + "@attributes": { + "xml:lang": "subitem_1522300295150" + }, + "model_id": "item_1617186643794" + }, + "sourceIdentifier": { + "@value": "subitem_1522646572813", + "@attributes": { + "identifierType": "subitem_1522646500366" + }, + "model_id": "item_1617186920753" + }, + "relation": { + "@attributes": { + "relationType": "subitem_1522306207484" + }, + "relatedTitle": { + "@value": "subitem_1523320863692.subitem_1523320909613", + "@attributes": { + "xml:lang": "subitem_1523320863692.subitem_1523320867455" + } + }, + "relatedIdentifier": { + "@value": "subitem_1522306287251.subitem_1522306436033", + "@attributes": { + "identifierType": "subitem_1522306287251.subitem_1522306382014" + } + }, + "model_id": "item_1617353299429" + } +} \ No newline at end of file diff --git a/modules/weko-workspace/tests/data/test_get_cinii_autofill_item.json b/modules/weko-workspace/tests/data/test_get_cinii_autofill_item.json new file mode 100644 index 0000000000..58396affac --- /dev/null +++ b/modules/weko-workspace/tests/data/test_get_cinii_autofill_item.json @@ -0,0 +1,217 @@ +{ + "title": { + "@value": "subitem_1551255647225", + "@attributes": { + "xml:lang": "subitem_1551255648112" + }, + "model_id": "item_1617186331708" + }, + "creator": { + "givenName": { + "@value": "givenNames.givenName", + "@attributes": { + "xml:lang": "givenNames.givenNameLang" + } + }, + "familyName": { + "@value": "familyNames.familyName", + "@attributes": { + "xml:lang": "familyNames.familyNameLang" + } + }, + "affiliation": { + "nameIdentifier": { + "@value": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifier", + "@attributes": { + "nameIdentifierURI": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierURI", + "nameIdentifierScheme": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierScheme" + } + }, + "affiliationName": { + "@value": "creatorAffiliations.affiliationNames.affiliationName", + "@attributes": { + "xml:lang": "creatorAffiliations.affiliationNames.affiliationNameLang" + } + } + }, + "creatorName": { + "@value": "creatorNames.creatorName", + "@attributes": { + "xml:lang": "creatorNames.creatorNameLang" + } + }, + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "creatorAlternative": { + "@value": "creatorAlternatives.creatorAlternative", + "@attributes": { + "xml:lang": "creatorAlternatives.creatorAlternativeLang" + } + }, + "model_id": "item_1617186419668" + }, + "description": { + "@value": "subitem_description", + "@attributes": { + "xml:lang": "subitem_description_language", + "descriptionType": "subitem_description_type" + }, + "model_id": "item_1617186626617" + }, + "subject": { + "@value": "subitem_1523261968819", + "@attributes": { + "xml:lang": "subitem_1522299896455", + "subjectURI": "subitem_1522300048512", + "subjectScheme": "subitem_1522300014469" + }, + "model_id": "item_1617186609386" + }, + "sourceTitle": [ + { + "sourceTitle": { + "@value": "subitem_1522650091861", + "@attributes": { + "xml:lang": "subitem_1522650068558" + }, + "model_id": "item_1617186941041" + } + }, + { + "sourceTitle": { + "@value": "bibliographic_titles.bibliographic_title", + "@attributes": { + "xml:lang": "bibliographic_titles.bibliographic_titleLang" + }, + "model_id": "item_1617187056579" + } + } + ], + "volume": [ + { + "volume": { + "@value": "subitem_1551256328147", + "model_id": "item_1617186959569" + } + }, + { + "volume": { + "@value": "bibliographicVolumeNumber", + "model_id": "item_1617187056579" + } + } + ], + "issue": [ + { + "issue": { + "@value": "subitem_1551256294723", + "model_id": "item_1617186981471" + } + }, + { + "issue": { + "@value": "bibliographicIssueNumber", + "model_id": "item_1617187056579" + } + } + ], + "pageStart": [ + { + "pageStart": { + "@value": "subitem_1551256198917", + "model_id": "item_1617187024783" + } + }, + { + "pageStart": { + "@value": "bibliographicPageStart", + "model_id": "item_1617187056579" + } + } + ], + "pageEnd": [ + { + "pageEnd": { + "@value": "subitem_1551256185532", + "model_id": "item_1617187045071" + } + }, + { + "pageEnd": { + "@value": "bibliographicPageEnd", + "model_id": "item_1617187056579" + } + } + ], + "numPages": [ + { + "numPages": { + "@value": "subitem_1551256248092", + "model_id": "item_1617186994930" + } + }, + { + "numPages": { + "@value": "bibliographicNumberOfPages", + "model_id": "item_1617187056579" + } + } + ], + "date": [ + { + "date": { + "@value": "subitem_1522300722591", + "@attributes": { + "dateType": "subitem_1522300695726" + }, + "model_id": "item_1617186660861" + } + }, + { + "date": { + "@value": "bibliographicIssueDates.bibliographicIssueDate", + "@attributes": { + "dateType": "bibliographicIssueDates.bibliographicIssueDateType" + }, + "model_id": "item_1617187056579" + } + } + ], + "publisher": { + "@value": "subitem_1522300316516", + "@attributes": { + "xml:lang": "subitem_1522300295150" + }, + "model_id": "item_1617186643794" + }, + "sourceIdentifier": { + "@value": "subitem_1522646572813", + "@attributes": { + "identifierType": "subitem_1522646500366" + }, + "model_id": "item_1617186920753" + }, + "relation": { + "@attributes": { + "relationType": "subitem_1522306207484" + }, + "relatedTitle": { + "@value": "subitem_1523320863692.subitem_1523320909613", + "@attributes": { + "xml:lang": "subitem_1523320863692.subitem_1523320867455" + } + }, + "relatedIdentifier": { + "@value": "subitem_1522306287251.subitem_1522306436033", + "@attributes": { + "identifierType": "subitem_1522306287251.subitem_1522306382014" + } + }, + "model_id": "item_1617353299429" + } +} \ No newline at end of file diff --git a/modules/weko-workspace/tests/data/test_get_item_id.json b/modules/weko-workspace/tests/data/test_get_item_id.json new file mode 100644 index 0000000000..deb0f906ca --- /dev/null +++ b/modules/weko-workspace/tests/data/test_get_item_id.json @@ -0,0 +1,557 @@ +{ + "system_file": { + "URI": { + "@value": "subitem_systemfile_filename_uri", + "@attributes": { + "label": "subitem_systemfile_filename_label", + "objectType": "subitem_systemfile_filename_type" + } + }, + "date": { + "@value": "subitem_systemfile_datetime_date", + "@attributes": { + "dateType": "subitem_systemfile_datetime_type" + } + }, + "extent": { + "@value": "subitem_systemfile_size" + }, + "version": { + "@value": "subitem_systemfile_version" + }, + "mimeType": { + "@value": "subitem_systemfile_mimetype" + }, + "model_id": "system_file" + }, + "title": { + "@value": "subitem_1551255647225", + "@attributes": { + "xml:lang": "subitem_1551255648112" + }, + "model_id": "item_1617186331708" + }, + "alternative": { + "@value": "subitem_1551255720400", + "@attributes": { + "xml:lang": "subitem_1551255721061" + }, + "model_id": "item_1617186385884" + }, + "creator": { + "givenName": { + "@value": "givenNames.givenName", + "@attributes": { + "xml:lang": "givenNames.givenNameLang" + } + }, + "familyName": { + "@value": "familyNames.familyName", + "@attributes": { + "xml:lang": "familyNames.familyNameLang" + } + }, + "affiliation": { + "nameIdentifier": { + "@value": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifier", + "@attributes": { + "nameIdentifierURI": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierURI", + "nameIdentifierScheme": "creatorAffiliations.affiliationNameIdentifiers.affiliationNameIdentifierScheme" + } + }, + "affiliationName": { + "@value": "creatorAffiliations.affiliationNames.affiliationName", + "@attributes": { + "xml:lang": "creatorAffiliations.affiliationNames.affiliationNameLang" + } + } + }, + "creatorName": { + "@value": "creatorNames.creatorName", + "@attributes": { + "xml:lang": "creatorNames.creatorNameLang" + } + }, + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "creatorAlternative": { + "@value": "creatorAlternatives.creatorAlternative", + "@attributes": { + "xml:lang": "creatorAlternatives.creatorAlternativeLang" + } + }, + "model_id": "item_1617186419668" + }, + "accessRights": { + "@value": "subitem_1522299639480", + "@attributes": { + "rdf:resource": "subitem_1600958577026" + }, + "model_id": "item_1617186476635" + }, + "rights": { + "@value": "subitem_1522651041219", + "@attributes": { + "xml:lang": "subitem_1522650717957", + "rdf:resource": "subitem_1522650727486" + }, + "model_id": "item_1617186499011" + }, + "subject": { + "@value": "subitem_1523261968819", + "@attributes": { + "xml:lang": "subitem_1522299896455", + "subjectURI": "subitem_1522300048512", + "subjectScheme": "subitem_1522300014469" + }, + "model_id": "item_1617186609386" + }, + "description": { + "@value": "subitem_description", + "@attributes": { + "xml:lang": "subitem_description_language", + "descriptionType": "subitem_description_type" + }, + "model_id": "item_1617186626617" + }, + "publisher": { + "@value": "subitem_1522300316516", + "@attributes": { + "xml:lang": "subitem_1522300295150" + }, + "model_id": "item_1617186643794" + }, + "date": [ + { + "date": { + "@value": "subitem_1522300722591", + "@attributes": { + "dateType": "subitem_1522300695726" + }, + "model_id": "item_1617186660861" + } + }, + { + "date": { + "@value": "bibliographicIssueDates.bibliographicIssueDate", + "@attributes": { + "dateType": "bibliographicIssueDates.bibliographicIssueDateType" + }, + "model_id": "item_1617187056579" + } + } + ], + "language": { + "@value": "subitem_1551255818386", + "model_id": "item_1617186702042" + }, + "identifier": { + "@value": "subitem_identifier_uri", + "@attributes": { + "identifierType": "subitem_identifier_type" + }, + "model_id": "item_1617186783814" + }, + "identifierRegistration": { + "@value": "subitem_identifier_reg_text", + "@attributes": { + "identifierType": "subitem_identifier_reg_type" + }, + "model_id": "item_1617186819068" + }, + "temporal": { + "@value": "subitem_1522658031721", + "@attributes": { + "xml:lang": "subitem_1522658018441" + }, + "model_id": "item_1617186859717" + }, + "geoLocation": { + "geoLocationBox": { + "eastBoundLongitude": { + "@value": "subitem_geolocation_box.subitem_east_longitude" + }, + "northBoundLatitude": { + "@value": "subitem_geolocation_box.subitem_north_latitude" + }, + "southBoundLatitude": { + "@value": "subitem_geolocation_box.subitem_south_latitude" + }, + "westBoundLongitude": { + "@value": "subitem_geolocation_box.subitem_west_longitude" + } + }, + "geoLocationPlace": { + "@value": "subitem_geolocation_place.subitem_geolocation_place_text" + }, + "geoLocationPoint": { + "pointLatitude": { + "@value": "subitem_geolocation_point.subitem_point_latitude" + }, + "pointLongitude": { + "@value": "subitem_geolocation_point.subitem_point_longitude" + } + }, + "model_id": "item_1617186882738" + }, + "fundingReference": { + "awardTitle": { + "@value": "subitem_1522399651758.subitem_1522721929892", + "@attributes": { + "xml:lang": "subitem_1522399651758.subitem_1522721910626" + } + }, + "funderName": { + "@value": "subitem_1522399412622.subitem_1522737543681", + "@attributes": { + "xml:lang": "subitem_1522399412622.subitem_1522399416691" + } + }, + "awardNumber": { + "@value": "subitem_1522399571623.subitem_1522399628911", + "@attributes": { + "awardURI": "subitem_1522399571623.subitem_1522399585738" + } + }, + "funderIdentifier": { + "@value": "subitem_1522399143519.subitem_1522399333375", + "@attributes": { + "funderIdentifierType": "subitem_1522399143519.subitem_1522399281603" + } + }, + "model_id": "item_1617186901218" + }, + "sourceIdentifier": { + "@value": "subitem_1522646572813", + "@attributes": { + "identifierType": "subitem_1522646500366" + }, + "model_id": "item_1617186920753" + }, + "sourceTitle": [ + { + "sourceTitle": { + "@value": "subitem_1522650091861", + "@attributes": { + "xml:lang": "subitem_1522650068558" + }, + "model_id": "item_1617186941041" + } + }, + { + "sourceTitle": { + "@value": "bibliographic_titles.bibliographic_title", + "@attributes": { + "xml:lang": "bibliographic_titles.bibliographic_titleLang" + }, + "model_id": "item_1617187056579" + } + } + ], + "volume": [ + { + "volume": { + "@value": "subitem_1551256328147", + "model_id": "item_1617186959569" + } + }, + { + "volume": { + "@value": "bibliographicVolumeNumber", + "model_id": "item_1617187056579" + } + } + ], + "issue": [ + { + "issue": { + "@value": "subitem_1551256294723", + "model_id": "item_1617186981471" + } + }, + { + "issue": { + "@value": "bibliographicIssueNumber", + "model_id": "item_1617187056579" + } + } + ], + "numPages": [ + { + "numPages": { + "@value": "subitem_1551256248092", + "model_id": "item_1617186994930" + } + }, + { + "numPages": { + "@value": "bibliographicNumberOfPages", + "model_id": "item_1617187056579" + } + } + ], + "pageStart": [ + { + "pageStart": { + "@value": "subitem_1551256198917", + "model_id": "item_1617187024783" + } + }, + { + "pageStart": { + "@value": "bibliographicPageStart", + "model_id": "item_1617187056579" + } + } + ], + "pageEnd": [ + { + "pageEnd": { + "@value": "subitem_1551256185532", + "model_id": "item_1617187045071" + } + }, + { + "pageEnd": { + "@value": "bibliographicPageEnd", + "model_id": "item_1617187056579" + } + } + ], + "dissertationNumber": { + "@value": "subitem_1551256171004", + "model_id": "item_1617187087799" + }, + "degreeName": { + "@value": "subitem_1551256126428", + "@attributes": { + "xml:lang": "subitem_1551256129013" + }, + "model_id": "item_1617187112279" + }, + "dateGranted": { + "@value": "subitem_1551256096004", + "model_id": "item_1617187136212" + }, + "conference": { + "conferenceDate": { + "@value": "subitem_1599711699392.subitem_1599711704251", + "@attributes": { + "endDay": "subitem_1599711699392.subitem_1599711735410", + "endYear": "subitem_1599711699392.subitem_1599711743722", + "endMonth": "subitem_1599711699392.subitem_1599711739022", + "startDay": "subitem_1599711699392.subitem_1599711712451", + "xml:lang": "subitem_1599711699392.subitem_1599711745532", + "startYear": "subitem_1599711699392.subitem_1599711731891", + "startMonth": "subitem_1599711699392.subitem_1599711727603" + } + }, + "conferenceName": { + "@value": "subitem_1599711633003.subitem_1599711636923", + "@attributes": { + "xml:lang": "subitem_1599711633003.subitem_1599711645590" + } + }, + "conferenceVenue": { + "@value": "subitem_1599711758470.subitem_1599711769260", + "@attributes": { + "xml:lang": "subitem_1599711758470.subitem_1599711775943" + } + }, + "conferenceCountry": { + "@value": "subitem_1599711813532" + }, + "conferenceSponsor": { + "@value": "subitem_1599711660052.subitem_1599711680082", + "@attributes": { + "xml:lang": "subitem_1599711660052.subitem_1599711686511" + } + }, + "conferenceSequence": { + "@value": "subitem_1599711655652" + }, + "model_id": "item_1617187187528" + }, + "type": { + "@value": "resourcetype", + "@attributes": { + "rdf:resource": "resourceuri" + }, + "model_id": "item_1617258105262" + }, + "versiontype": { + "@value": "subitem_1522305645492", + "@attributes": { + "rdf:resource": "subitem_1600292170262" + }, + "model_id": "item_1617265215918" + }, + "contributor": { + "givenName": { + "@value": "givenNames.givenName", + "@attributes": { + "xml:lang": "givenNames.givenNameLang" + } + }, + "familyName": { + "@value": "familyNames.familyName", + "@attributes": { + "xml:lang": "familyNames.familyNameLang" + } + }, + "@attributes": { + "contributorType": "contributorType" + }, + "affiliation": { + "nameIdentifier": { + "@value": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationNameIdentifier", + "@attributes": { + "nameIdentifierURI": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationURI", + "nameIdentifierScheme": "contributorAffiliations.contributorAffiliationNameIdentifiers.contributorAffiliationScheme" + } + }, + "affiliationName": { + "@value": "contributorAffiliations.contributorAffiliationNames.contributorAffiliationName", + "@attributes": { + "xml:lang": "contributorAffiliations.contributorAffiliationNames.contributorAffiliationNameLang" + } + } + }, + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "contributorName": { + "@value": "contributorNames.contributorName", + "@attributes": { + "xml:lang": "contributorNames.lang" + } + }, + "contributorAlternative": { + "@value": "contributorAlternatives.contributorAlternative", + "@attributes": { + "xml:lang": "contributorAlternatives.contributorAlternativeLang" + } + }, + "model_id": "item_1617349709064" + }, + "version": { + "@value": "subitem_1523263171732", + "model_id": "item_1617349808926" + }, + "apc": { + "@value": "subitem_1523260933860", + "model_id": "item_1617351524846" + }, + "relation": { + "@attributes": { + "relationType": "subitem_1522306207484" + }, + "relatedTitle": { + "@value": "subitem_1523320863692.subitem_1523320909613", + "@attributes": { + "xml:lang": "subitem_1523320863692.subitem_1523320867455" + } + }, + "relatedIdentifier": { + "@value": "subitem_1522306287251.subitem_1522306436033", + "@attributes": { + "identifierType": "subitem_1522306287251.subitem_1522306382014" + } + }, + "model_id": "item_1617353299429" + }, + "file": { + "URI": { + "@value": "url.url", + "@attributes": { + "label": "url.label", + "objectType": "url.objectType" + } + }, + "date": { + "@value": "fileDate.fileDateValue", + "@attributes": { + "dateType": "fileDate.fileDateType" + } + }, + "extent": { + "@value": "filesize.value" + }, + "version": { + "@value": "version" + }, + "mimeType": { + "@value": "format" + }, + "model_id": "item_1617605131499" + }, + "rightsHolder": { + "nameIdentifier": { + "@value": "nameIdentifiers.nameIdentifier", + "@attributes": { + "nameIdentifierURI": "nameIdentifiers.nameIdentifierURI", + "nameIdentifierScheme": "nameIdentifiers.nameIdentifierScheme" + } + }, + "rightsHolderName": { + "@value": "rightHolderNames.rightHolderName", + "@attributes": { + "xml:lang": "rightHolderNames.rightHolderLanguage" + } + }, + "model_id": "item_1617610673286" + }, + "degreeGrantor": { + "nameIdentifier": { + "@value": "subitem_1551256015892.subitem_1551256027296", + "@attributes": { + "nameIdentifierScheme": "subitem_1551256015892.subitem_1551256029891" + } + }, + "degreeGrantorName": { + "@value": "subitem_1551256037922.subitem_1551256042287", + "@attributes": { + "xml:lang": "subitem_1551256037922.subitem_1551256047619" + } + }, + "model_id": "item_1617944105607" + }, + "system_identifier": [ + { + "system_identifier": { + "@value": "subitem_systemidt_identifier", + "@attributes": { + "identifierType": "subitem_systemidt_identifier_type" + }, + "model_id": "system_identifier_doi" + } + }, + { + "system_identifier": { + "@value": "subitem_systemidt_identifier", + "@attributes": { + "identifierType": "subitem_systemidt_identifier_type" + }, + "model_id": "system_identifier_hdl" + } + }, + { + "system_identifier": { + "@value": "subitem_systemidt_identifier", + "@attributes": { + "identifierType": "subitem_systemidt_identifier_type" + }, + "model_id": "system_identifier_uri" + } + } + ] +} \ No newline at end of file diff --git a/modules/weko-workspace/tests/test_api.py b/modules/weko-workspace/tests/test_api.py index b671aa0ac5..c49bfdeb5b 100644 --- a/modules/weko-workspace/tests/test_api.py +++ b/modules/weko-workspace/tests/test_api.py @@ -1,9 +1,225 @@ -from flask_login.utils import login_user import pytest from mock import patch +from requests.cookies import RequestsCookieJar +from requests.exceptions import RequestException from requests.models import Response -from weko_workspace.api import CiNiiURL, JALCURL, DATACITEURL +from weko_workspace.api import JamasURL, CiNiiURL, JALCURL, DATACITEURL + + +# class JamasURL: +class TestJamasURL: +# def __init__(self, doi, timeout=None, http_proxy=None, https_proxy=None): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test__init__ -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test__init__(self): + # not doi + with pytest.raises(ValueError) as e: + jamas = JamasURL(None) + assert str(e) == "DOI is required." + + # not exist timeout,http_proxy,https_proxy + jamas = JamasURL("10.5109/16119") + assert jamas._doi == "10.5109/16119" + assert jamas._timeout == 5 + assert jamas._proxy == {"http": "", "https": ""} + + # exist timeout,http_proxy,https_proxy + jamas = JamasURL("10.5109/16119", timeout=10, http_proxy="test_http_proxy", https_proxy="test_https_proxy") + assert jamas._doi == "10.5109/16119" + assert jamas._timeout == 10 + assert jamas._proxy == {"http": "test_http_proxy", "https": "test_https_proxy"} + + +# def _create_endpoint(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_create_endpoint -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_create_endpoint(self): + jamas = JamasURL("10.5109/16119") + result = jamas._create_endpoint() + assert result == "api/sru?operation=searchRetrieve&version=1.2&startRecord=1&recordPacking=xml&recordSchema=pam&query=prism.doi%3D10.5109%2F16119" + + +# def _create_url(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_create_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_create_url(self, app): + jamas = JamasURL("10.5109/16119") + result = jamas._create_url() + assert result == "https://search.jamas.or.jp/api/sru?operation=searchRetrieve&version=1.2&startRecord=1&recordPacking=xml&recordSchema=pam&query=prism.doi%3D10.5109%2F16119" + + +# def url(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_url(self, app): + jamas = JamasURL("10.5109/16119") + result = jamas.url + assert result == "https://search.jamas.or.jp/api/sru?operation=searchRetrieve&version=1.2&startRecord=1&recordPacking=xml&recordSchema=pam&query=prism.doi%3D10.5109%2F16119" + + +# def _do_http_request(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_do_http_request -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_do_http_request(self, app, mocker): + mock_get = mocker.patch('weko_workspace.api.requests.get') + jamas = JamasURL("10.5109/16119", timeout=5, http_proxy="test_http_proxy", https_proxy="test_https_proxy") + jamas._do_http_request() + mock_get.assert_called_once_with( + "https://search.jamas.or.jp/api/sru?operation=searchRetrieve&version=1.2&startRecord=1&recordPacking=xml&recordSchema=pam&query=prism.doi%3D10.5109%2F16119", + cookies=None, + timeout=5, + proxies={"http": "test_http_proxy", "https": "test_https_proxy"} + ) + + +# def _login(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_login -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_login(self, app): + mock_response = Response() + mock_response.status_code = 200 + jar = RequestsCookieJar() + jar.set('JSESSIONID', 'test_session_id', domain='search.jamas.or.jp', path='/') + mock_response.cookies = jar + + # login success + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + result = jamas._login() + assert result is True + assert jamas._cookie == {'JSESSIONID': 'test_session_id'} + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/login', + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # login ng + mock_response._content = b'login ng' + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + result = jamas._login() + assert result is False + assert jamas._cookie is None + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/login', + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # status_code is not 200 + mock_response.status_code = 400 + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + result = jamas._login() + assert result is False + assert jamas._cookie is None + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/login', + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # RequestException + with patch('weko_workspace.api.requests.post', side_effect=RequestException("request error")) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + result = jamas._login() + assert result is False + assert jamas._cookie is None + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/login', + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + +# def _logout(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_logout -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_logout(self, app): + mock_response = Response() + mock_response.status_code = 200 + mock_response._content = b'logout ok' + + # logout success + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + jamas._cookie = {'JSESSIONID': 'test_session_id'} + result = jamas._logout() + assert result is True + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/logout', + cookies={'JSESSIONID': 'test_session_id'}, + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # logout ng + mock_response._content = b'logout ng' + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + jamas._cookie = {'JSESSIONID': 'test_session_id'} + result = jamas._logout() + assert result is False + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/logout', + cookies={'JSESSIONID': 'test_session_id'}, + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # status_code is not 200 + mock_response.status_code = 400 + with patch('weko_workspace.api.requests.post', return_value=mock_response) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + jamas._cookie = {'JSESSIONID': 'test_session_id'} + result = jamas._logout() + assert result is False + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/logout', + cookies={'JSESSIONID': 'test_session_id'}, + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + # RequestException + with patch('weko_workspace.api.requests.post', side_effect=RequestException("request error")) as mock_post: + jamas = JamasURL("10.5109/16119", http_proxy="test_http_proxy", https_proxy="test_https_proxy") + jamas._cookie = {'JSESSIONID': 'test_session_id'} + result = jamas._logout() + assert result is False + mock_post.assert_called_once_with( + 'https://search.jamas.or.jp/api/logout', + cookies={'JSESSIONID': 'test_session_id'}, + timeout=5, + proxies={'http': 'test_http_proxy', 'https': 'test_https_proxy'}, + ) + + +# def get_data(self): +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJamasURL::test_get_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp + def test_get_data(self, app): + jamas = JamasURL("10.5109/16119") + res = Response() + res._content = b'test response' + res.status_code = 200 + + with patch('weko_workspace.api.JamasURL._login', return_value=True): + with patch('weko_workspace.api.JamasURL._logout'): + # status_code is 200 + with patch('weko_workspace.api.JamasURL._do_http_request', return_value=res): + result = jamas.get_data() + assert result == {'response': 'test response', 'error': ''} + + # status_code is not 200 + res.status_code = 400 + with patch('weko_workspace.api.JamasURL._do_http_request', return_value=res): + result = jamas.get_data() + assert result == {'response': '', 'error': ''} + + # raise Exception + with patch('weko_workspace.api.JamasURL._do_http_request', side_effect=Exception("request error")): + result = jamas.get_data() + assert result == {'response': '', 'error': 'request error'} + + with patch('weko_workspace.api.JamasURL._login', return_value=False): + # login failed + result = jamas.get_data() + assert result == {'response': '', 'error': 'Login to Jamas failed.'} # class CiNiiURL: @@ -18,11 +234,13 @@ def test__init__(self): # not exist timeout,http_proxy,https_proxy cini = CiNiiURL("10.5109/16119") + assert cini._doi == "10.5109/16119" assert cini._timeout == 5 assert cini._proxy == {"http":"","https":""} # exist timeout,http_proxy,https_proxy cini = CiNiiURL("10.5109/16119",timeout=10,http_proxy="test_http_proxy",https_proxy="test_https_proxy") + assert cini._doi == "10.5109/16119" assert cini._timeout == 10 assert cini._proxy == {"http":"test_http_proxy","https":"test_https_proxy"} @@ -32,12 +250,12 @@ def test__init__(self): def test_create_endpoint(self): cini = CiNiiURL("10.5109/16119") result = cini._create_endpoint() - assert result is not None + assert result == 'doi=10.5109/16119&format=json' # def _create_url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestCiNiiURL::test_create_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_create_url(self,mocker): + def test_create_url(self): cini = CiNiiURL("10.5109/16119") result = cini._create_url() assert result == "https://cir.nii.ac.jp/opensearch/all?doi=10.5109/16119&format=json" @@ -45,7 +263,7 @@ def test_create_url(self,mocker): # def url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestCiNiiURL::test_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_url(self,mocker): + def test_url(self): cini = CiNiiURL("10.5109/16119") result = cini.url assert result == "https://cir.nii.ac.jp/opensearch/all?doi=10.5109/16119&format=json" @@ -57,13 +275,16 @@ def test_do_http_request(self,mocker): mock_get = mocker.patch("weko_workspace.api.requests.get") cini = CiNiiURL("10.5109/16119",timeout=5,http_proxy="test_http_proxy",https_proxy="test_https_proxy") cini._do_http_request() - mock_get.assert_called_with("https://cir.nii.ac.jp/opensearch/all?doi=10.5109/16119&format=json", - timeout=5,proxies={"http":"test_http_proxy","https":"test_https_proxy"}) + mock_get.assert_called_once_with( + "https://cir.nii.ac.jp/opensearch/all?doi=10.5109/16119&format=json", + timeout=5, + proxies={"http": "test_http_proxy", "https": "test_https_proxy"} + ) # def get_data(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestCiNiiURL::test_get_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_get_data(self): + def test_get_data(self, app): cini = CiNiiURL("10.5109/16119") res = Response() res._content = b'{"key":"test response"}' @@ -96,11 +317,13 @@ def test__init__(self): # not exist timeout,http_proxy,https_proxy jalc = JALCURL("10.5109/16119") + assert jalc._doi == "10.5109/16119" assert jalc._timeout == 5 assert jalc._proxy == {"http":"","https":""} # exist timeout,http_proxy,https_proxy jalc = JALCURL("10.5109/16119",timeout=10,http_proxy="test_http_proxy",https_proxy="test_https_proxy") + assert jalc._doi == "10.5109/16119" assert jalc._timeout == 10 assert jalc._proxy == {"http":"test_http_proxy","https":"test_https_proxy"} @@ -110,12 +333,12 @@ def test__init__(self): def test_create_endpoint(self): jalc = JALCURL("10.5109/16119") result = jalc._create_endpoint() - assert result is not None + assert result == "10.5109/16119" # def _create_url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJALCURL::test_create_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_create_url(self,mocker): + def test_create_url(self): jalc = JALCURL("10.5109/16119") result = jalc._create_url() assert result == "https://api.japanlinkcenter.org/dois/10.5109/16119" @@ -123,7 +346,7 @@ def test_create_url(self,mocker): # def url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJALCURL::test_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_url(self,mocker): + def test_url(self): jalc = JALCURL("10.5109/16119") result = jalc.url assert result == "https://api.japanlinkcenter.org/dois/10.5109/16119" @@ -135,13 +358,16 @@ def test_do_http_request(self,mocker): mock_get = mocker.patch("weko_workspace.api.requests.get") jalc = JALCURL("10.5109/16119",timeout=5,http_proxy="test_http_proxy",https_proxy="test_https_proxy") jalc._do_http_request() - mock_get.assert_called_with("https://api.japanlinkcenter.org/dois/10.5109/16119", - timeout=5,proxies={"http":"test_http_proxy","https":"test_https_proxy"}) + mock_get.assert_called_once_with( + "https://api.japanlinkcenter.org/dois/10.5109/16119", + timeout=5, + proxies={"http": "test_http_proxy", "https": "test_https_proxy"} + ) # def get_data(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestJALCURL::test_get_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_get_data(self): + def test_get_data(self, app): jalc = JALCURL("10.5109/16119") res = Response() res._content = b'{"key":"test response"}' @@ -174,11 +400,13 @@ def test__init__(self): # not exist timeout,http_proxy,https_proxy datacite = DATACITEURL("10.5109/16119") + assert datacite._doi == "10.5109/16119" assert datacite._timeout == 5 assert datacite._proxy == {"http":"","https":""} # exist timeout,http_proxy,https_proxy datacite = DATACITEURL("10.5109/16119",timeout=10,http_proxy="test_http_proxy",https_proxy="test_https_proxy") + assert datacite._doi == "10.5109/16119" assert datacite._timeout == 10 assert datacite._proxy == {"http":"test_http_proxy","https":"test_https_proxy"} @@ -188,12 +416,12 @@ def test__init__(self): def test_create_endpoint(self): datacite = DATACITEURL("10.5109/16119") result = datacite._create_endpoint() - assert result is not None + assert result == "10.5109/16119" # def _create_url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestDATACITEURL::test_create_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_create_url(self,mocker): + def test_create_url(self): datacite = DATACITEURL("10.5109/16119") result = datacite._create_url() assert result == "https://api.datacite.org/dois/10.5109/16119" @@ -201,7 +429,7 @@ def test_create_url(self,mocker): # def url(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestDATACITEURL::test_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_url(self,mocker): + def test_url(self): datacite = DATACITEURL("10.5109/16119") result = datacite.url assert result == "https://api.datacite.org/dois/10.5109/16119" @@ -213,13 +441,16 @@ def test_do_http_request(self,mocker): mock_get = mocker.patch("weko_workspace.api.requests.get") datacite = DATACITEURL("10.5109/16119",timeout=5,http_proxy="test_http_proxy",https_proxy="test_https_proxy") datacite._do_http_request() - mock_get.assert_called_with("https://api.datacite.org/dois/10.5109/16119", - timeout=5,proxies={"http":"test_http_proxy","https":"test_https_proxy"}) + mock_get.assert_called_once_with( + "https://api.datacite.org/dois/10.5109/16119", + timeout=5, + proxies={"http": "test_http_proxy", "https": "test_https_proxy"} + ) # def get_data(self): # .tox/c1/bin/pytest --cov=weko_workspace tests/test_api.py::TestDATACITEURL::test_get_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp - def test_get_data(self): + def test_get_data(self, app): datacite = DATACITEURL("10.5109/16119") res = Response() res._content = b'{"key":"test response"}' diff --git a/modules/weko-workspace/tests/test_defaultfilters.py b/modules/weko-workspace/tests/test_defaultfilters.py index c185b1efdd..2df5eb9522 100644 --- a/modules/weko-workspace/tests/test_defaultfilters.py +++ b/modules/weko-workspace/tests/test_defaultfilters.py @@ -21,12 +21,12 @@ """Module tests.""" import pytest from flask_babelex import gettext as _ -from weko_workspace.utils import * from weko_workspace.defaultfilters import merge_default_filters from weko_workspace.config import WEKO_WORKSPACE_DEFAULT_FILTERS as DEFAULT_FILTERS # ===========================def merge_default_filters():===================================== +# .tox/c1/bin/pytest --cov=weko_workspace tests/test_defaultfilters.py::test_merge_default_filters -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workspace/.tox/c1/tmp @pytest.mark.parametrize('default_con, expected_response', [ # シナリオ1: default_conが空の場合、デフォルトテンプレートを返す ( @@ -41,8 +41,8 @@ }, { **{key: dict(value) for key, value in DEFAULT_FILTERS.items()}, - "favorite": {**DEFAULT_FILTERS["favorite"], "default": "あり"}, - "peer_review": {**DEFAULT_FILTERS["peer_review"], "default": "なし"} + "favorite": {**DEFAULT_FILTERS["favorite"], "default": "Yes"}, + "peer_review": {**DEFAULT_FILTERS["peer_review"], "default": "No"} } ), # シナリオ3: 複数選択フィールドを更新 @@ -69,11 +69,11 @@ }, { **{key: dict(value) for key, value in DEFAULT_FILTERS.items()}, - "peer_review": {**DEFAULT_FILTERS["peer_review"], "default": "あり"}, - "related_to_paper": {**DEFAULT_FILTERS["related_to_paper"], "default": "なし"}, + "peer_review": {**DEFAULT_FILTERS["peer_review"], "default": "Yes"}, + "related_to_paper": {**DEFAULT_FILTERS["related_to_paper"], "default": "No"}, "related_to_data": {**DEFAULT_FILTERS["related_to_data"], "default": None}, - "file_present": {**DEFAULT_FILTERS["file_present"], "default": "あり"}, - "favorite": {**DEFAULT_FILTERS["favorite"], "default": "なし"} + "file_present": {**DEFAULT_FILTERS["file_present"], "default": "Yes"}, + "favorite": {**DEFAULT_FILTERS["favorite"], "default": "No"} } ), # シナリオ5: default_conに存在しないキーを含む場合、無視する @@ -84,7 +84,7 @@ }, { **{key: dict(value) for key, value in DEFAULT_FILTERS.items()}, - "favorite": {**DEFAULT_FILTERS["favorite"], "default": "あり"} + "favorite": {**DEFAULT_FILTERS["favorite"], "default": "Yes"} } ), # シナリオ6: None値を含む場合 @@ -100,7 +100,7 @@ } ), ]) -def test_merge_default_filters(default_con, expected_response): +def test_merge_default_filters(app, default_con, expected_response): result = merge_default_filters(default_con) assert result == expected_response diff --git a/modules/weko-workspace/tests/test_utils.py b/modules/weko-workspace/tests/test_utils.py index 6e0fc586cc..92769a7a1d 100644 --- a/modules/weko-workspace/tests/test_utils.py +++ b/modules/weko-workspace/tests/test_utils.py @@ -21,14 +21,15 @@ """Module tests.""" import pytest -from mock import patch +from mock import patch, MagicMock from datetime import datetime, timedelta, timezone from flask_babelex import gettext as _ from flask_login.utils import login_user +from invenio_cache import current_cache from weko_user_profiles import UserProfile import json -from unittest.mock import Mock, patch +from unittest.mock import Mock, patch, call import pytest from sqlalchemy.exc import SQLAlchemyError from flask_login import login_user @@ -39,6 +40,7 @@ from weko_workspace.config import WEKO_WORKSPACE_DEFAULT_FILTERS # ===========================def get_workspace_filterCon():===================================== +# .tox/c1/bin/pytest tests/test_utils.py::test_get_workspace_filterCon -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp # ワークスペースのフィルター条件を取得する関数のテスト @pytest.mark.parametrize('users_index, mock_setup, expected_response', [ (0, @@ -74,85 +76,86 @@ def test_get_workspace_filterCon(users, users_index, mock_setup, expected_respon # ===========================def get_es_itemlist():===================================== -# Elasticsearchからアイテムリストを取得する関数のテスト -@pytest.mark.parametrize('mock_setup, expected_response, expected_exception', [ +# .tox/c1/bin/pytest tests/test_utils.py::test_get_es_itemlist -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('mock_responses, mock_exception, expected_response', [ ( - { - 'first_response': Mock(status_code=200, json=lambda: {"hits": {"total": 2}}), - 'second_response': Mock(status_code=200, json=lambda: { - "hits": { - "hits": [ - {"id": "1", "metadata": {"title": ["Title 1"]}}, - {"id": "2", "metadata": {"title": ["Title 2"]}} - ] - } - }) - }, - { - "hits": { - "hits": [ - {"id": "1", "metadata": {"title": ["Title 1"]}}, - {"id": "2", "metadata": {"title": ["Title 2"]}} - ] - } - }, - None + [ + {'hits': {'hits': [{'id': str(i), 'metadata': {'title': [f'Title {i}']}} for i in range(1, 4)]}}, + ], + None, + [{'id': str(i), 'metadata': {'title': [f'Title {i}']}} for i in range(1, 4)] ), ( - { - 'first_response': Mock(status_code=404, json=lambda: {"error": "Not Found"}, raise_for_status=lambda: (_ for _ in ()).throw(requests.exceptions.HTTPError("404 Not Found"))), - 'second_response': None - }, + [ + {'hits': {'hits': []}}, + ], None, - requests.exceptions.RequestException + [] ), ( - { - 'first_response': Mock(status_code=200, json=lambda: {"hits": {"total": 2}}), - 'second_response': Mock(status_code=500, json=lambda: {"error": "Server Error"}, raise_for_status=lambda: (_ for _ in ()).throw(requests.exceptions.HTTPError("500 Server Error"))) - }, + [ + {} + ], None, - requests.exceptions.RequestException + [] ), ( - { - 'first_response': Mock(status_code=200, json=lambda: {"hits": {"total": 2}}), - 'second_response': Mock(status_code=200, text="invalid json", json=lambda: (_ for _ in ()).throw(json.JSONDecodeError("Invalid JSON", "invalid json", 0))) - }, + [ + {'hits': {'hits': [{'id': str(i), 'metadata': {'title': [f'Title {i}']}} for i in range(1, 10001)]}}, + {'hits': {'hits': [{'id': str(i), 'metadata': {'title': [f'Title {i}']}} for i in range(10001, 10101)]}} + ], None, - json.JSONDecodeError + [{'id': str(i), 'metadata': {'title': [f'Title {i}']}} for i in range(1, 10101)] ), ( - { - 'first_response': Mock(status_code=200, json=lambda: {"not_hits": "invalid data"}), - 'second_response': None - }, - None, - KeyError + [], + TransportError(500, 'Server Error'), + None ), + ( + [], + Exception("Unexpected error"), + None + ) ]) -def test_get_es_itemlist(mock_setup, expected_response, expected_exception, app): - with patch('requests.get') as mock_get: - mock_get.side_effect = ( - [mock_setup['first_response'], mock_setup['second_response']] - if mock_setup['second_response'] is not None - else [mock_setup['first_response']] - ) - with app.test_request_context(base_url='http://test.example.com'): - result = get_es_itemlist() - assert result == expected_response - if expected_response is not None: - mock_get.assert_called_with( - 'http://test.example.com/api/workspace/search?size=2', - headers={"Accept": "application/json"} - ) - if mock_setup['second_response'] is not None: - assert mock_get.call_count == 2 +def test_get_es_itemlist(app, mock_responses, mock_exception, expected_response): + with patch('weko_workspace.utils.RecordsSearch') as mock_search_cls: + mock_search = MagicMock() + + # setup mock responses or exception + if mock_exception: + mock_search.execute.side_effect = mock_exception else: - assert mock_get.call_count == 1 + side_effects = [] + for i in range(len(mock_responses)): + if i == 0: + mock_execute = MagicMock() + mock_execute.to_dict.return_value = mock_responses[i] + mock_search.execute.return_value = mock_execute + else: + mock_execute = MagicMock() + mock_execute.to_dict.return_value = mock_responses[i] + side_effects.append(mock_execute) + if side_effects: + mock_extra = MagicMock() + mock_extra.execute.side_effect = side_effects + mock_search.extra.return_value = mock_extra + + # Mock the RecordsSearch class + mock_search_cls.return_value = mock_search + mock_search.with_preference_param.return_value = mock_search + mock_search.params.return_value = mock_search + mock_search.filter.return_value = mock_search + mock_search.query.return_value = mock_search + mock_search.sort.return_value = mock_search + + result = get_es_itemlist() + assert result == expected_response + # ===========================def get_workspace_status_management():===================================== # ワークスペースのステータス管理情報を取得する関数のテスト +# .tox/c1/bin/pytest tests/test_utils.py::test_get_workspace_status_management -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('recid, mock_setup, expected_response', [ ( "recid_0", @@ -193,6 +196,7 @@ def test_get_workspace_status_management(recid, mock_setup, expected_response, a # ===========================def get_accessCnt_downloadCnt():===================================== # アイテムのアクセス数とダウンロード数を取得する関数のテスト +# .tox/c1/bin/pytest tests/test_utils.py::test_get_accessCnt_downloadCnt -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('recid, mock_setup, expected_response', [ ( "valid_recid", @@ -259,7 +263,7 @@ def test_get_accessCnt_downloadCnt(recid, mock_setup, expected_response, app): # ===========================def get_item_status():===================================== # .tox/c1/bin/pytest tests/test_utils.py::test_get_item_status -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('recid, expected_response', [ - ('1', "Embargo OA"), + ('1', ['OA', 'Embargo OA']), ('2', 'Metadata Registered'), ('3', 'Unlinked'), ]) @@ -269,38 +273,38 @@ def test_get_item_status(app, oa_status, recid, expected_response): dt = datetime.now(timezone.utc) file_info = {'date': [{'dateValue': dt.strftime('%Y-%m-%d')}]} result = get_item_status(recid, file_info) - assert result == expected_response + assert result == expected_response[0] dt = datetime.now(timezone.utc) + timedelta(days=1) file_info = {'date': [{'dateValue': dt.strftime('%Y-%m-%d')}]} result = get_item_status(recid, file_info) - assert result == expected_response + assert result == expected_response[1] file_info = {'date': [{}]} result = get_item_status(recid, file_info) - assert result == expected_response + assert result == expected_response[0] file_info = {} result = get_item_status(recid, file_info) - assert result == expected_response + assert result == expected_response[0] else: result = get_item_status(recid, file_info) assert result == expected_response # ===========================def get_userNm_affiliation():===================================== -# TODO ユーザー名と所属情報を取得する関数のテスト(未完成) +# .tox/c1/bin/pytest tests/test_utils.py::test_get_userNm_affiliation -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('mock_setup, expected_response', [ ( { 'username': "test_user" }, - ("test_user", "ivis-testdata") + "test_user" ), ( { 'username': None }, - ("contributor@test.org", "ivis-testdata") + "contributor@test.org" ), ( { @@ -330,6 +334,7 @@ def test_get_userNm_affiliation(mock_setup, expected_response, app, users): # ===========================def insert_workspace_status():===================================== # ワークスペースのステータスを挿入する関数のテスト +# .tox/c1/bin/pytest tests/test_utils.py::test_insert_workspace_status -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('user_id, recid, is_favorited, is_read, mock_setup, expected_exception', [ ( "1", @@ -383,6 +388,7 @@ def test_insert_workspace_status(user_id, recid, is_favorited, is_read, mock_set # ===========================def update_workspace_status():===================================== # ワークスペースのステータスを更新する関数のテスト +# .tox/c1/bin/pytest tests/test_utils.py::test_update_workspace_status -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('user_id, recid, is_favorited, is_read, mock_setup, expected_favorited, expected_read', [ ( 1, @@ -457,6 +463,7 @@ def test_update_workspace_status(user_id, recid, is_favorited, is_read, mock_set # ===========================def extract_metadata_info():===================================== # メタデータからファイルリストとピアレビュー情報を抽出する関数のテスト +# .tox/c1/bin/pytest tests/test_utils.py::test_extract_metadata_info -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp @pytest.mark.parametrize('item_metadata, expected_filelist, expected_peer_reviewed', [ ( { @@ -554,4 +561,2087 @@ def test_update_workspace_status(user_id, recid, is_favorited, is_read, mock_set def test_extract_metadata_info(item_metadata, expected_filelist, expected_peer_reviewed): result_filelist, result_peer_reviewed = extract_metadata_info(item_metadata) assert result_filelist == expected_filelist - assert result_peer_reviewed == expected_peer_reviewed \ No newline at end of file + assert result_peer_reviewed == expected_peer_reviewed + +# .tox/c1/bin/pytest tests/test_utils.py::test_changeLang -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('lang, expected_conditions', [ + ('en', { + 'resource_type': { + 'label': 'Resource Type', + 'options': ['conference paper', 'data paper'] + }, + 'peer_review': { + 'label': 'Peer Review', + 'options': ['Yes', 'No'], + }, + 'related_to_paper': { + 'label': 'Related To Paper', + 'options': ['Yes', 'No'], + }, + 'related_to_data': { + 'label': 'Related To Data', + 'options': ['Yes', 'No'], + }, + 'funder_name': { + 'label': 'Funding Reference - Funder Name', + 'options': [], + }, + 'award_title': { + 'label': 'Funding Reference - Award Title', + 'options': [], + }, + 'file_present': { + 'label': 'File', + 'options': ['Yes', 'No'], + }, + 'favorite': { + 'label': 'Favorite', + 'options': ['Yes', 'No'], + }, + 'no_label': { + 'title': 'Resource Type', + 'options': ['conference paper', 'data paper'], + }, + 'no_mapping': { + 'label': 'No Mapping', + 'options': ['Option 1', 'Option 2'], + } + }), + ('ja', { + 'resource_type': { + 'label': 'リソースタイプ', + 'options': ['conference paper', 'data paper'], + }, + 'peer_review': { + 'label': '査読', + 'options': ['Yes', 'No'], + }, + 'related_to_paper': { + 'label': '論文への関連', + 'options': ['Yes', 'No'], + }, + 'related_to_data': { + 'label': '根拠データへの関連', + 'options': ['Yes', 'No'], + }, + 'funder_name': { + 'label': '資金別情報 - 助成機関名', + 'options': [], + }, + 'award_title': { + 'label': '資金別情報 - 研究課題名', + 'options': [], + }, + 'file_present': { + 'label': '本文ファイル', + 'options': ['Yes', 'No'], + }, + 'favorite': { + 'label': 'お気に入り', + 'options': ['Yes', 'No'], + }, + 'no_label': { + 'title': 'Resource Type', + 'options': ['conference paper', 'data paper'], + }, + 'no_mapping': { + 'label': 'No Mapping', + 'options': ['Option 1', 'Option 2'], + } + }), +]) +def test_changeLang(lang, expected_conditions): + default_conditions = { + 'resource_type': { + 'label': 'Resource Type', + 'options': ['conference paper', 'data paper'] + }, + 'peer_review': { + 'label': 'Peer Review', + 'options': ['Yes', 'No'], + }, + 'related_to_paper': { + 'label': 'Related To Paper', + 'options': ['Yes', 'No'], + }, + 'related_to_data': { + 'label': 'Related To Data', + 'options': ['Yes', 'No'], + }, + 'funder_name': { + 'label': 'Funding Reference - Funder Name', + 'options': [], + }, + 'award_title': { + 'label': 'Funding Reference - Award Title', + 'options': [], + }, + 'file_present': { + 'label': 'File', + 'options': ['Yes', 'No'], + }, + 'favorite': { + 'label': 'Favorite', + 'options': ['Yes', 'No'], + }, + 'no_label': { + 'title': 'Resource Type', + 'options': ['conference paper', 'data paper'], + }, + 'no_mapping': { + 'label': 'No Mapping', + 'options': ['Option 1', 'Option 2'], + } + } + result = changeLang(lang, default_conditions) + assert result == expected_conditions + + result = changeLang(lang, {}) + assert result == {} + +# .tox/c1/bin/pytest tests/test_utils.py::test_changeMsg -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('lang, expected_messages', [ + ( + 'en', + [ + 'Successfully saved default conditions.', + 'Successfully reset default conditions.', + 'No default conditions found to reset.' + ] + ), + ( + 'ja', + [ + 'デフォルト条件の保存に成功しました。', + 'デフォルト条件のリセットに成功しました。', + 'リセットするデフォルト条件が見つかりません。' + ] + ) +]) +def test_changeMsg(lang, expected_messages): + message = 'Successfully saved default conditions.' + result = changeMsg(lang, 1, None, message) + assert result == expected_messages[0] + + message = 'Successfully reset default conditions.' + result = changeMsg(lang, 2, True, message) + assert result == expected_messages[1] + + message = 'No default conditions found to reset.' + result = changeMsg(lang, 2, False, message) + assert result == expected_messages[2] + + message = 'No change message.' + result = changeMsg(lang, 3, None, message) + assert result == message + +# .tox/c1/bin/pytest tests/test_utils.py::test_convert_jamas_xml_data_to_dictionary -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_convert_jamas_xml_data_to_dictionary(app): + with open('tests/data/test_convert_xml_to_dict.xml', 'r') as file: + api_data = file.read() + expected_result = { + 'response': { + "identifier": 'test-identifier', + "title": 'Test Title', + "creator": ['Test Creator 1', 'Test Creator 2'], + "type": ['Test Type 1', 'Test Type 2'], + "language": 'en', + "publisher": 'Test Publisher', + "description": ['Test Description 1', 'Test Description 2'], + "organization": ['Test Organization 1', 'Test Organization 2'], + "publicationName": 'Test Publication Name', + "issn": '1234-5678', + "eIssn": '8765-4321', + "isbn": ['978-3-16-148410-0', '978-1-23-456789-0'], + "volume": '1', + "number": '2', + "startingPage": '10', + "pageRange": '10-20', + "publicationDate": ['2023-01-01', '2023-02-01'], + "keyword": ['Test Keyword 1', 'Test Keyword 2'], + "doi": '10.1234/test-doi', + "postDate": ['2023-01-15', '2023-02-15'], + }, + 'error': '' + } + result = convert_jamas_xml_data_to_dictionary(api_data) + assert result == expected_result + + with patch('lxml.etree.QName') as mock_qname: + mock_qname.side_effect = Exception("XML parsing error") + result = convert_jamas_xml_data_to_dictionary(api_data) + assert result['error'] == 'XML parsing error' + assert result['response'] == {} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_record_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_record_data(app, mocker_itemtype): + mock_get_data = { + 'response': '', + 'error': '' + } + mock_dictionary = { + 'response': { + 'identifier': 'test-identifier' + }, + 'error': '' + } + with patch('weko_workspace.utils.JamasURL.get_data', return_value=mock_get_data): + with patch('weko_workspace.utils.convert_jamas_xml_data_to_dictionary', return_value=mock_dictionary): + with patch('weko_workspace.utils.get_jamas_data_by_key', return_value={}): + with patch('weko_workspace.utils.get_jamas_autofill_item'): + with patch('weko_workspace.utils.get_autofill_key_tree'): + with patch('weko_items_autofill.utils.sort_by_item_type_order'): + with patch('weko_workspace.utils.build_record_model', return_value=[1]): + current_cache.delete('jamas_data10.1234/test-doi1') + result = get_jamas_record_data('10.1234/test-doi', 1) + assert result == [1] + + item_type = Mock(item_type='test_item_type', form=None) + with patch('weko_workspace.utils.ItemTypes.get_by_id', return_value=item_type): + current_cache.delete('jamas_data10.1234/test-doi1') + result = get_jamas_record_data('10.1234/test-doi', 1) + assert result == [] + + with patch('weko_workspace.utils.ItemTypes.get_by_id', return_value=None): + current_cache.delete('jamas_data10.1234/test-doi1') + result = get_jamas_record_data('10.1234/test-doi', 1) + assert result == [] + + # Test with error in convert_jamas_xml_data_to_dictionary + mock_dictionary = { + 'response': {}, + 'error': 'test error message' + } + with patch('weko_workspace.utils.JamasURL.get_data', return_value=mock_get_data): + with patch('weko_workspace.utils.convert_jamas_xml_data_to_dictionary', return_value=mock_dictionary): + current_cache.delete('jamas_data10.1234/test-doi1') + result = get_jamas_record_data('10.1234/test-doi', 1) + assert result == [] + + # Test with error in get_data + mock_get_data = { + 'response': '', + 'error': 'test error message' + } + with patch('weko_workspace.utils.JamasURL.get_data', return_value=mock_get_data): + current_cache.delete('jamas_data10.1234/test-doi1') + result = get_jamas_record_data('10.1234/test-doi', 1) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_autofill_item -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_autofill_item(app): + mock_item = { + 'title': { + '@value': 'subitem_1551255647225', + '@attributes': { 'xml:lang': 'subitem_1551255648112' }, + 'model_id': 'item_1617186331708' + }, + 'sourceTitle': { + '@value': 'subitem_1522650091861', + '@attributes': { 'xml:lang': 'subitem_1522650068558' }, + 'model_id': 'item_1617186941041' + }, + 'sourceIdentifier': { + '@value': 'subitem_1522646572813', + '@attributes': { 'identifierType': 'subitem_1522646500366' }, + 'model_id': 'item_1617186920753' + }, + 'no_required': { + '@value': 'subitem_1234567890123', + '@attributes': { 'identifierType': 'subitem_9876543210987' }, + 'model_id': 'item_1122334455667' + } + } + with patch('weko_workspace.utils.get_item_id', return_value=mock_item): + result = get_jamas_autofill_item(15) + del mock_item['no_required'] + assert result == mock_item + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_data_by_key -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('keyword, expected_result', [ + ('title', {'title': [{'@value': 'title', '@language': 'en'}]}), + ('creator', {'creator': [{'@value': 'test creator', '@language': 'en'}]}), + ('sourceTitle', {'sourceTitle': [{'@value': 'sourceTitle', '@language': 'en'}]}), + ('volume', {'volume': [{'@value': 'volume'}]}), + ('issue', {'issue': [{'@value': 'issue'}]}), + ('pageStart', {'pageStart': [{'@value': 'pageStart'}]}), + ('numPages', {'numPages': [{'@value': 'numPages'}]}), + ('date', {'date': [{'@value': '2025-01-01', '@type': 'Issued'}]}), + ('relation', {'relation': [ + {'@value': 'test doi', '@type': 'DOI'}, + {'@value': 'test issn', '@type': 'ISSN'}, + {'@value': 'test eissn', '@type': 'EISSN'} + ]}), + ('sourceIdentifier', {'sourceIdentifier': [{'@value': 'test issn', '@type': 'ISSN'}]}), + ('publisher', {'publisher': [{'@value': 'publisher', '@language': 'en'}]}), + ('description', {'description': [{'@value': 'description', '@language': 'en'}]}), + ('all', { + 'title': [{'@value': 'all', '@language': 'en'}], + 'creator': [{'@value': 'test creator', '@language': 'en'}], + 'sourceTitle': [{'@value': 'all', '@language': 'en'}], + 'volume': [{'@value': 'all'}], + 'issue': [{'@value': 'all'}], + 'pageStart': [{'@value': 'all'}], + 'numPages': [{'@value': 'all'}], + 'date': [{'@value': '2025-01-01', '@type': 'Issued'}], + 'relation': [ + {'@value': 'test doi', '@type': 'DOI'}, + {'@value': 'test issn', '@type': 'ISSN'}, + {'@value': 'test eissn', '@type': 'EISSN'} + ], + 'sourceIdentifier': [{'@value': 'test issn', '@type': 'ISSN'}], + 'publisher': [{'@value': 'all', '@language': 'en'}], + 'description': [{'@value': 'all', '@language': 'en'}] + }), + ('error', None), + ('empty', {}), + ('none', None), + ('invalid_key', {}) +]) +def test_get_jamas_data_by_key(app, mocker, keyword, expected_result): + mocker.patch('weko_workspace.utils.get_jamas_language_data', return_value='en') + mocker.patch( + 'weko_workspace.utils.pack_with_language_value_for_jamas', + return_value=[{'@value': keyword, '@language': 'en'}] + ) + mocker.patch( + 'weko_workspace.utils.get_jamas_creator_data', + return_value=[{'@value': 'test creator', '@language': 'en'}] + ) + mocker.patch( + 'weko_workspace.utils.pack_single_value_for_jamas', + return_value=[{'@value': keyword}] + ) + mocker.patch( + 'weko_workspace.utils.get_jamas_issue_date', + return_value=[{'@value': '2025-01-01', '@type': 'Issued'}] + ) + mocker.patch( + 'weko_workspace.utils.get_jamas_relation_data', + return_value=[ + {'@value': 'test doi', '@type': 'DOI'}, + {'@value': 'test issn', '@type': 'ISSN'}, + {'@value': 'test eissn', '@type': 'EISSN'} + ] + ) + mocker.patch( + 'weko_workspace.utils.get_jamas_source_data', + return_value=[{'@value': 'test issn', '@type': 'ISSN'}] + ) + if keyword == 'error': + api = { + 'response': {}, + 'error': 'test error message' + } + elif keyword == 'empty': + api = { + 'response': {}, + 'error': '' + } + elif keyword == 'none': + api = { + 'response': None, + 'error': '' + } + else: + api = { + 'response': { + 'language': 'en', + 'title': keyword, + 'creator': 'test creator', + 'publicationName': keyword, + 'volume': keyword, + 'number': keyword, + 'startingPage': keyword, + 'pageRange': keyword, + 'publicationDate': '2025年01月01日', + 'issn': 'test issn', + 'eIssn': 'test eissn', + 'doi': 'test doi', + 'publisher': keyword, + 'description': keyword + }, + 'error': '' + } + + result = get_jamas_data_by_key(api, keyword) + assert result == expected_result + +# .tox/c1/bin/pytest tests/test_utils.py::test_pack_single_value_for_jamas -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_pack_single_value_for_jamas(): + # Test with string value + value = "test_value" + result = pack_single_value_for_jamas(value) + assert result == [{'@value': value}] + + # Test with list of strings + value = ["value1", "value2"] + result = pack_single_value_for_jamas(value) + assert result == [{'@value': v} for v in value] + + # Test with empty list + value = [] + result = pack_single_value_for_jamas(value) + assert result == [] + + # Test with none value + value = None + result = pack_single_value_for_jamas(value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_pack_with_language_value_for_jamas -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_pack_with_language_value_for_jamas(): + # Test with string value + value = "test_value" + result = pack_with_language_value_for_jamas(value) + assert result == [{'@value': value, '@language': 'en'}] + + # Test with string value and specific language + lang = "ja" + value = "テスト値" + result = pack_with_language_value_for_jamas(value, lang) + assert result == [{'@value': value, '@language': lang}] + + # Test with list of strings + value = ["value1", "value2"] + result = pack_with_language_value_for_jamas(value) + assert result == [{'@value': v, '@language': 'en'} for v in value] + + # Test with list of strings and specific language + lang = "ja" + value = ["値1", "値2"] + result = pack_with_language_value_for_jamas(value, lang) + assert result == [{'@value': v, '@language': lang} for v in value] + + # Test with empty list + value = [] + result = pack_with_language_value_for_jamas(value) + assert result == [] + + # Test with none value + value = None + result = pack_with_language_value_for_jamas(value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_source_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_source_data(): + # Test with string value + value = "test_value" + result = get_jamas_source_data(value) + assert result == [{'@value': value, '@type': 'ISSN'}] + + # Test with list of strings + value = ["value1", "value2"] + result = get_jamas_source_data(value) + assert result == [{'@value': v, '@type': 'ISSN'} for v in value] + + # Test with empty list + value = [] + result = get_jamas_source_data(value) + assert result == [] + + # Test with none value + value = None + result = get_jamas_source_data(value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_relation_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_relation_data(): + # Test with string value + issn_value = "test_issn" + eissn_value = "test_eissn" + doi_value = "test_doi" + result = get_jamas_relation_data(issn_value, eissn_value, doi_value) + assert result == [ + {'@value': doi_value, '@type': 'DOI'}, + {'@value': issn_value, '@type': 'ISSN'}, + {'@value': eissn_value, '@type': 'EISSN'} + ] + + # Test with list of strings + issn_value = ["issn1", "issn2"] + eissn_value = ["eissn1", "eissn2"] + doi_value = ["doi1", "doi2"] + result = get_jamas_relation_data(issn_value, eissn_value, doi_value) + assert result == [ + {'@value': doi, '@type': 'DOI'} for doi in doi_value + ] + [ + {'@value': issn, '@type': 'ISSN'} for issn in issn_value + ] + [ + {'@value': eissn, '@type': 'EISSN'} for eissn in eissn_value + ] + + # Test with empty lists + issn_value = [] + eissn_value = [] + doi_value = [] + result = get_jamas_relation_data(issn_value, eissn_value, doi_value) + assert result == [] + + # Test with none value + issn_value = None + eissn_value = None + doi_value = None + result = get_jamas_relation_data(issn_value, eissn_value, doi_value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_issue_date -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_issue_date(): + # Test with string value that formats as 'YYYY-MM-DD' + date_value = "2025-01-01" + result = get_jamas_issue_date(date_value) + assert result == [{'@value': date_value, '@type': 'Issued'}] + + # Test with string value that formats as 'YYYY年MM月DD日' + date_value = "2025年01月01日" + result = get_jamas_issue_date(date_value) + assert result == [{'@value': '2025-01-01', '@type': 'Issued'}] + + # Test with string value that formats as 'YYYY.MM.DD' + date_value = "2025.01.01" + result = get_jamas_issue_date(date_value) + assert result == [{'@value': '2025-01-01', '@type': 'Issued'}] + + # Test with string value that formats as 'YYYY/MM/DD' + date_value = "2025/01/01" + result = get_jamas_issue_date(date_value) + assert result == [{'@value': date_value, '@type': 'Issued'}] + + # Test with list of strings + date_value = ["2025-01-01", "2025年02月01日", "2025.03.01"] + result = get_jamas_issue_date(date_value) + assert result == [ + {'@value': '2025-01-01', '@type': 'Issued'}, + {'@value': '2025-02-01', '@type': 'Issued'}, + {'@value': '2025-03-01', '@type': 'Issued'} + ] + + # Test with empty list + date_value = [] + result = get_jamas_issue_date(date_value) + assert result == [] + + # Test with none value + date_value = None + result = get_jamas_issue_date(date_value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_creator_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jamas_creator_data(): + # Test with string value + creator_value = "test_creator" + result = get_jamas_creator_data(creator_value) + assert result == [{'@value': creator_value, '@language': 'en'}] + + # Test with string value and specific language + creator_value = "テストクリエイター" + lang = "ja" + result = get_jamas_creator_data(creator_value, lang) + assert result == [{'@value': creator_value, '@language': lang}] + + # Test with list of strings + creator_value = ["creator1", "creator2"] + result = get_jamas_creator_data(creator_value) + assert result == [[{'@value': c, '@language': 'en'}] for c in creator_value] + + # Test with list of strings and specific language + creator_value = ["クリエイター1", "クリエイター2"] + lang = "ja" + result = get_jamas_creator_data(creator_value, lang) + assert result == [[{'@value': c, '@language': lang}] for c in creator_value] + + # Test with empty list + creator_value = [] + result = get_jamas_creator_data(creator_value) + assert result == [] + + # Test with none value + creator_value = None + result = get_jamas_creator_data(creator_value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jamas_language_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('lang, expected_response', [ + ('en', 'en'), + ('eng', 'en'), + ('English', 'en'), + ('英語', 'en'), + ('ja', 'ja'), + ('jpn', 'ja'), + ('japanese', 'ja'), + ('日本語', 'ja'), + ('', 'en'), + (['en', 'ja'], 'en'), + ([], 'en'), + (None, 'en') +]) +def test_get_jamas_language_data(app, lang, expected_response): + result = get_jamas_language_data(lang) + assert result == expected_response + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_record_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_record_data(app, mocker_itemtype): + mock_get_data = { + 'response': { + 'items': [ + { + 'title': 'Test Title', + 'creator': 'Test Creator', + 'publicationDate': '2023-01-01', + 'identifier': '10.1234/test-doi', + 'language': 'en' + } + ] + }, + 'error': '' + } + with patch('weko_workspace.utils.CiNiiURL.get_data', return_value=mock_get_data): + with patch('weko_workspace.utils.get_cinii_data_by_key', return_value={}): + with patch('weko_workspace.utils.get_cinii_autofill_item'): + with patch('weko_workspace.utils.get_autofill_key_tree'): + with patch('weko_workspace.utils.build_record_model', return_value=[1]): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [1] + + item_type = Mock(item_type='test_item_type', form=None) + with patch('weko_workspace.utils.ItemTypes.get_by_id', return_value=item_type): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [] + + with patch('weko_workspace.utils.ItemTypes.get_by_id', return_value=None): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [] + + # Test with error in get_data + mock_get_data = { + 'response': {}, + 'error': 'test error message' + } + with patch('weko_workspace.utils.CiNiiURL.get_data', return_value=mock_get_data): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [] + + # Test with response type is not dict + mock_get_data = { + 'response': 'not a dict', + 'error': '' + } + with patch('weko_workspace.utils.CiNiiURL.get_data', return_value=mock_get_data): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [] + + # Test with item is not found in response + mock_get_data = { + 'response': { + 'items': [] + }, + 'error': '' + } + with patch('weko_workspace.utils.CiNiiURL.get_data', return_value=mock_get_data): + current_cache.delete('cinii_data10.1234/test-doi1') + result = get_cinii_record_data('10.1234/test-doi', 1) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_data_by_key -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('keyword, expected_result', [ + ('title', {'title': [{'@value': 'title', '@language': 'ja'}]}), + ('creator', {'creator': [[{'@value': 'test creator', '@language': 'ja'}]]}), + ('description', {'description': [{'@value': 'test description', '@type': 'Abstract', '@language': 'ja'}]}), + ('subject', {'subject': [{'@scheme': 'Other', '@value': 'test subject', '@language': 'ja'}]}), + ('sourceTitle', {'sourceTitle': [{'@value': 'sourceTitle', '@language': 'ja'}]}), + ('volume', {'volume': {'@value': 'volume'}}), + ('issue', {'issue': {'@value': 'issue'}}), + ('pageStart', {'pageStart': {'@value': '10'}}), + ('pageEnd', {'pageEnd': {'@value': '10'}}), + ('numPages', {'numPages': {'@value': '1'}}), + ('date', {'date': {'@value': '2025-01-01', '@type': 'Issued'}}), + ('publisher', {'publisher': [{'@value': 'publisher', '@language': 'ja'}]}), + ('sourceIdentifier', {'sourceIdentifier': [{'@value': 'test issn', '@type': 'ISSN'}]}), + ('relation', {'relation': [{'@value': 'test doi', '@type': 'DOI'}, {'@value': 'test naid', '@type': 'NAID'}]}), + ('all', { + 'title': [{'@value': 'all', '@language': 'ja'}], + 'creator': [[{'@value': 'test creator', '@language': 'ja'}]], + 'description': [{'@value': 'test description', '@type': 'Abstract', '@language': 'ja'}], + 'subject': [{'@scheme': 'Other', '@value': 'test subject', '@language': 'ja'}], + 'sourceTitle': [{'@value': 'all', '@language': 'ja'}], + 'volume': {'@value': 'all'}, + 'issue': {'@value': 'all'}, + 'pageStart': {'@value': '10'}, + 'pageEnd': {'@value': '10'}, + 'numPages': {'@value': '1'}, + 'date': {'@value': '2025-01-01', '@type': 'Issued'}, + 'publisher': [{'@value': 'all', '@language': 'ja'}], + 'sourceIdentifier': [{'@value': 'test issn', '@type': 'ISSN'}], + 'relation': [{'@value': 'test doi', '@type': 'DOI'}, {'@value': 'test naid', '@type': 'NAID'}] + }), + ('empty', { + 'title': None, + 'creator': None, + 'description': None, + 'subject': None, + 'sourceTitle': None, + 'volume': None, + 'issue': None, + 'pageStart': None, + 'pageEnd': None, + 'numPages': {'@value': '1'}, + 'date': None, + 'publisher': None, + 'sourceIdentifier': None, + 'relation': None + }), + ('none', {}) +]) +def test_get_cinii_data_by_key(app, mocker, keyword, expected_result): + mocker.patch( + 'weko_workspace.utils.get_cinii_title_data', + return_value=[{'@value': keyword, '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_creator_data', + return_value=[[{'@value': 'test creator', '@language': 'ja'}]] + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_description_data', + return_value=[{'@value': 'test description', '@type': 'Abstract', '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_subject_data', + return_value=[{'@scheme': 'Other', '@value': 'test subject', '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.pack_single_value_as_dict', + return_value={'@value': keyword} + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_page_data', + return_value={'@value': '10'} + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_numpage', + return_value={'@value': '1'} + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_date_data', + return_value={'@value': '2025-01-01', '@type': 'Issued'} + ) + mocker.patch( + 'weko_workspace.utils.pack_data_with_multiple_type_cinii', + return_value=[{'@value': 'test issn', '@type': 'ISSN'}] + ) + mocker.patch( + 'weko_workspace.utils.get_cinii_product_identifier', + return_value=[{'@value': 'test doi', '@type': 'DOI'}, {'@value': 'test naid', '@type': 'NAID'}] + ) + + if keyword == 'empty': + api = { + 'response': { + 'items': [ + { + 'title': '', + 'dc:creator': [], + 'description': '', + 'dc:subject': '', + 'prism:publicationName': '', + 'prism:volume': '', + 'prism:number': '', + 'prism:startingPage': '', + 'prism:endingPage': '', + 'prism:publicationDate': '', + 'prism:issn': '', + 'dc:identifier': [], + 'dc:publisher': '', + } + ] + } + } + elif keyword == 'none': + api = { + 'response': None + } + else: + api = { + 'response': { + 'items': [ + { + 'title': keyword, + 'dc:creator': ['test creator'], + 'description': 'test description', + 'dc:subject': 'test subject', + 'prism:publicationName': keyword, + 'prism:volume': keyword, + 'prism:number': keyword, + 'prism:startingPage': '10', + 'prism:endingPage': '10', + 'prism:publicationDate': '2025-01-01', + 'prism:issn': 'test issn', + 'dc:identifier': [{'@value': 'test doi', '@type': 'cir:DOI'}, {'@value': 'test naid', '@type': 'cir:NAID'}], + 'dc:publisher': keyword, + } + ] + } + } + + result = get_cinii_data_by_key(api, keyword if keyword != 'empty' else 'all') + assert result == expected_result + + if keyword == 'all': + api['response']['items'].insert(0, {}) + mocker.patch( + 'weko_workspace.utils.get_cinii_numpage', + return_value={'@value': None} + ) + result = get_cinii_data_by_key(api, keyword) + assert result == { + 'title': None, + 'creator': None, + 'description': None, + 'subject': None, + 'sourceTitle': None, + 'volume': None, + 'issue': None, + 'pageStart': None, + 'pageEnd': None, + 'numPages': {'@value': None}, + 'date': None, + 'publisher': None, + 'sourceIdentifier': None, + 'relation': None + } + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_product_identifier -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_product_identifier(): + data = [ + {'@type': 'cir:DOI', '@value': '10.1234/test-doi'}, + {'@type': 'cir:NAID', '@value': '123456789'} + ] + # type1 and type2 are in the list + result = get_cinii_product_identifier(data, 'cir:DOI', 'cir:NAID') + assert result == [ + {'@value': '10.1234/test-doi', '@type': 'DOI'}, + {'@value': '123456789', '@type': 'NAID'} + ] + + # type1 is in the list, type2 is not in the list + result = get_cinii_product_identifier(data, 'cir:DOI', 'cir:ISBN') + assert result == [{'@value': '10.1234/test-doi', '@type': 'DOI'}] + + # type1 is not in the list, type2 is in the list + result = get_cinii_product_identifier(data, 'cir:ISBN', 'cir:NAID') + assert result == [{'@value': '123456789', '@type': 'NAID'}] + + # type1 and type2 are not in the list + result = get_cinii_product_identifier(data, 'cir:ISBN', 'cir:ISSN') + assert result == [] + + # Test with empty data + data = [] + result = get_cinii_product_identifier(data, 'cir:DOI', 'cir:NAID') + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_pack_data_with_multiple_type_cinii -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_pack_data_with_multiple_type_cinii(): + # Test with single value + value = 'test_value' + result = pack_data_with_multiple_type_cinii(value) + assert result == [{'@value': value, '@type': 'ISSN'}] + + # Test with list of values + value = ['value1', 'value2'] + result = pack_data_with_multiple_type_cinii(value) + assert result == [{'@value': value, '@type': 'ISSN'}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_date_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_date_data(): + # Test with string value that formats as 'YYYY-MM-DD' + date_value = "2025-01-01" + result = get_cinii_date_data(date_value) + assert result == {'@value': date_value, '@type': 'Issued'} + + # Test with string value that formats as 'YYYY年MM月DD日' + date_value = "2025年01月01日" + result = get_cinii_date_data(date_value) + assert result == {'@value': None, '@type': None} + + # Test with string value that formats as 'YYYY-MM' + date_value = "2025-01" + result = get_cinii_date_data(date_value) + assert result == {'@value': None, '@type': None} + + # Test with empty string + date_value = "" + result = get_cinii_date_data(date_value) + assert result == {'@value': None, '@type': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_numpage -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_numpage(app): + # startingPage and endingPage are both present and valid + starting_page = "10" + ending_page = "20" + result = get_cinii_numpage(starting_page, ending_page) + assert result == {'@value': '11'} + + # startingPage and endingPage are both present but invalid + starting_page = "invalid" + ending_page = "invalid" + result = get_cinii_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # startingPage is present and valid, endingPage is not present + starting_page = "10" + ending_page = None + result = get_cinii_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # startingPage is not present, endingPage is present and valid + starting_page = None + ending_page = "20" + result = get_cinii_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # both startingPage and endingPage are not present + starting_page = None + ending_page = None + result = get_cinii_numpage(starting_page, ending_page) + assert result == {'@value': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_page_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_page_data(app): + # value is number + value = "10" + result = get_cinii_page_data(value) + assert result == {'@value': value} + + # value is string + value = "test_page" + result = get_cinii_page_data(value) + assert result == {'@value': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_pack_single_value_as_dict -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_pack_single_value_as_dict(): + value = "test_value" + result = pack_single_value_as_dict(value) + assert result == {'@value': value} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_subject_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_subject_data(app): + # Test with list of strings + value = ["subject1", "subject2"] + result = get_cinii_subject_data(value, None) + assert result == [{'@scheme': 'Other', '@value': v, '@language': 'en'} for v in value] + + # Test with empty list + value = [] + result = get_cinii_subject_data(value, None) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_creator_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_creator_data(app): + # Test with list of strings + value = ["creator1", "creator2"] + result = get_cinii_creator_data(value) + assert result == [[{'@value': c, '@language': 'en'}] for c in value] + + # Test with list of strings what value is empty + value = ["", "creator2"] + result = get_cinii_creator_data(value) + assert result == [[{'@value': 'creator2', '@language': 'en'}]] + + # Test with list of strings what value is None + value = [None, "creator2"] + result = get_cinii_creator_data(value) + assert result == [[{'@value': 'creator2', '@language': 'en'}]] + + # Test with empty list + value = [] + result = get_cinii_creator_data(value) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_title_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_title_data(app): + value = "test_title" + result = get_cinii_title_data(value) + assert result == [{'@value': value, '@language': 'en'}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_description_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_description_data(app): + value = "test_description" + result = get_cinii_description_data(value) + assert result == [{'@value': value, '@type': 'Abstract', '@language': 'en'}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_autofill_key_tree -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('item_keys, mock_result, expected_result', [ + ( + ['creator'], + [ + { + '@value': 'item_1617186419668.creatorNames.creatorName', + '@language': 'item_1617186419668.creatorNames.creatorNameLang' + } + ], + { + 'creator': { + '@value': 'item_1617186419668.creatorNames.creatorName', + '@language': 'item_1617186419668.creatorNames.creatorNameLang' + } + } + ), + ( + ['contributor'], + [ + { + '@value': 'item_1617349709064.contributorNames.contributorName', + '@language': 'item_1617349709064.contributorNames.lang' + } + ], + { + 'contributor': { + '@value': 'item_1617349709064.contributorNames.contributorName', + '@language': 'item_1617349709064.contributorNames.lang' + } + } + ), + ( + ['relation'], + [ + { + '@value': 'item_1617353299429.subitem_1522306287251.subitem_1522306436033', + '@type': 'item_1617353299429.subitem_1522306287251.subitem_1522306382014' + } + ], + { + 'relation': { + '@value': 'item_1617353299429.subitem_1522306287251.subitem_1522306436033', + '@type': 'item_1617353299429.subitem_1522306287251.subitem_1522306382014' + } + } + ), + ( + ['issue'], + [ + {'@value': 'item_1617186981471.subitem_1551256294723'}, + {'@value': 'item_1617187056579.bibliographicIssueNumber'} + ], + { + 'issue': [ + {'issue': {'@value': 'item_1617186981471.subitem_1551256294723'}}, + {'issue': {'@value': 'item_1617187056579.bibliographicIssueNumber'}} + ] + } + ), + ( + ['relation', 'issue'], + [ + { + '@value': 'item_1617353299429.subitem_1522306287251.subitem_1522306436033', + '@type': 'item_1617353299429.subitem_1522306287251.subitem_1522306382014' + }, + {'@value': 'item_1617186981471.subitem_1551256294723'}, + {'@value': 'item_1617187056579.bibliographicIssueNumber'} + ], + { + 'relation': { + '@value': 'item_1617353299429.subitem_1522306287251.subitem_1522306436033', + '@type': 'item_1617353299429.subitem_1522306287251.subitem_1522306382014' + }, + 'issue': [ + {'issue': {'@value': 'item_1617186981471.subitem_1551256294723'}}, + {'issue': {'@value': 'item_1617187056579.bibliographicIssueNumber'}} + ] + } + ), + ( + ['others'], + [], + {} + ) +]) +def test_get_autofill_key_tree(item_keys, mock_result, expected_result): + with open('tests/data/item_type/15_form.json', 'r') as f: + form = json.load(f) + + if item_keys != ['others']: + with open('tests/data/test_get_autofill_key_tree.json', 'r') as f: + autofill_item = json.load(f) + + item = {} + for key in item_keys: + if key in autofill_item: + item[key] = autofill_item[key] + + with patch('weko_workspace.utils.get_key_value', side_effect=mock_result) as mock_get_key_value: + result = get_autofill_key_tree(form, item) + assert result == expected_result + expected_calls = [] + for key in item_keys: + if key == 'creator': + expected_calls.append( + call(form, item[key]['creatorName'], item[key]['model_id']) + ) + elif key == 'contributor': + expected_calls.append( + call(form, item[key]['contributorName'], item[key]['model_id']) + ) + elif key == 'relation': + expected_calls.append( + call(form, item[key]['relatedIdentifier'], item[key]['model_id']) + ) + mock_get_key_value.assert_has_calls(expected_calls) + + input_result = {'key': 'value'} + with patch('weko_workspace.utils.get_key_value', side_effect=mock_result): + result = get_autofill_key_tree(form, item, input_result) + assert result == {**input_result, **expected_result} + + if len(item_keys) == 1 and item_keys[0] in ['creator', 'contributor', 'relation']: + if item_keys[0] == 'creator': + del item['creator']['creatorName'] + elif item_keys[0] == 'contributor': + del item['contributor']['contributorName'] + elif item_keys[0] == 'relation': + del item['relation']['relatedIdentifier'] + with patch('weko_workspace.utils.get_key_value', side_effect=mock_result) as mock_get_key_value: + result = get_autofill_key_tree(form, item) + assert result == {} + mock_get_key_value.assert_not_called() + else: + # item's type is not dict + item = 'not a dict' + result = get_autofill_key_tree(form, item) + assert result is None + + # item's type is not dict with input_result + input_result = {'key': 'value'} + result = get_autofill_key_tree(form, item, input_result) + assert result is None + + item = { + 'pubdate': { + '@value': 'pubdate', + 'model_id': 'pubdate' + }, + 'three': [ + { + 'three': { + '@value': 'three_value1', + 'model_id': 'three_model_id_1' + } + }, + { + 'three': { + '@value': 'three_value2', + 'model_id': 'three_model_id_2' + } + }, + { + 'three': { + '@value': 'three_value3', + 'model_id': 'three_model_id_3' + } + } + ], + 'no_model_id': { + '@value': 'no_model_id_value' + }, + 'not_dict': 'not a dict value' + } + side_effects = [ + {'@value': 'three_value1'}, + {'@value': 'three_value2'}, + {'@value': 'three_value3'} + ] + with patch('weko_workspace.utils.get_key_value', side_effect=side_effects) as mock_get_key_value: + result = get_autofill_key_tree(form, item) + assert result == { + 'three': [ + {'three': {'@value': 'three_value1'}}, + {'three': {'@value': 'three_value2'}}, + {'three': {'@value': 'three_value3'}} + ] + } + assert mock_get_key_value.call_count == 3 + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_key_value -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('value, parent_key, mock_result, expected_result, expected_value_key_list', [ + ( + { + "@value": "subitem_1551256294723", + "model_id": "item_1617186981471" + }, + 'item_1617186981471', + [{'key': 'item_1617186981471.subitem_1551256294723'}], + {'@value': 'item_1617186981471.subitem_1551256294723'}, + ['subitem_1551256294723'] + ), + ( + { + "@value": "subitem_description", + "@attributes": { + "xml:lang": "subitem_description_language", + "descriptionType": "subitem_description_type" + }, + "model_id": "item_1617186626617" + }, + 'item_1617186626617', + [ + {'key': 'item_1617186626617.subitem_description'}, + {'key': 'item_1617186626617.subitem_description_language'}, + {'key': 'item_1617186626617.subitem_description_type'} + ], + { + '@value': 'item_1617186626617.subitem_description', + '@language': 'item_1617186626617.subitem_description_language', + '@type': 'item_1617186626617.subitem_description_type' + }, + ['subitem_description', 'subitem_description_language', 'subitem_description_type'] + ), + ( + { + "@value": "subitem_1522646572813", + "@attributes": { + "identifierType": "subitem_1522646500366" + }, + "model_id": "item_1617186920753" + }, + 'item_1617186920753', + [ + {'key': 'item_1617186920753.subitem_1522646572813'}, + {'key': 'item_1617186920753.subitem_1522646500366'} + ], + { + '@value': 'item_1617186920753.subitem_1522646572813', + '@type': 'item_1617186920753.subitem_1522646500366' + }, + ['subitem_1522646572813', 'subitem_1522646500366'] + ), + ( + { + "@value": "subitem_1523261968819", + "@attributes": { + "xml:lang": "subitem_1522299896455", + "subjectURI": "subitem_1522300048512", + "subjectScheme": "subitem_1522300014469" + }, + "model_id": "item_1617186609386" + }, + 'item_1617186609386', + [ + {'key': 'item_1617186609386.subitem_1523261968819'}, + {'key': 'item_1617186609386.subitem_1522299896455'}, + {'key': 'item_1617186609386.subitem_1522300014469'}, + {'key': 'item_1617186609386.subitem_1522300048512'} + ], + { + '@value': 'item_1617186609386.subitem_1523261968819', + '@language': 'item_1617186609386.subitem_1522299896455', + '@scheme': 'item_1617186609386.subitem_1522300014469', + '@URI': 'item_1617186609386.subitem_1522300048512' + }, + ['subitem_1523261968819', 'subitem_1522299896455', 'subitem_1522300014469', 'subitem_1522300048512'] + ), + ( + { + "@value": "subitem_1522300722591", + "@attributes": { + "dateType": "subitem_1522300695726" + }, + "model_id": "item_1617186660861" + }, + 'item_1617186660861', + [ + {'key': 'item_1617186660861.subitem_1522300722591'}, + {'key': 'item_1617186660861.subitem_1522300695726'} + ], + { + '@value': 'item_1617186660861.subitem_1522300722591', + '@type': 'item_1617186660861.subitem_1522300695726' + }, + ['subitem_1522300722591', 'subitem_1522300695726'] + ), + ( + { + '@attribute': { + 'no_value': 'subitem_1522300722591' + } + }, + 'item_123456789012', + [], + {}, + [] + ) +]) +def test_get_key_value(value, parent_key, mock_result, expected_result, expected_value_key_list): + with open('tests/data/item_type/15_form.json', 'r') as f: + form = json.load(f) + + with patch('weko_workspace.utils.get_autofill_key_path', side_effect=mock_result) as mock_get_autofill_key_path: + result = get_key_value(form, value, parent_key) + assert result == expected_result + expect_call_args = [] + for key in expected_value_key_list: + expect_call_args.append(call(form, parent_key, key)) + if expected_value_key_list: + mock_get_autofill_key_path.assert_has_calls(expect_call_args) + else: + mock_get_autofill_key_path.assert_not_called() + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_item_id -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_item_id(app, mocker_itemtype): + class MockMapping: + def items(self): + raise Exception("Mocked items method") + result = get_item_id(15) + with open('tests/data/test_get_item_id.json', 'r') as f: + expected_result = json.load(f) + assert result == expected_result + + # Test with mapping having no items + with patch('weko_workspace.utils.Mapping.get_record', return_value={}): + result = get_item_id(15) + assert result == {} + + # Test with error + with patch('weko_workspace.utils.Mapping.get_record', return_value=MockMapping()): + result = get_item_id(15) + assert result == {'error': 'Mocked items method'} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_cinii_autofill_item -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_cinii_autofill_item(app): + with open('tests/data/test_get_item_id.json', 'r') as f: + mock_item = json.load(f) + with patch('weko_workspace.utils.get_item_id', return_value=mock_item): + result = get_cinii_autofill_item(15) + with open('tests/data/test_get_cinii_autofill_item.json', 'r') as f: + expected_result = json.load(f) + assert result == expected_result + + # Test with empty item + with patch('weko_workspace.utils.get_item_id', return_value={}): + result = get_cinii_autofill_item(15) + assert result == {} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_autofill_key_path -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_autofill_key_path(app): + with open('tests/data/item_type/15_form.json', 'r') as f: + form = json.load(f) + + mock_side_effect = [ + (False, None), + (True, 'item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType'), + ] + with patch('weko_workspace.utils.get_specific_key_path', side_effect=mock_side_effect) as mock_get_specific_key_path: + result = get_autofill_key_path(form, 'item_1617187056579', 'bibliographicIssueDates.bibliographicIssueDateType') + assert result == {'key': 'item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType'} + assert mock_get_specific_key_path.call_count == 2 + + # Test with non-existing key + with patch('weko_workspace.utils.get_specific_key_path', return_value=(False, None)): + result = get_autofill_key_path(form, 'item_1617187056579', 'nonExistingKey') + assert result == {'key': None} + + with patch('weko_workspace.utils.get_specific_key_path', side_effect=Exception("Mocked exception")): + result = get_autofill_key_path(form, 'item_1617187056579', 'bibliographicIssueDates.bibliographicIssueDateType') + assert result == {'key': None, 'error': 'Mocked exception'} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_specific_key_path -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_specific_key_path(): + des_key = ['bibliographicIssueDates', 'bibliographicIssueDateType'] + form = { + "key": "item_1617187056579.bibliographicIssueDates", + "type": "fieldset", + "items": [ + { + "key": "item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType", + "type": "select", + "title": "日付タイプ", + "titleMap": [ + { + "name": "Issued", + "value": "Issued" + } + ], + "title_i18n": { + "en": "Date Type", + "ja": "日付タイプ" + }, + "title_i18n_temp": { + "en": "Date Type", + "ja": "日付タイプ" + } + }, + { + "key": "item_1617187056579.bibliographicIssueDates.bibliographicIssueDate", + "type": "template", + "title": "日付", + "format": "yyyy-MM-dd", + "title_i18n": { + "en": "Date", + "ja": "日付" + }, + "templateUrl": "/static/templates/weko_deposit/datepicker_multi_format.html", + "title_i18n_temp": { + "en": "Date", + "ja": "日付" + } + } + ], + "title": "発行日", + "title_i18n": { + "en": "Issue Date", + "ja": "発行日" + }, + "title_i18n_temp": { + "en": "Issue Date", + "ja": "発行日" + } + } + existed, result = get_specific_key_path(des_key, form) + assert existed is True + assert result == 'item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType' + + # Test with non-existing key + des_key = ['bibliographicIssueDates', 'nonExistingKey'] + existed, result = get_specific_key_path(des_key, form) + assert existed is False + assert result is None + + # Test with form not having 'key' + form = { + "type": "fieldset", + "items": [ + { + "key": "item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType", + "type": "select", + "title": "日付タイプ" + } + ], + "title": "発行日" + } + des_key = ['bibliographicIssueDates', 'bibliographicIssueDateType'] + existed, result = get_specific_key_path(des_key, form) + assert existed is True + assert result == 'item_1617187056579.bibliographicIssueDates.bibliographicIssueDateType' + + # Test with form type not dict or list + form = "not a dict or list" + existed, result = get_specific_key_path(des_key, form) + assert existed is False + assert result is None + +# .tox/c1/bin/pytest tests/test_utils.py::test_build_form_model -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('form_model, form_key, expected_value',[ + ( + {}, + { + '@value': 'item_1617186419668.creatorNames.creatorName', + '@language': 'item_1617186419668.creatorNames.creatorNameLang' + }, + { + '@value': { + 'item_1617186419668': { + 'creatorNames': { + 'creatorName': '@value' + } + } + }, + '@language': { + 'item_1617186419668': { + 'creatorNames': { + 'creatorNameLang': '@language' + } + } + } + } + ), + ( + {}, + { + '@value': 'item_1617605131499[].filesize[].value' + }, + { + '@value': { + 'item_1617605131499': [ + { + 'filesize': [ + { + 'value': '@value' + } + ] + } + ] + } + } + ), + ({}, {'@value': {'key': 'value'}}, {}), + ({}, 'item_123456789012', {}), + ([], [], []), + ('', ['item_123456789012'], '') +]) +def test_build_form_model(form_model, form_key, expected_value): + build_form_model(form_model, form_key) + assert form_model == expected_value + +# .tox/c1/bin/pytest tests/test_utils.py::test_build_model -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_build_model(): + # form_model is a dict and form_key is including '[]' + form_model = {} + form_key = 'item_123456789012[]' + build_model(form_model, form_key) + assert form_model == {'item_123456789012': []} + + # form_model is a dict and form_key is not including '[]' + form_model = {} + form_key = 'item_123456789012' + build_model(form_model, form_key) + assert form_model == {'item_123456789012': {}} + + # form_model is a list and form_key is including '[]' + form_model = [] + form_key = 'item_123456789012[]' + build_model(form_model, form_key) + assert form_model == [{'item_123456789012': []}] + + # form_model is a list and form_key is not including '[]' + form_model = [] + form_key = 'item_123456789012' + build_model(form_model, form_key) + assert form_model == [{'item_123456789012': {}}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_data_by_key -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +@pytest.mark.parametrize('keyword, expected_result', [ + ('title', {'title': [{'@value': 'test title', '@language': 'ja'}]}), + ('creator', {'creator': [[{'@value': 'Doe John', '@language': 'en'}]]}), + ('sourceTitle', {'sourceTitle': [{'@value': 'test subject', '@language': 'ja'}]}), + ('volume', {'volume': {'@value': '1'}}), + ('issue', {'issue': {'@value': '1'}}), + ('pageStart', {'pageStart': {'@value': '10'}}), + ('pageEnd', {'pageEnd': {'@value': '10'}}), + ('numPages', {'numPages': {'@value': '1'}}), + ('date', {'date': {'@value': '2025-01-01', '@type': 'Issued'}}), + ('publisher', {'publisher': [{'@value': 'Publisher A', '@language': 'ja'}]}), + ('sourceIdentifier', {'sourceIdentifier': [{'@value': '1234-5678', '@type': 'ISSN'}]}), + ('relation', {'relation': [{'@value': '10.1234/56789'}]}), + ('all', { + 'title': [{'@value': 'test title', '@language': 'ja'}], + 'creator': [[{'@value': 'Doe John', '@language': 'en'}]], + 'sourceTitle': [{'@value': 'test subject', '@language': 'ja'}], + 'volume': {'@value': '1'}, + 'issue': {'@value': '1'}, + 'pageStart': {'@value': '10'}, + 'pageEnd': {'@value': '10'}, + 'numPages': {'@value': '1'}, + 'date': {'@value': '2025-01-01', '@type': 'Issued'}, + 'publisher': [{'@value': 'Publisher A', '@language': 'ja'}], + 'sourceIdentifier': [{'@value': '1234-5678', '@type': 'ISSN'}], + 'relation': [{'@value': '10.1234/56789'}] + }), + ('empty', { + 'title': None, + 'creator': None, + 'sourceTitle': None, + 'volume': None, + 'issue': None, + 'pageStart': None, + 'pageEnd': None, + 'numPages': {'@value': '1'}, + 'date': None, + 'publisher': None, + 'sourceIdentifier': None, + 'relation': None + }), + ('none', {}) +]) +def test_get_jalc_data_by_key(app, mocker, keyword, expected_result): + mocker.patch( + 'weko_workspace.utils.get_jalc_title_data', + return_value=[{'@value': 'test title', '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_creator_data', + return_value=[[{'@value': 'Doe John', '@language': 'en'}]] + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_source_title_data', + return_value=[{'@value': 'test subject', '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.pack_single_value_as_dict', + return_value={'@value': '1'} + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_page_data', + return_value={'@value': '10'} + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_numpage', + return_value={'@value': '1'} + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_date_data', + return_value={'@value': '2025-01-01', '@type': 'Issued'} + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_publisher_data', + return_value=[{'@value': 'Publisher A', '@language': 'ja'}] + ) + mocker.patch( + 'weko_workspace.utils.pack_data_with_multiple_type_jalc', + return_value=[{'@value': '1234-5678', '@type': 'ISSN'}] + ) + mocker.patch( + 'weko_workspace.utils.get_jalc_product_identifier', + return_value=[{'@value': '10.1234/56789'}] + ) + + if keyword == 'empty': + api = { + 'response': { + 'data': { + 'title_list': [{}], + 'creator_list': [], + 'journal_title_name_list': [], + 'volume': '', + 'issue': '', + 'first_page': '', + 'last_page': '', + 'date': '', + 'publisher_list': [], + 'journal_id_list': [], + 'doi': '' + } + } + } + elif keyword == 'none': + api = { + 'response': None + } + else: + api = { + 'response': { + 'data': { + 'title_list': [ + { + 'title': 'test title', + 'lang': 'ja' + } + ], + 'creator_list': [ + { + 'sequence': 1, + 'type': 'person', + 'names': [ + { + 'lang': 'en', + 'last_name': 'Doe', + 'first_name': 'John' + } + ], + 'affiliation_list': [ + { + 'affiliation_name': 'University A', + 'sequence': 1, + 'lang': 'en' + } + ] + } + ], + 'journal_title_name_list': [ + { + 'journal_title_name': 'test subject', + 'type': 'full', + 'lang': 'ja' + } + ], + 'volume': '1', + 'issue': '1', + 'first_page': '10', + 'last_page': '10', + 'date': '2025-01-01', + 'publisher_list': [ + { + 'publisher_name': 'Publisher A', + 'lang': 'ja' + } + ], + 'journal_id_list': [ + { + 'journal_id': '1234-5678', + 'type': 'ISSN', + 'issn_type': 'print' + } + ], + 'doi': '10.1234/56789' + } + } + } + + result = get_jalc_data_by_key(api, keyword if keyword != 'empty' else 'all') + assert result == expected_result + + if keyword == 'title': + api['response']['data']['title_list'].insert(0, {}) + result = get_jalc_data_by_key(api, keyword) + assert result == {} + + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_publisher_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_publisher_data(app): + # Test with valid data + data = [ + { + 'publisher_name': '出版者A', + 'lang': 'ja' + }, + { + 'publisher_name': 'Publisher B' + } + ] + result = get_jalc_publisher_data(data) + assert result == [ + { + '@value': '出版者A', + '@language': 'ja' + }, + { + '@value': 'Publisher B', + '@language': 'en' + } + ] + + # Test with empty data + data = [] + result = get_jalc_publisher_data(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_title_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_title_data(app): + # data has 'lang' key + data = { + 'title': 'test title', + 'lang': 'ja' + } + result = get_jalc_title_data(data) + assert result == [{'@value': 'test title', '@language': 'ja'}] + + # data does not have 'lang' key + data = { + 'title': 'test title' + } + result = get_jalc_title_data(data) + assert result == [{'@value': 'test title', '@language': 'en'}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_creator_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_creator_data(app): + # Test with valid data + data = [ + { + 'sequence': 1, + 'type': 'person', + 'names': [ + { + 'lang': 'en', + 'last_name': 'Doe', + 'first_name': 'John', + } + ], + 'affiliation_list': [ + { + 'affiliation_name': 'University A', + 'sequence': 1, + 'lang': 'en' + } + ] + }, + { + 'sequence': 2, + 'type': 'person', + 'names': [ + { + 'last_name': '山田', + 'first_name': '太郎', + }, + { + 'lang': 'en', + 'last_name': 'Yamada', + 'first_name': 'Taro', + } + ], + 'affiliation_list': [ + { + 'affiliation_name': 'B大学', + 'sequence': 2, + 'lang': 'ja' + }, + { + 'affiliation_name': 'University B', + 'sequence': 2, + 'lang': 'en' + } + ] + }, + { + 'sequence': 3, + 'type': 'other' + } + ] + result = get_jalc_creator_data(data) + assert result == [ + [ + { + '@value': 'Doe John', + '@language': 'en' + } + ], + [ + { + '@value': '山田 太郎', + '@language': 'en' + }, + { + '@value': 'Yamada Taro', + '@language': 'en' + } + ] + ] + + # Test with empty data + data = [] + result = get_jalc_creator_data(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_source_title_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_source_title_data(app): + # Test with valid data + data = [ + { + 'journal_title_name': 'Journal of Example Studies', + 'type': 'full', + 'lang': 'en' + }, + { + 'journal_title_name': '例の研究ジャーナル', + 'type': 'full', + } + ] + result = get_jalc_source_title_data(data) + assert result == [ + { + '@value': 'Journal of Example Studies', + '@language': 'en' + }, + { + '@value': '例の研究ジャーナル', + '@language': 'en' + } + ] + + # Test with empty data + data = [] + result = get_jalc_source_title_data(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_page_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_page_data(app): + # Test with valid page number + value = "10" + result = get_jalc_page_data(value) + assert result == {'@value': value} + + # Test with invalid page number + value = "invalid" + result = get_jalc_page_data(value) + assert result == {'@value': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_numpage -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_numpage(app): + # Test with valid startingPage and endingPage + starting_page = "10" + ending_page = "20" + result = get_jalc_numpage(starting_page, ending_page) + assert result == {'@value': '11'} + + # Test with invalid startingPage and endingPage + starting_page = "invalid" + ending_page = "invalid" + result = get_jalc_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # Test with only startingPage present + starting_page = "10" + ending_page = None + result = get_jalc_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # Test with only endingPage present + starting_page = None + ending_page = "20" + result = get_jalc_numpage(starting_page, ending_page) + assert result == {'@value': None} + + # Test with neither startingPage nor endingPage present + starting_page = None + ending_page = None + result = get_jalc_numpage(starting_page, ending_page) + assert result == {'@value': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_date_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_date_data(): + # Test with string value that formats as 'YYYY-MM-DD' + date_value = "2025-01-01" + result = get_jalc_date_data(date_value) + assert result == {'@value': date_value, '@type': 'Issued'} + + # Test with string value that formats as 'YYYY年MM月DD日' + date_value = "2025年01月01日" + result = get_jalc_date_data(date_value) + assert result == {'@value': None, '@type': None} + + # Test with string value that formats as 'YYYY-MM' + date_value = "2025-01" + result = get_jalc_date_data(date_value) + assert result == {'@value': None, '@type': None} + + # Test with empty string + date_value = "" + result = get_jalc_date_data(date_value) + assert result == {'@value': None, '@type': None} + +# .tox/c1/bin/pytest tests/test_utils.py::test_pack_data_with_multiple_type_jalc -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_pack_data_with_multiple_type_jalc(): + # Test with valid data + data = [ + { + 'journal_id': '1234-5678', + 'type': 'ISSN', + 'issn_type': 'print' + }, + { + 'journal_id': 'AA12345678', + 'type': 'NCID' + } + ] + result = pack_data_with_multiple_type_jalc(data) + assert result == [ + { + '@value': '1234-5678', + '@type': 'ISSN' + }, + { + '@value': 'AA12345678', + '@type': 'NCID' + } + ] + + # Test with empty data + data = [] + result = pack_data_with_multiple_type_jalc(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_jalc_product_identifier -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_jalc_product_identifier(): + data = '10.1234/56789' + result = get_jalc_product_identifier(data) + assert result == [{'@value': '10.1234/56789'}] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_datacite_title_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_datacite_title_data(app): + # Test with valid data + data = [ + { + 'title': 'Test Title', + 'lang': 'en' + }, + { + 'title': 'テストタイトル' + } + ] + result = get_datacite_title_data(data) + assert result == [ + {'@value': 'Test Title', '@language': 'en'}, + {'@value': 'テストタイトル', '@language': 'en'} + ] + + # Test with empty data + data = [] + result = get_datacite_title_data(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_datacite_creator_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_datacite_creator_data(app): + # Test with valid data + data = [ + { + 'name': 'Doe, John', + 'affiliation': [], + 'nameIdentifiers': [] + }, + { + 'affiliation': [ + 'University A', + ], + 'nameIdentifiers': [ + { + 'nameIdentifier': 'http://orcid.org/0000-0001-2345-6789', + 'nameIdentifierScheme': 'ORCID', + 'schemeURI': 'http://orcid.org' + } + ] + } + ] + result = get_datacite_creator_data(data) + assert result == [[{'@value': 'Doe, John', '@language': 'en'}]] + + # Test with empty data + data = [] + result = get_datacite_creator_data(data) + assert result == [] + +# .tox/c1/bin/pytest tests/test_utils.py::test_get_datacite_contributor_data -vv -s --cov-branch --cov=weko_workspace --cov-report=term --basetemp=/code/modules/weko-workspace/tests/.tox/c1/tmp +def test_get_datacite_contributor_data(app): + # Test with valid data + data = [ + { + 'name': 'Smith, Jane', + 'givenName': 'Jane', + 'familyName': 'Smith', + 'affiliation': [ + 'Institute A', + ], + 'contributorType': 'ProjectLeader', + 'nameIdentifiers': [ + { + 'nameIdentifier': 'http://orcid.org/0000-0001-2345-6789', + 'nameIdentifierScheme': 'ORCID', + 'schemeURI': 'http://orcid.org' + } + ] + }, + { + 'givenName': 'Taro', + 'familyName': 'Yamada', + 'affiliation': [ + 'Institute B', + ], + 'contributorType': 'Editor', + 'nameIdentifiers': [ + { + 'nameIdentifier': 'http://orcid.org/0000-0002-3456-7890', + 'nameIdentifierScheme': 'ORCID', + 'schemeURI': 'http://orcid.org' + } + ] + } + ] + result = get_datacite_contributor_data(data) + assert result == [[{'@value': 'Smith, Jane', '@language': 'en'}]] + + # Test with empty data + data = [] + result = get_datacite_contributor_data(data) + assert result == [] diff --git a/modules/weko-workspace/weko_workspace/config.py b/modules/weko-workspace/weko_workspace/config.py index 99ba2eb0c3..f01e766ba9 100644 --- a/modules/weko-workspace/weko_workspace/config.py +++ b/modules/weko-workspace/weko_workspace/config.py @@ -103,6 +103,7 @@ "volume", "issue", "pageStart", + "numPages", "date", "relation", "publisher", @@ -836,24 +837,54 @@ WEKO_WORKSPACE_DATACITE_API_URL = 'https://api.datacite.org/dois/' """DataCite API URL""" -WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM = [ - "title", - "alternative", - "creator", - "contributor", - "description", - "subject", - "sourceTitle", - "volume", - "issue", - "pageStart", - "pageEnd", - "numPages", - "date", - "publisher", - "sourceIdentifier", - "relation" +WEKO_WORKSPACE_CINII_REQUIRED_ITEM = [ + 'title', + 'creator', + 'description', + 'subject', + 'sourceTitle', + 'volume', + 'issue', + 'pageStart', + 'pageEnd', + 'numPages', + 'date', + 'publisher', + 'sourceIdentifier', + 'relation' +] +"""CiNii required item""" + +WEKO_WORKSPACE_JALC_REQUIRED_ITEM = [ + 'title', + 'creator', + 'sourceTitle', + 'volume', + 'issue', + 'pageStart', + 'pageEnd', + 'numPages', + 'date', + 'publisher', + 'sourceIdentifier', + 'relation' ] +"""JaLC required item""" + +WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM = [ + 'title', + 'creator', + 'contributor', + 'description', + 'subject', + 'sourceTitle', + 'sourceIdentifier', + 'relation' +] +"""DataCite required item""" + +WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE = 'en' +"""Default language for data""" WEKO_RECORDS_UI_LICENSE_DICT = [ { diff --git a/modules/weko-workspace/weko_workspace/utils.py b/modules/weko-workspace/weko_workspace/utils.py index dfbe4109ff..beb5a315d4 100644 --- a/modules/weko-workspace/weko_workspace/utils.py +++ b/modules/weko-workspace/weko_workspace/utils.py @@ -831,7 +831,9 @@ def get_jamas_language_data(data): Returns: list: A list containing a dictionary with the language. """ - default_language = 'en' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) if isinstance(data, str): result = data if data else default_language elif isinstance(data, list) and len(data) > 0: @@ -939,7 +941,7 @@ def get_cinii_data_by_key(api, keyword): 'cir:NAID' ) elif keyword == 'all': - for key in current_app.config.get("WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM"): + for key in current_app.config.get("WEKO_WORKSPACE_CINII_REQUIRED_ITEM"): result[key] = get_cinii_data_by_key(api, key).get(key) return result @@ -1102,12 +1104,13 @@ def get_cinii_subject_data(data, title): list: A list of dictionaries containing the subject data. """ result = list() - default_language = 'ja' for sub in data: new_data = dict() new_data["@scheme"] = "Other" new_data["@value"] = sub - new_data["@language"] = default_language + new_data["@language"] = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) result.append(new_data) return result @@ -1128,43 +1131,20 @@ def get_cinii_creator_data(data): list: A list of lists, each containing a dictionary with the creator name and language. """ result = list() - default_language = 'ja' for item in data: name_data = item if name_data: result_creator = list() new_data = dict() new_data['@value'] = name_data - new_data['@language'] = default_language + new_data['@language'] = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) result_creator.append(new_data) result.append(result_creator) return result -def get_cinii_contributor_data(data): - """Get contributor data from CiNii. - - Get contributor name and form it as format: - { - '@value': name, - '@language': language - } - - Args: - data (list): A list of contributor names. - - Returns: - list: A list of dictionaries, each containing a contributor name and language. - """ - result = list() - for item in data: - name_data = item - if name_data: - - result.append(get_basic_cinii_data(name_data)) - return result - - def get_cinii_title_data(data): """Get title data from CiNii. @@ -1181,40 +1161,15 @@ def get_cinii_title_data(data): list: A list containing a dictionary with the title and its language. """ result = list() - default_language = 'ja' new_data = dict() new_data['@value'] = data - new_data['@language'] = default_language + new_data['@language'] = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) result.append(new_data) return result -def get_basic_cinii_data(data): - """Get basic data template from CiNii. - - Basic value format: - { - '@value': value, - '@language': language - } - - Args: - data (str): The basic value to be packed. - - Returns: - list: A list containing a dictionary with the value and its language. - """ - result = list() - default_language = 'ja' - - for item in data: - new_data = dict() - new_data['@value'] = item - new_data['@language'] = default_language - result.append(new_data) - return result - - def get_cinii_description_data(data): """Get description data from CiNii. @@ -1232,13 +1187,14 @@ def get_cinii_description_data(data): list: A list containing a dictionary with the description, language, and type. """ result = list() - default_language = 'ja' default_type = 'Abstract' new_data = dict() new_data['@value'] = data new_data['@type'] = default_type - new_data["@language"] = default_language + new_data["@language"] = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) result.append(new_data) return result @@ -1410,7 +1366,7 @@ def get_cinii_autofill_item(item_id): jpcoar_item = get_item_id(item_id) cinii_req_item = dict() - for key in current_app.config.get("WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM"): + for key in current_app.config.get("WEKO_WORKSPACE_CINII_REQUIRED_ITEM"): if jpcoar_item.get(key) is not None: cinii_req_item[key] = jpcoar_item.get(key) return cinii_req_item @@ -1778,7 +1734,7 @@ def get_jalc_record_data(doi, item_type_id, exclude_duplicate_lang=True): return result elif items.form is not None: autofill_key_tree = get_autofill_key_tree( - items.form, get_cinii_autofill_item(item_type_id)) + items.form, get_jalc_autofill_item(item_type_id)) result = build_record_model( autofill_key_tree, api_data, schema=items.schema, exclude_duplicate_lang=exclude_duplicate_lang @@ -1797,18 +1753,19 @@ def get_jalc_data_by_key(api, keyword): Returns: dict: A dictionary containing the data for the specified keyword. """ - import json data_response = api['response'] result = dict() if data_response is None: return result data = data_response - if keyword == 'title' and data['data']['title_list'][0]: + if keyword == 'title'\ + and data['data'].get('title_list')\ + and data['data']['title_list'][0]: result[keyword] = get_jalc_title_data(data['data']['title_list'][0]) - elif keyword == 'creator' and data['data']['creator_list']: + elif keyword == 'creator' and data['data'].get('creator_list'): result[keyword] = get_jalc_creator_data(data['data']['creator_list']) - elif keyword == 'sourceTitle' and data['data']['journal_title_name_list']: - result[keyword] = get_jalc_subject_data(data['data']['journal_title_name_list']) + elif keyword == 'sourceTitle' and data['data'].get('journal_title_name_list'): + result[keyword] = get_jalc_source_title_data(data['data']['journal_title_name_list']) elif keyword == 'volume' and data['data'].get('volume'): result[keyword] = pack_single_value_as_dict(data['data'].get('volume')) elif keyword == 'issue' and data['data'].get('issue'): @@ -1834,7 +1791,7 @@ def get_jalc_data_by_key(api, keyword): data['data'].get('doi') ) elif keyword == 'all': - for key in current_app.config.get("WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM"): + for key in current_app.config.get("WEKO_WORKSPACE_JALC_REQUIRED_ITEM"): result[key] = get_jalc_data_by_key(api, key).get(key) return result @@ -1855,12 +1812,15 @@ def get_jalc_publisher_data(data): list: A list of dictionaries containing the publisher names and their languages. """ result = list() - default_language = 'en' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for item in data: new_data = dict() new_data['@value'] = item.get('publisher_name') - new_data['@language'] = item.get('lang') if item.get('lang', None) else default_language + new_data['@language'] = item.get('lang', default_language) result.append(new_data) + return result def get_jalc_title_data(data): @@ -1879,10 +1839,12 @@ def get_jalc_title_data(data): list: A list containing a dictionary with the title and its language. """ result = list() - default_language = 'en' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) new_data = dict() new_data['@value'] = data.get('title') - new_data['@language'] = data.get('lang') if data.get('lang', None) else default_language + new_data['@language'] = data.get('lang', default_language) result.append(new_data) return result @@ -1903,7 +1865,9 @@ def get_jalc_creator_data(data): list: A list of lists, each containing a dictionary with the creator names and languages. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for item in data: if 'names' in item: @@ -1912,87 +1876,35 @@ def get_jalc_creator_data(data): new_data = dict() full_name = name_entry.get('last_name',"") + ' ' + name_entry.get('first_name',"") new_data['@value'] = full_name - new_data['@language'] = name_entry.get('lang') if name_entry.get('lang', None) else default_language + new_data['@language'] = name_entry.get('lang', default_language) result_creator.append(new_data) result.append(result_creator) return result -def get_jalc_contributor_data(data): - """Get contributor data from jalc. - - Get contributor name and form it as format: - { - '@value': name, - '@language': language - } - - Args: - data (list): A list of contributor names. - - Returns: - list: A list of dictionaries, each containing a contributor name and language. - """ - result = list() - for item in data: - name_data = item - if name_data: - result.append(get_basic_cinii_data(name_data)) - return result - - -def get_jalc_description_data(data): - """Get description data from jalc. - - Get description and form it as format: - { - '@value': description, - '@language': language, - '@type': type of description - } - - Args: - data (str): The description of the item. - - Returns: - list: A list containing a dictionary with the description, language, and type. - """ - result = list() - default_language = 'ja' - default_type = 'Abstract' - - new_data = dict() - new_data['@value'] = data - new_data['@type'] = default_type - new_data["@language"] = default_language - result.append(new_data) - return result - - -def get_jalc_subject_data(data): - """Get subject data from jalc. +def get_jalc_source_title_data(data): + """Get source title data from jalc. - Get subject and form it as format: + Get title and form it as format: { '@value': title, - '2language': language, - '@scheme': scheme of subject - '@URI': source of subject + '@language': language, } Args: - data (list): A list of subjects. + data (list): A list of source titles. Returns: - list: A list of dictionaries containing the subject data. + list: A list of dictionaries containing the source title data. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for sub in data: new_data = dict() - new_data["@type"] = sub.get('type') new_data["@value"] = sub.get('journal_title_name') - new_data['@language'] = sub.get('lang') if sub.get('lang', None) else default_language + new_data['@language'] = sub.get('lang', default_language) result.append(new_data) return result @@ -2073,7 +1985,7 @@ def get_jalc_date_data(data): def pack_data_with_multiple_type_jalc(data): """Map jalc multi data with type. - Args: + Args: data (list): A list of dictionaries containing journal IDs and their types. Returns: @@ -2107,6 +2019,24 @@ def get_jalc_product_identifier(data): return result +def get_jalc_autofill_item(item_id): + """Get JaLC autofill item. + + Args: + item_id (int): The item ID. + + Returns: + dict: A dictionary containing the JaLC required item data. + """ + jpcoar_item = get_item_id(item_id) + jalc_req_item = dict() + + for key in current_app.config.get("WEKO_WORKSPACE_JALC_REQUIRED_ITEM"): + if jpcoar_item.get(key) is not None: + jalc_req_item[key] = jpcoar_item.get(key) + return jalc_req_item + + @cached_api_json(timeout=50, key_prefix="datacite_data") def get_datacite_record_data(doi, item_type_id, exclude_duplicate_lang=True): """Get record data base on DATACITE API. @@ -2131,7 +2061,7 @@ def get_datacite_record_data(doi, item_type_id, exclude_duplicate_lang=True): return result elif items.form is not None: autofill_key_tree = get_autofill_key_tree( - items.form, get_cinii_autofill_item(item_type_id)) + items.form, get_datacite_autofill_item(item_type_id)) result = build_record_model( autofill_key_tree, api_data, items.schema, exclude_duplicate_lang ) @@ -2179,7 +2109,7 @@ def get_datacite_data_by_key(api, keyword): data['data'].get('id') ) elif keyword == 'all': - for key in current_app.config.get("WEKO_ITEMS_AUTOFILL_CINII_REQUIRED_ITEM"): + for key in current_app.config.get("WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM"): result[key] = get_datacite_data_by_key(api, key).get(key) return result @@ -2200,11 +2130,11 @@ def get_datacite_publisher_data(data): list: A list containing a dictionary with the publisher name and its language. """ result = list() - default_language = 'en' new_data = dict() - new_data["@type"] = data - - new_data['@language'] = default_language + new_data["@value"] = data + new_data['@language'] = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) result.append(new_data) return result @@ -2226,7 +2156,9 @@ def get_datacite_title_data(data): list: A list of dictionaries, each containing a title and its language. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for title in data: new_data = dict() new_data["@value"] = title.get('title') @@ -2252,13 +2184,15 @@ def get_datacite_creator_data(data): list: A list of lists, each containing a dictionary with the creator names and languages. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for item in data: if 'name' in item: result_creator = list() new_data = dict() new_data['@value'] = item.get('name') - new_data['@language'] = default_language + new_data['@language'] = item.get('lang', default_language) result_creator.append(new_data) result.append(result_creator) @@ -2281,13 +2215,15 @@ def get_datacite_contributor_data(data): list: A list of lists, each containing a dictionary with the contributor names and languages. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for item in data: if 'name' in item: result_creator = list() new_data = dict() new_data['@value'] = item.get('name') - new_data['@language'] = default_language + new_data['@language'] = item.get('lang', default_language) result_creator.append(new_data) result.append(result_creator) @@ -2311,13 +2247,15 @@ def get_datacite_description_data(data): list: A list of dictionaries, each containing a description, its language, and type. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) default_type = 'Abstract' for item in data: if 'description' in item: new_data = dict() new_data['@value'] = item.get('description') - new_data['@language'] = default_language + new_data['@language'] = item.get('lang', default_language) result.append(new_data) return result @@ -2329,7 +2267,7 @@ def get_datacite_subject_data(data): Get subject and form it as format: { '@value': title, - '2language': language, + '@language': language, '@scheme': scheme of subject '@URI': source of subject } @@ -2341,45 +2279,20 @@ def get_datacite_subject_data(data): list: A list of dictionaries, each containing a subject, its language, and scheme. """ result = list() - default_language = 'ja' + default_language = current_app.config.get( + "WEKO_WORKSPACE_DATA_DEFAULT_LANGUAGE", "en" + ) for sub in data: if 'subject' in sub: new_data = dict() new_data['@value'] = sub.get('subject') new_data['@scheme'] = sub.get('subjectScheme') + new_data['@language'] = sub.get('lang', default_language) result.append(new_data) return result -def get_datacite_date_data(data): - """Get publication date. - - Get publication date from datacite data - format: - { - '@value': date - '@type': type of date - } - - Args: - data (str): The publication date in the format 'YYYY-MM-DD'. - - Returns: - dict: A dictionary containing the publication date packed as '@value' and its type as '@type'. - """ - result = dict() - - result = dict() - if len(data.split('-')) != 3: - result['@value'] = None - result['@type'] = None - else: - result['@value'] = data - result['@type'] = 'Issued' - return result - - def pack_data_with_multiple_type_datacite(data): """Map datacite multi data with type. @@ -2391,15 +2304,10 @@ def pack_data_with_multiple_type_datacite(data): """ result = list() - if data: - new_data = dict() - new_data['@value'] = data - new_data['@type'] = "DOI" - result.append(new_data) - else: + for d in data: new_data = dict() - new_data['@value'] = None - new_data['@type'] = None + new_data['@value'] = d.get('identifier') + new_data['@type'] = d.get('identifierType') result.append(new_data) return result @@ -2426,3 +2334,21 @@ def get_datacite_product_identifier(data): new_data['@type'] = None result.append(new_data) return result + + +def get_datacite_autofill_item(item_id): + """Get DataCite autofill item. + + Args: + item_id (int): The item ID. + + Returns: + dict: A dictionary containing the DataCite required item data. + """ + jpcoar_item = get_item_id(item_id) + datacite_req_item = dict() + + for key in current_app.config.get("WEKO_WORKSPACE_DATACITE_REQUIRED_ITEM"): + if jpcoar_item.get(key) is not None: + datacite_req_item[key] = jpcoar_item.get(key) + return datacite_req_item diff --git a/nginx/weko.conf b/nginx/weko.conf index 95aeabafef..414df1ffd5 100644 --- a/nginx/weko.conf +++ b/nginx/weko.conf @@ -217,15 +217,17 @@ server { } - location /inbox { - location ~ ^/inbox/(.*) { - set $notify_id $1; - proxy_pass http://inbox_server/inbox/$notify_id; - } - location /inbox { - proxy_pass http://inbox_server/inbox; - proxy_set_header Host $http_host; + + location ~ ^/inbox/(.+) { + proxy_pass http://inbox_server; + proxy_set_header Host $http_host; + } + location ~ ^/inbox/?$ { + limit_except POST { + deny all; } + proxy_pass http://inbox_server; + proxy_set_header Host $http_host; } diff --git a/postgresql/ddl/WOA-06-jsonld_mapping.sql b/postgresql/ddl/WOA-06-jsonld_mapping.sql index da1db43efa..bc3556fa79 100644 --- a/postgresql/ddl/WOA-06-jsonld_mapping.sql +++ b/postgresql/ddl/WOA-06-jsonld_mapping.sql @@ -5,5 +5,5 @@ -- Data for Name: jsonld_mappings; Type: TABLE DATA; Schema: public; Owner: invenio -- -INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30001, 'デフォルトマッピング(シンプル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ識別子": "dc:type.value", "Resource Type.資源タイプ": "dc:type.rdf:resource", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.ページ数": "dcterms:medium.issueNumber", "Bibliographic Information.終了ページ": "dcterms:medium.numberOfPages", "Bibliographic Information.開始ページ": "dcterms:medium.pageEnd", "Bibliographic Information.号": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日": "hasPart.datePublished", "File.公開日.タイプ": "hasPart.datePublished.dateType", "File.公開日.公開日": "hasPart.datePublished.value", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.自由ライセンス": "", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language"}', 30001, 1, false); -INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30002, 'デフォルトマッピング(フル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "APC": "rioxxterms:apc", "APC.APC": "rioxxterms:apc.value", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Date": "datacite:date", "Date.日付": "datacite:date.value", "Date.日付タイプ": "datacite:date.dateType", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ": "dc:type.value", "Resource Type.資源タイプ識別子": "dc:type.rdf:resource", "Version": "datacite:version", "Version.バージョン情報": "datacite:version.value", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier": "jpcoar:identifier", "Identifier.識別子タイプ": "jpcoar:identifier.identifierType", "Identifier.識別子": "jpcoar:identifier.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Temporal": "dcterms:temporal", "Temporal.言語": "dcterms:temporal.language", "Temporal.時間的範囲": "dcterms:temporal.value", "Geo Location": "datacite:geoLocation", "Geo Location.位置情報(空間)": "datacite:geoLocation.datacite:geoLocationBox", "Geo Location.位置情報(空間).東部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:eastBoundLongitude", "Geo Location.位置情報(空間).北部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:northBoundLatitude", "Geo Location.位置情報(空間).南部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:southBoundLatitude", "Geo Location.位置情報(空間).西部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:westBoundLongitude", "Geo Location.位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace", "Geo Location.位置情報(自由記述).位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace.value", "Geo Location.位置情報(点)": "datacite:geoLocation.datacite:geoLocationPoint", "Geo Location.位置情報(点).緯度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLatitude", "Geo Location.位置情報(点).経度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLongitude", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Source Title": "jpcoar:sourceTitle", "Source Title.収録物名": "jpcoar:sourceTitle.value", "Source Title.言語": "jpcoar:sourceTitle.language", "Volume Number": "jpcoar:volume", "Volume Number.巻": "jpcoar:volume.value", "Issue Number": "jpcoar:issue", "Issue Number.号": "jpcoar:issue.value", "Number of Pages": "jpcoar:numPages", "Number of Pages.ページ数": "jpcoar:numPages.value", "Page Start": "jpcoar:pageStart", "Page Start.開始ページ": "jpcoar:pageStart.value", "Page End": "jpcoar:pageEnd", "Page End.終了ページ": "jpcoar:pageEnd.value", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.号": "dcterms:medium.issueNumber", "Bibliographic Information.ページ数": "dcterms:medium.numberOfPages", "Bibliographic Information.終了ページ": "dcterms:medium.pageEnd", "Bibliographic Information.開始ページ": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "Conference": "jpcoar:conference", "Conference.開催国": "jpcoar:conference.jpcoar:conferenceCountry", "Conference.開催期間": "jpcoar:conference.jpcoar:conferenceDate.language", "Conference.開催期間.言語": "jpcoar:conference.jpcoar:conferenceDate", "Conference.開催期間.終了日": "jpcoar:conference.jpcoar:conferenceDate.endDay", "Conference.開催期間.終了月": "jpcoar:conference.jpcoar:conferenceDate.endMonth", "Conference.開催期間.終了年": "jpcoar:conference.jpcoar:conferenceDate.endYear", "Conference.開催期間.開催期間": "jpcoar:conference.jpcoar:conferenceDate.value", "Conference.開催期間.開始日": "jpcoar:conference.jpcoar:conferenceDate.startDay", "Conference.開催期間.開始月": "jpcoar:conference.jpcoar:conferenceDate.startMonth", "Conference.開催期間.開始年": "jpcoar:conference.jpcoar:conferenceDate.startYear", "Conference.会議名": "jpcoar:conference.jpcoar:conferenceName", "Conference.会議名.会議名": "jpcoar:conference.jpcoar:conferenceName.value", "Conference.会議名.言語": "jpcoar:conference.jpcoar:conferenceName.language", "Conference.開催地": "jpcoar:conference.jpcoar:conferencePlace", "Conference.開催地.開催地": "jpcoar:conference.jpcoar:conferencePlace.value", "Conference.開催地.言語": "jpcoar:conference.jpcoar:conferencePlace.language", "Conference.回次": "jpcoar:conference.jpcoar:conferenceSequence", "Conference.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor", "Conference.主催機関.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor.value", "Conference.主催機関.言語": "jpcoar:conference.jpcoar:conferenceSponsor.language", "Conference.開催会場": "jpcoar:conference.jpcoar:conferenceVenue", "Conference.開催会場.開催会場": "jpcoar:conference.jpcoar:conferenceVenue.value", "Conference.開催会場.言語": "jpcoar:conference.jpcoar:conferenceVenue.language", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日": "hasPart.datePublished", "File.公開日.タイプ": "hasPart.datePublished.dateType", "File.公開日.公開日": "hasPart.datePublished.value", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.自由ライセンス": "", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language", "所蔵機関": "jpcoar:holdingAgent", "所蔵機関.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier", "所蔵機関.所蔵機関識別子.所蔵機関識別子スキーマ": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierScheme", "所蔵機関.所蔵機関識別子.所蔵機関識別子URI": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierURI", "所蔵機関.所蔵機関識別子.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.value", "所蔵機関.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName", "所蔵機関.所蔵機関名.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName.value", "所蔵機関.所蔵機関名.Language": "jpcoar:holdingAgent.jpcoar:holdingAgentName.language", "日付(リテラル)": "dcterms:date", "日付(リテラル).日付(リテラル)": "dcterms:date.value", "日付(リテラル).言語": "dcterms:date.language", "データセットシリーズ": "jpcoar:datasetSeries", "データセットシリーズ.Dataset Series": "jpcoar:datasetSeries.value", "出版者情報": "jpcoar:publisher", "出版者情報.出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace", "出版者情報.出版地(国名コード).出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace.value", "出版者情報.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription", "出版者情報.出版者注記.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription.value", "出版者情報.出版者注記.言語": "jpcoar:publisher.jpcoar:publisherDescription.language", "出版者情報.出版地": "jpcoar:publisher.dcndl:location", "出版者情報.出版地.出版地": "jpcoar:publisher.dcndl:location.value", "出版者情報.出版者名": "jpcoar:publisher.jpcoar:publisherName", "出版者情報.出版者名.出版者名": "jpcoar:publisher.jpcoar:publisherName.value", "出版者情報.出版者名.言語": "jpcoar:publisher.jpcoar:publisherName.language", "大きさ": "dcterms:extent", "大きさ.Extent": "dcterms:extent.value", "大きさ.Language": "dcterms:extent.language", "カタログ": "jpcoar:catalog", "カタログ.Access Rights": "jpcoar:catalog.dcterms:accessRights", "カタログ.Access Rights.Access Rights": "jpcoar:catalog.dcterms:accessRights.value", "カタログ.Access Rights.RDF Resource": "jpcoar:catalog.dcterms:accessRights.rdf:resource", "カタログ.Contributor": "jpcoar:catalog.jpcoar:contributor", "カタログ.Contributor.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName", "カタログ.Contributor.Contributor Name.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.value", "カタログ.Contributor.Contributor Name.Language": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.language", "カタログ.Contributor.Contributor Type": "jpcoar:catalog.jpcoar:contributor.contributorType", "カタログ.Description": "jpcoar:catalog.datacite:description", "カタログ.Description.Description": "jpcoar:catalog.datacite:description.value", "カタログ.Description.Language": "jpcoar:catalog.datacite:description.language", "カタログ.Description.Description Type": "jpcoar:catalog.datacite:description.descriptionType", "カタログ.File": "jpcoar:catalog.jpcoar:file", "カタログ.File.Object Type": "jpcoar:catalog.jpcoar:file.objectType", "カタログ.File.File URI": "jpcoar:catalog.jpcoar:file.jpcoar:URI", "カタログ.Identifier": "jpcoar:catalog.jpcoar:identifier", "カタログ.Identifier.Identifier": "jpcoar:catalog.jpcoar:identifier.value", "カタログ.Identifier.Identifier Type": "jpcoar:catalog.jpcoar:identifier.identifierType", "カタログ.License": "jpcoar:catalog.jpcoar:license", "カタログ.License.License": "jpcoar:catalog.jpcoar:license.value", "カタログ.License.Language": "jpcoar:catalog.jpcoar:license.language", "カタログ.License.RDF Resource": "jpcoar:catalog.jpcoar:license.rdf:resource", "カタログ.License.License Type": "jpcoar:catalog.jpcoar:license.licenseType", "カタログ.Rights": "jpcoar:catalog.dc:rights", "カタログ.Rights.Language": "jpcoar:catalog.dc:rights.language", "カタログ.Rights.RDF Resource": "jpcoar:catalog.dc:rights.rdf:resource", "カタログ.Rights.Rights": "jpcoar:catalog.dc:rights.value", "カタログ.Subject": "jpcoar:catalog.jpcoar:subject", "カタログ.Subject.Subject": "jpcoar:catalog.jpcoar:subject.value", "カタログ.Subject.Language": "jpcoar:catalog.jpcoar:subject.language", "カタログ.Subject.Subject Scheme": "jpcoar:catalog.jpcoar:subject.subjectScheme", "カタログ.Subject.Subject URI": "jpcoar:catalog.jpcoar:subject.subjectURI", "カタログ.Title": "jpcoar:catalog.dc:title", "カタログ.Title.Title": "jpcoar:catalog.dc:title.value", "カタログ.Title.Language": "jpcoar:catalog.dc:title.language", "原文の言語": "dcndl:originalLanguage", "原文の言語.Original Language": "dcndl:originalLanguage.value", "原文の言語.Language": "dcndl:originalLanguage.language", "部編名": "dcndl:volumeTitle", "部編名.部編名": "dcndl:volumeTitle.value", "部編名.Language": "dcndl:volumeTitle.language", "版": "dcndl:edition", "版.版": "dcndl:edition.value", "版.言語": "dcndl:edition.language", "物理的形態": "jpcoar:format", "物理的形態.物理的形態": "jpcoar:format.value", "物理的形態.Language": "jpcoar:format.language"}' , 30002, 1, false); +INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30001, 'デフォルトマッピング(シンプル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ識別子": "dc:type.value", "Resource Type.資源タイプ": "dc:type.rdf:resource", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.ページ数": "dcterms:medium.issueNumber", "Bibliographic Information.終了ページ": "dcterms:medium.numberOfPages", "Bibliographic Information.開始ページ": "dcterms:medium.pageEnd", "Bibliographic Information.号": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日.タイプ": "$Available", "File.公開日.公開日": "hasPart.datePublished", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language"}', 30001, 1, false); +INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30002, 'デフォルトマッピング(フル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "APC": "rioxxterms:apc", "APC.APC": "rioxxterms:apc.value", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Date": "datacite:date", "Date.日付": "datacite:date.value", "Date.日付タイプ": "datacite:date.dateType", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ": "dc:type.value", "Resource Type.資源タイプ識別子": "dc:type.rdf:resource", "Version": "datacite:version", "Version.バージョン情報": "datacite:version.value", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier": "jpcoar:identifier", "Identifier.識別子タイプ": "jpcoar:identifier.identifierType", "Identifier.識別子": "jpcoar:identifier.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Temporal": "dcterms:temporal", "Temporal.言語": "dcterms:temporal.language", "Temporal.時間的範囲": "dcterms:temporal.value", "Geo Location": "datacite:geoLocation", "Geo Location.位置情報(空間)": "datacite:geoLocation.datacite:geoLocationBox", "Geo Location.位置情報(空間).東部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:eastBoundLongitude", "Geo Location.位置情報(空間).北部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:northBoundLatitude", "Geo Location.位置情報(空間).南部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:southBoundLatitude", "Geo Location.位置情報(空間).西部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:westBoundLongitude", "Geo Location.位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace", "Geo Location.位置情報(自由記述).位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace.value", "Geo Location.位置情報(点)": "datacite:geoLocation.datacite:geoLocationPoint", "Geo Location.位置情報(点).緯度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLatitude", "Geo Location.位置情報(点).経度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLongitude", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Source Title": "jpcoar:sourceTitle", "Source Title.収録物名": "jpcoar:sourceTitle.value", "Source Title.言語": "jpcoar:sourceTitle.language", "Volume Number": "jpcoar:volume", "Volume Number.巻": "jpcoar:volume.value", "Issue Number": "jpcoar:issue", "Issue Number.号": "jpcoar:issue.value", "Number of Pages": "jpcoar:numPages", "Number of Pages.ページ数": "jpcoar:numPages.value", "Page Start": "jpcoar:pageStart", "Page Start.開始ページ": "jpcoar:pageStart.value", "Page End": "jpcoar:pageEnd", "Page End.終了ページ": "jpcoar:pageEnd.value", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.号": "dcterms:medium.issueNumber", "Bibliographic Information.ページ数": "dcterms:medium.numberOfPages", "Bibliographic Information.終了ページ": "dcterms:medium.pageEnd", "Bibliographic Information.開始ページ": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "Conference": "jpcoar:conference", "Conference.開催国": "jpcoar:conference.jpcoar:conferenceCountry", "Conference.開催期間": "jpcoar:conference.jpcoar:conferenceDate.language", "Conference.開催期間.言語": "jpcoar:conference.jpcoar:conferenceDate", "Conference.開催期間.終了日": "jpcoar:conference.jpcoar:conferenceDate.endDay", "Conference.開催期間.終了月": "jpcoar:conference.jpcoar:conferenceDate.endMonth", "Conference.開催期間.終了年": "jpcoar:conference.jpcoar:conferenceDate.endYear", "Conference.開催期間.開催期間": "jpcoar:conference.jpcoar:conferenceDate.value", "Conference.開催期間.開始日": "jpcoar:conference.jpcoar:conferenceDate.startDay", "Conference.開催期間.開始月": "jpcoar:conference.jpcoar:conferenceDate.startMonth", "Conference.開催期間.開始年": "jpcoar:conference.jpcoar:conferenceDate.startYear", "Conference.会議名": "jpcoar:conference.jpcoar:conferenceName", "Conference.会議名.会議名": "jpcoar:conference.jpcoar:conferenceName.value", "Conference.会議名.言語": "jpcoar:conference.jpcoar:conferenceName.language", "Conference.開催地": "jpcoar:conference.jpcoar:conferencePlace", "Conference.開催地.開催地": "jpcoar:conference.jpcoar:conferencePlace.value", "Conference.開催地.言語": "jpcoar:conference.jpcoar:conferencePlace.language", "Conference.回次": "jpcoar:conference.jpcoar:conferenceSequence", "Conference.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor", "Conference.主催機関.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor.value", "Conference.主催機関.言語": "jpcoar:conference.jpcoar:conferenceSponsor.language", "Conference.開催会場": "jpcoar:conference.jpcoar:conferenceVenue", "Conference.開催会場.開催会場": "jpcoar:conference.jpcoar:conferenceVenue.value", "Conference.開催会場.言語": "jpcoar:conference.jpcoar:conferenceVenue.language", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日.タイプ": "$Available", "File.公開日.公開日": "hasPart.datePublished", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language", "所蔵機関": "jpcoar:holdingAgent", "所蔵機関.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier", "所蔵機関.所蔵機関識別子.所蔵機関識別子スキーマ": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierScheme", "所蔵機関.所蔵機関識別子.所蔵機関識別子URI": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierURI", "所蔵機関.所蔵機関識別子.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.value", "所蔵機関.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName", "所蔵機関.所蔵機関名.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName.value", "所蔵機関.所蔵機関名.Language": "jpcoar:holdingAgent.jpcoar:holdingAgentName.language", "日付(リテラル)": "dcterms:date", "日付(リテラル).日付(リテラル)": "dcterms:date.value", "日付(リテラル).言語": "dcterms:date.language", "データセットシリーズ": "jpcoar:datasetSeries", "データセットシリーズ.Dataset Series": "jpcoar:datasetSeries.value", "出版者情報": "jpcoar:publisher", "出版者情報.出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace", "出版者情報.出版地(国名コード).出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace.value", "出版者情報.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription", "出版者情報.出版者注記.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription.value", "出版者情報.出版者注記.言語": "jpcoar:publisher.jpcoar:publisherDescription.language", "出版者情報.出版地": "jpcoar:publisher.dcndl:location", "出版者情報.出版地.出版地": "jpcoar:publisher.dcndl:location.value", "出版者情報.出版者名": "jpcoar:publisher.jpcoar:publisherName", "出版者情報.出版者名.出版者名": "jpcoar:publisher.jpcoar:publisherName.value", "出版者情報.出版者名.言語": "jpcoar:publisher.jpcoar:publisherName.language", "大きさ": "dcterms:extent", "大きさ.Extent": "dcterms:extent.value", "大きさ.Language": "dcterms:extent.language", "カタログ": "jpcoar:catalog", "カタログ.Access Rights": "jpcoar:catalog.dcterms:accessRights", "カタログ.Access Rights.Access Rights": "jpcoar:catalog.dcterms:accessRights.value", "カタログ.Access Rights.RDF Resource": "jpcoar:catalog.dcterms:accessRights.rdf:resource", "カタログ.Contributor": "jpcoar:catalog.jpcoar:contributor", "カタログ.Contributor.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName", "カタログ.Contributor.Contributor Name.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.value", "カタログ.Contributor.Contributor Name.Language": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.language", "カタログ.Contributor.Contributor Type": "jpcoar:catalog.jpcoar:contributor.contributorType", "カタログ.Description": "jpcoar:catalog.datacite:description", "カタログ.Description.Description": "jpcoar:catalog.datacite:description.value", "カタログ.Description.Language": "jpcoar:catalog.datacite:description.language", "カタログ.Description.Description Type": "jpcoar:catalog.datacite:description.descriptionType", "カタログ.File": "jpcoar:catalog.jpcoar:file", "カタログ.File.Object Type": "jpcoar:catalog.jpcoar:file.objectType", "カタログ.File.File URI": "jpcoar:catalog.jpcoar:file.jpcoar:URI", "カタログ.Identifier": "jpcoar:catalog.jpcoar:identifier", "カタログ.Identifier.Identifier": "jpcoar:catalog.jpcoar:identifier.value", "カタログ.Identifier.Identifier Type": "jpcoar:catalog.jpcoar:identifier.identifierType", "カタログ.License": "jpcoar:catalog.jpcoar:license", "カタログ.License.License": "jpcoar:catalog.jpcoar:license.value", "カタログ.License.Language": "jpcoar:catalog.jpcoar:license.language", "カタログ.License.RDF Resource": "jpcoar:catalog.jpcoar:license.rdf:resource", "カタログ.License.License Type": "jpcoar:catalog.jpcoar:license.licenseType", "カタログ.Rights": "jpcoar:catalog.dc:rights", "カタログ.Rights.Language": "jpcoar:catalog.dc:rights.language", "カタログ.Rights.RDF Resource": "jpcoar:catalog.dc:rights.rdf:resource", "カタログ.Rights.Rights": "jpcoar:catalog.dc:rights.value", "カタログ.Subject": "jpcoar:catalog.jpcoar:subject", "カタログ.Subject.Subject": "jpcoar:catalog.jpcoar:subject.value", "カタログ.Subject.Language": "jpcoar:catalog.jpcoar:subject.language", "カタログ.Subject.Subject Scheme": "jpcoar:catalog.jpcoar:subject.subjectScheme", "カタログ.Subject.Subject URI": "jpcoar:catalog.jpcoar:subject.subjectURI", "カタログ.Title": "jpcoar:catalog.dc:title", "カタログ.Title.Title": "jpcoar:catalog.dc:title.value", "カタログ.Title.Language": "jpcoar:catalog.dc:title.language", "原文の言語": "dcndl:originalLanguage", "原文の言語.Original Language": "dcndl:originalLanguage.value", "原文の言語.Language": "dcndl:originalLanguage.language", "部編名": "dcndl:volumeTitle", "部編名.部編名": "dcndl:volumeTitle.value", "部編名.Language": "dcndl:volumeTitle.language", "版": "dcndl:edition", "版.版": "dcndl:edition.value", "版.言語": "dcndl:edition.language", "物理的形態": "jpcoar:format", "物理的形態.物理的形態": "jpcoar:format.value", "物理的形態.Language": "jpcoar:format.language"}' , 30002, 1, false); diff --git a/scripts/demo/item_type.sql b/scripts/demo/item_type.sql index 6384054577..787bd0eee4 100644 --- a/scripts/demo/item_type.sql +++ b/scripts/demo/item_type.sql @@ -509,8 +509,8 @@ INSERT INTO public.oaiserver_schema (created, updated, id, schema_name, form_dat -- Data for Name: jsonld_mappings; Type: TABLE DATA; Schema: public; Owner: invenio -- -INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30001, 'デフォルトマッピング(シンプル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ識別子": "dc:type.value", "Resource Type.資源タイプ": "dc:type.rdf:resource", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.ページ数": "dcterms:medium.issueNumber", "Bibliographic Information.終了ページ": "dcterms:medium.numberOfPages", "Bibliographic Information.開始ページ": "dcterms:medium.pageEnd", "Bibliographic Information.号": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日": "hasPart.datePublished", "File.公開日.タイプ": "hasPart.datePublished.dateType", "File.公開日.公開日": "hasPart.datePublished.value", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.自由ライセンス": "", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language"}', 30001, 1, false); -INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30002, 'デフォルトマッピング(フル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "APC": "rioxxterms:apc", "APC.APC": "rioxxterms:apc.value", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Date": "datacite:date", "Date.日付": "datacite:date.value", "Date.日付タイプ": "datacite:date.dateType", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ": "dc:type.value", "Resource Type.資源タイプ識別子": "dc:type.rdf:resource", "Version": "datacite:version", "Version.バージョン情報": "datacite:version.value", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier": "jpcoar:identifier", "Identifier.識別子タイプ": "jpcoar:identifier.identifierType", "Identifier.識別子": "jpcoar:identifier.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Temporal": "dcterms:temporal", "Temporal.言語": "dcterms:temporal.language", "Temporal.時間的範囲": "dcterms:temporal.value", "Geo Location": "datacite:geoLocation", "Geo Location.位置情報(空間)": "datacite:geoLocation.datacite:geoLocationBox", "Geo Location.位置情報(空間).東部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:eastBoundLongitude", "Geo Location.位置情報(空間).北部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:northBoundLatitude", "Geo Location.位置情報(空間).南部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:southBoundLatitude", "Geo Location.位置情報(空間).西部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:westBoundLongitude", "Geo Location.位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace", "Geo Location.位置情報(自由記述).位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace.value", "Geo Location.位置情報(点)": "datacite:geoLocation.datacite:geoLocationPoint", "Geo Location.位置情報(点).緯度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLatitude", "Geo Location.位置情報(点).経度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLongitude", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Source Title": "jpcoar:sourceTitle", "Source Title.収録物名": "jpcoar:sourceTitle.value", "Source Title.言語": "jpcoar:sourceTitle.language", "Volume Number": "jpcoar:volume", "Volume Number.巻": "jpcoar:volume.value", "Issue Number": "jpcoar:issue", "Issue Number.号": "jpcoar:issue.value", "Number of Pages": "jpcoar:numPages", "Number of Pages.ページ数": "jpcoar:numPages.value", "Page Start": "jpcoar:pageStart", "Page Start.開始ページ": "jpcoar:pageStart.value", "Page End": "jpcoar:pageEnd", "Page End.終了ページ": "jpcoar:pageEnd.value", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.号": "dcterms:medium.issueNumber", "Bibliographic Information.ページ数": "dcterms:medium.numberOfPages", "Bibliographic Information.終了ページ": "dcterms:medium.pageEnd", "Bibliographic Information.開始ページ": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "Conference": "jpcoar:conference", "Conference.開催国": "jpcoar:conference.jpcoar:conferenceCountry", "Conference.開催期間": "jpcoar:conference.jpcoar:conferenceDate.language", "Conference.開催期間.言語": "jpcoar:conference.jpcoar:conferenceDate", "Conference.開催期間.終了日": "jpcoar:conference.jpcoar:conferenceDate.endDay", "Conference.開催期間.終了月": "jpcoar:conference.jpcoar:conferenceDate.endMonth", "Conference.開催期間.終了年": "jpcoar:conference.jpcoar:conferenceDate.endYear", "Conference.開催期間.開催期間": "jpcoar:conference.jpcoar:conferenceDate.value", "Conference.開催期間.開始日": "jpcoar:conference.jpcoar:conferenceDate.startDay", "Conference.開催期間.開始月": "jpcoar:conference.jpcoar:conferenceDate.startMonth", "Conference.開催期間.開始年": "jpcoar:conference.jpcoar:conferenceDate.startYear", "Conference.会議名": "jpcoar:conference.jpcoar:conferenceName", "Conference.会議名.会議名": "jpcoar:conference.jpcoar:conferenceName.value", "Conference.会議名.言語": "jpcoar:conference.jpcoar:conferenceName.language", "Conference.開催地": "jpcoar:conference.jpcoar:conferencePlace", "Conference.開催地.開催地": "jpcoar:conference.jpcoar:conferencePlace.value", "Conference.開催地.言語": "jpcoar:conference.jpcoar:conferencePlace.language", "Conference.回次": "jpcoar:conference.jpcoar:conferenceSequence", "Conference.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor", "Conference.主催機関.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor.value", "Conference.主催機関.言語": "jpcoar:conference.jpcoar:conferenceSponsor.language", "Conference.開催会場": "jpcoar:conference.jpcoar:conferenceVenue", "Conference.開催会場.開催会場": "jpcoar:conference.jpcoar:conferenceVenue.value", "Conference.開催会場.言語": "jpcoar:conference.jpcoar:conferenceVenue.language", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日": "hasPart.datePublished", "File.公開日.タイプ": "hasPart.datePublished.dateType", "File.公開日.公開日": "hasPart.datePublished.value", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.自由ライセンス": "", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language", "所蔵機関": "jpcoar:holdingAgent", "所蔵機関.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier", "所蔵機関.所蔵機関識別子.所蔵機関識別子スキーマ": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierScheme", "所蔵機関.所蔵機関識別子.所蔵機関識別子URI": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierURI", "所蔵機関.所蔵機関識別子.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.value", "所蔵機関.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName", "所蔵機関.所蔵機関名.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName.value", "所蔵機関.所蔵機関名.Language": "jpcoar:holdingAgent.jpcoar:holdingAgentName.language", "日付(リテラル)": "dcterms:date", "日付(リテラル).日付(リテラル)": "dcterms:date.value", "日付(リテラル).言語": "dcterms:date.language", "データセットシリーズ": "jpcoar:datasetSeries", "データセットシリーズ.Dataset Series": "jpcoar:datasetSeries.value", "出版者情報": "jpcoar:publisher", "出版者情報.出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace", "出版者情報.出版地(国名コード).出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace.value", "出版者情報.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription", "出版者情報.出版者注記.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription.value", "出版者情報.出版者注記.言語": "jpcoar:publisher.jpcoar:publisherDescription.language", "出版者情報.出版地": "jpcoar:publisher.dcndl:location", "出版者情報.出版地.出版地": "jpcoar:publisher.dcndl:location.value", "出版者情報.出版者名": "jpcoar:publisher.jpcoar:publisherName", "出版者情報.出版者名.出版者名": "jpcoar:publisher.jpcoar:publisherName.value", "出版者情報.出版者名.言語": "jpcoar:publisher.jpcoar:publisherName.language", "大きさ": "dcterms:extent", "大きさ.Extent": "dcterms:extent.value", "大きさ.Language": "dcterms:extent.language", "カタログ": "jpcoar:catalog", "カタログ.Access Rights": "jpcoar:catalog.dcterms:accessRights", "カタログ.Access Rights.Access Rights": "jpcoar:catalog.dcterms:accessRights.value", "カタログ.Access Rights.RDF Resource": "jpcoar:catalog.dcterms:accessRights.rdf:resource", "カタログ.Contributor": "jpcoar:catalog.jpcoar:contributor", "カタログ.Contributor.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName", "カタログ.Contributor.Contributor Name.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.value", "カタログ.Contributor.Contributor Name.Language": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.language", "カタログ.Contributor.Contributor Type": "jpcoar:catalog.jpcoar:contributor.contributorType", "カタログ.Description": "jpcoar:catalog.datacite:description", "カタログ.Description.Description": "jpcoar:catalog.datacite:description.value", "カタログ.Description.Language": "jpcoar:catalog.datacite:description.language", "カタログ.Description.Description Type": "jpcoar:catalog.datacite:description.descriptionType", "カタログ.File": "jpcoar:catalog.jpcoar:file", "カタログ.File.Object Type": "jpcoar:catalog.jpcoar:file.objectType", "カタログ.File.File URI": "jpcoar:catalog.jpcoar:file.jpcoar:URI", "カタログ.Identifier": "jpcoar:catalog.jpcoar:identifier", "カタログ.Identifier.Identifier": "jpcoar:catalog.jpcoar:identifier.value", "カタログ.Identifier.Identifier Type": "jpcoar:catalog.jpcoar:identifier.identifierType", "カタログ.License": "jpcoar:catalog.jpcoar:license", "カタログ.License.License": "jpcoar:catalog.jpcoar:license.value", "カタログ.License.Language": "jpcoar:catalog.jpcoar:license.language", "カタログ.License.RDF Resource": "jpcoar:catalog.jpcoar:license.rdf:resource", "カタログ.License.License Type": "jpcoar:catalog.jpcoar:license.licenseType", "カタログ.Rights": "jpcoar:catalog.dc:rights", "カタログ.Rights.Language": "jpcoar:catalog.dc:rights.language", "カタログ.Rights.RDF Resource": "jpcoar:catalog.dc:rights.rdf:resource", "カタログ.Rights.Rights": "jpcoar:catalog.dc:rights.value", "カタログ.Subject": "jpcoar:catalog.jpcoar:subject", "カタログ.Subject.Subject": "jpcoar:catalog.jpcoar:subject.value", "カタログ.Subject.Language": "jpcoar:catalog.jpcoar:subject.language", "カタログ.Subject.Subject Scheme": "jpcoar:catalog.jpcoar:subject.subjectScheme", "カタログ.Subject.Subject URI": "jpcoar:catalog.jpcoar:subject.subjectURI", "カタログ.Title": "jpcoar:catalog.dc:title", "カタログ.Title.Title": "jpcoar:catalog.dc:title.value", "カタログ.Title.Language": "jpcoar:catalog.dc:title.language", "原文の言語": "dcndl:originalLanguage", "原文の言語.Original Language": "dcndl:originalLanguage.value", "原文の言語.Language": "dcndl:originalLanguage.language", "部編名": "dcndl:volumeTitle", "部編名.部編名": "dcndl:volumeTitle.value", "部編名.Language": "dcndl:volumeTitle.language", "版": "dcndl:edition", "版.版": "dcndl:edition.value", "版.言語": "dcndl:edition.language", "物理的形態": "jpcoar:format", "物理的形態.物理的形態": "jpcoar:format.value", "物理的形態.Language": "jpcoar:format.language"}' , 30002, 1, false); +INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30001, 'デフォルトマッピング(シンプル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ識別子": "dc:type.value", "Resource Type.資源タイプ": "dc:type.rdf:resource", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.ページ数": "dcterms:medium.issueNumber", "Bibliographic Information.終了ページ": "dcterms:medium.numberOfPages", "Bibliographic Information.開始ページ": "dcterms:medium.pageEnd", "Bibliographic Information.号": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日.タイプ": "$Available", "File.公開日.公開日": "hasPart.datePublished", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language"}', 30001, 1, false); +INSERT INTO public.jsonld_mappings(created, updated, id, name, mapping, item_type_id, version_id, is_deleted) VALUES ('2025-03-21 17:00:00.000000', '2025-03-21 17:00:00.000000', 30002, 'デフォルトマッピング(フル)', '{"PubDate": "datePublished", "Title": "dc:title", "Title.タイトル": "dc:title.value", "Title.言語": "dc:title.language", "Alternative Title": "dcterms:alternative", "Alternative Title.その他のタイトル": "dcterms:alternative.value", "Alternative Title.言語": "dcterms:alternative.language", "Creator": "jpcoar:creator", "Creator.作成者名": "jpcoar:creator.jpcoar:givenName", "Creator.作成者名.名": "jpcoar:creator.jpcoar:givenName.value", "Creator.作成者名.言語": "jpcoar:creator.jpcoar:givenName.language", "Creator.作成者タイプ": "jpcoar:creator.creatorType", "Creator.作成者姓": "jpcoar:creator.jpcoar:familyName", "Creator.作成者姓.姓": "jpcoar:creator.jpcoar:familyName.value", "Creator.作成者姓.言語": "jpcoar:creator.jpcoar:familyName.language", "Creator.作成者メールアドレス": "jpcoar:creator.email", "Creator.作成者メールアドレス.メールアドレス": "jpcoar:creator.email.value", "Creator.作成者姓名": "jpcoar:creator.jpcoar:creatorName", "Creator.作成者姓名.姓名": "jpcoar:creator.jpcoar:creatorName.value", "Creator.作成者姓名.言語": "jpcoar:creator.jpcoar:creatorName.language", "Creator.作成者姓名.名前タイプ": "jpcoar:creator.jpcoar:creatorName.nameType", "Creator.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier", "Creator.作成者識別子.作成者識別子": "jpcoar:creator.jpcoar:nameIdentifier.value", "Creator.作成者識別子.作成者識別子URI": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者識別子.作成者識別子Scheme": "jpcoar:creator.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者所属": "jpcoar:creator.jpcoar:affiliation", "Creator.作成者所属.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName", "Creator.作成者所属.所属機関名.所属機関名": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.value", "Creator.作成者所属.所属機関名.言語": "jpcoar:creator.jpcoar:affiliation.jpcoar:affiliationName.language", "Creator.作成者所属.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier", "Creator.作成者所属.所属機関識別子.所属機関識別子": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Creator.作成者所属.所属機関識別子.所属機関識別子URI": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Creator.作成者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:creator.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Creator.作成者別名": "jpcoar:creator.jpcoar:creatorAlternative", "Creator.作成者別名.別名": "jpcoar:creator.jpcoar:creatorAlternative.value", "Creator.作成者別名.言語": "jpcoar:creator.jpcoar:creatorAlternative.language", "Contributor": "jpcoar:contributor", "Contributor.寄与者名": "jpcoar:contributor.jpcoar:givenName", "Contributor.寄与者名.名": "jpcoar:contributor.jpcoar:givenName.value", "Contributor.寄与者名.言語": "jpcoar:contributor.jpcoar:givenName.language", "Contributor.寄与者タイプ": "jpcoar:contributor.contributorType", "Contributor.寄与者姓": "jpcoar:contributor.jpcoar:familyName", "Contributor.寄与者姓.姓": "jpcoar:contributor.jpcoar:familyName.value", "Contributor.寄与者姓.言語": "jpcoar:contributor.jpcoar:familyName.language", "Contributor.寄与者メールアドレス": "jpcoar:contributor.email", "Contributor.寄与者メールアドレス.メールアドレス": "jpcoar:contributor.email.value", "Contributor.寄与者姓名": "jpcoar:contributor.jpcoar:contributorName", "Contributor.寄与者姓名.姓名": "jpcoar:contributor.jpcoar:contributorName.value", "Contributor.寄与者姓名.言語": "jpcoar:contributor.jpcoar:contributorName.language", "Contributor.寄与者姓名.名前タイプ": "jpcoar:contributor.jpcoar:contributorName.nameType", "Contributor.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier", "Contributor.寄与者識別子.寄与者識別子": "jpcoar:contributor.jpcoar:nameIdentifier.value", "Contributor.寄与者識別子.寄与者識別子URI": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者識別子.寄与者識別子Scheme": "jpcoar:contributor.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者所属": "jpcoar:contributor.jpcoar:affiliation", "Contributor.寄与者所属.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName", "Contributor.寄与者所属.所属機関名.所属機関名": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.value", "Contributor.寄与者所属.所属機関名.言語": "jpcoar:contributor.jpcoar:affiliationjpcoar:affiliationName.language", "Contributor.寄与者所属.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier", "Contributor.寄与者所属.所属機関識別子.所属機関識別子": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.value", "Contributor.寄与者所属.所属機関識別子.所属機関識別子URI": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierURI", "Contributor.寄与者所属.所属機関識別子.所属機関識別子Scheme": "jpcoar:contributor.jpcoar:affiliation.jpcoar:nameIdentifier.nameIdentifierScheme", "Contributor.寄与者別名": "jpcoar:contributor.jpcoar:contributorAlternative", "Contributor.寄与者別名.別名": "jpcoar:contributor.jpcoar:contributorAlternative.value", "Contributor.寄与者別名.言語": "jpcoar:contributor.jpcoar:contributorAlternative.language", "Access Rights": "dcterms:accessRights", "Access Rights.アクセス権": "dcterms:accessRights.value", "Access Rights.アクセス権URI": "dcterms:accessRights.rdf:resource", "APC": "rioxxterms:apc", "APC.APC": "rioxxterms:apc.value", "Rights": "dc:rights", "Rights.権利情報": "dc:rights.value", "Rights.言語": "dc:rights.language", "Rights.権利情報Resource": "dc:rights.rdf:resource", "Rights Holder": "jpcoar:rightsHolder", "Rights Holder.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier", "Rights Holder.権利者識別子.権利者識別子": "jpcoar:rightsHolder.jpcoar:nameIdentifier.value", "Rights Holder.権利者識別子.権利者識別子Scheme": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierScheme", "Rights Holder.権利者識別子.権利者識別子URI": "jpcoar:rightsHolder.jpcoar:nameIdentifier.nameIdentifierURI", "Rights Holder.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName", "Rights Holder.権利者名.言語": "jpcoar:rightsHolder.jpcoar:rightsHolderName.value", "Rights Holder.権利者名.権利者名": "jpcoar:rightsHolder.jpcoar:rightsHolderName.language", "Subject": "jpcoar:subject", "Subject.主題": "jpcoar:subject.value", "Subject.言語": "jpcoar:subject.language", "Subject.主題Scheme": "jpcoar:subject.subjectScheme", "Subject.主題URI": "jpcoar:subject.subjectURI", "Description": "datacite:description", "Description.内容記述": "datacite:description.value", "Description.言語": "datacite:description.language", "Description.内容記述タイプ": "datacite:description.descriptionType", "Publisher": "dc:publisher", "Publisher.出版者": "dc:publisher.value", "Publisher.言語": "dc:publisher.language", "Date": "datacite:date", "Date.日付": "datacite:date.value", "Date.日付タイプ": "datacite:date.dateType", "Language": "dc:language", "Language.言語": "dc:language.value", "Resource Type": "dc:type", "Resource Type.資源タイプ": "dc:type.value", "Resource Type.資源タイプ識別子": "dc:type.rdf:resource", "Version": "datacite:version", "Version.バージョン情報": "datacite:version.value", "Version Type": "oaire:version", "Version Type.査読の有無": "oaire:version.itemReviewed", "Version Type.出版タイプResource": "oaire:version.rdf:resource", "Version Type.出版タイプ": "oaire:version.value", "Identifier": "jpcoar:identifier", "Identifier.識別子タイプ": "jpcoar:identifier.identifierType", "Identifier.識別子": "jpcoar:identifier.value", "Identifier Registration": "jpcoar:identifierRegistration", "Identifier Registration.ID登録": "jpcoar:identifierRegistration.value", "Identifier Registration.ID登録タイプ": "jpcoar:identifierRegistration.identifierType", "Relation": "jpcoar:relation", "Relation.関連名称": "jpcoar:relation.jpcoar:relatedTitle", "Relation.関連名称.言語": "jpcoar:relation.jpcoar:relatedTitle.language", "Relation.関連名称.関連名称": "jpcoar:relation.jpcoar:relatedTitle.value", "Relation.関連タイプ": "jpcoar:relation.relationType", "Relation.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier", "Relation.関連識別子.関連識別子": "jpcoar:relation.jpcoar:relatedIdentifier.value", "Relation.関連識別子.識別子タイプ": "jpcoar:relation.jpcoar:relatedIdentifier.identifierType", "Temporal": "dcterms:temporal", "Temporal.言語": "dcterms:temporal.language", "Temporal.時間的範囲": "dcterms:temporal.value", "Geo Location": "datacite:geoLocation", "Geo Location.位置情報(空間)": "datacite:geoLocation.datacite:geoLocationBox", "Geo Location.位置情報(空間).東部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:eastBoundLongitude", "Geo Location.位置情報(空間).北部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:northBoundLatitude", "Geo Location.位置情報(空間).南部緯度": "datacite:geoLocation.datacite:geoLocationBox.datacite:southBoundLatitude", "Geo Location.位置情報(空間).西部経度": "datacite:geoLocation.datacite:geoLocationBox.datacite:westBoundLongitude", "Geo Location.位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace", "Geo Location.位置情報(自由記述).位置情報(自由記述)": "datacite:geoLocation.datacite:geoLocationPlace.value", "Geo Location.位置情報(点)": "datacite:geoLocation.datacite:geoLocationPoint", "Geo Location.位置情報(点).緯度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLatitude", "Geo Location.位置情報(点).経度": "datacite:geoLocation.datacite:geoLocationPoint.datacite:pointLongitude", "Funding Reference": "jpcoar:fundingReference", "Funding Reference.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber", "Funding Reference.研究課題番号.研究課題番号": "jpcoar:fundingReference.jpcoar:awardNumber.value", "Funding Reference.研究課題番号.研究課題番号タイプ": "jpcoar:fundingReference.jpcoar:awardNumber.awardNumberType", "Funding Reference.研究課題番号.研究課題番号URI": "jpcoar:fundingReference.jpcoar:awardNumber.awardURI", "Funding Reference.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle", "Funding Reference.研究課題名.研究課題名": "jpcoar:fundingReference.jpcoar:awardTitle.value", "Funding Reference.研究課題名.言語": "jpcoar:fundingReference.jpcoar:awardTitle.language", "Funding Reference.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier", "Funding Reference.助成機関識別子.助成機関識別子": "jpcoar:fundingReference.jpcoar:funderIdentifier.value", "Funding Reference.助成機関識別子.識別子タイプ": "jpcoar:fundingReference.jpcoar:funderIdentifier.funderIdentifierType", "Funding Reference.助成機関名": "jpcoar:fundingReference.jpcoar:funderName", "Funding Reference.助成機関名.助成機関名": "jpcoar:fundingReference.jpcoar:funderName.value", "Funding Reference.助成機関名.言語": "jpcoar:fundingReference.jpcoar:funderName.language", "Funding Reference.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier", "Funding Reference.プログラム情報識別子.プログラム情報識別子": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.value", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプ": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierType", "Funding Reference.プログラム情報識別子.プログラム情報識別子タイプURI": "jpcoar:fundingReference.jpcoar:fundingStreamIdentifier.fundingStreamIdentifierTypeURI", "Funding Reference.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream", "Funding Reference.プログラム情報.プログラム情報": "jpcoar:fundingReference.jpcoar:fundingStream.value", "Funding Reference.プログラム情報.言語": "jpcoar:fundingReference.jpcoar:fundingStream.language", "Source Identifier": "jpcoar:sourceIdentifier", "Source Identifier.収録物識別子": "jpcoar:sourceIdentifier.value", "Source Identifier.収録物識別子タイプ": "jpcoar:sourceIdentifier.identifierType", "Source Title": "jpcoar:sourceTitle", "Source Title.収録物名": "jpcoar:sourceTitle.value", "Source Title.言語": "jpcoar:sourceTitle.language", "Volume Number": "jpcoar:volume", "Volume Number.巻": "jpcoar:volume.value", "Issue Number": "jpcoar:issue", "Issue Number.号": "jpcoar:issue.value", "Number of Pages": "jpcoar:numPages", "Number of Pages.ページ数": "jpcoar:numPages.value", "Page Start": "jpcoar:pageStart", "Page Start.開始ページ": "jpcoar:pageStart.value", "Page End": "jpcoar:pageEnd", "Page End.終了ページ": "jpcoar:pageEnd.value", "Bibliographic Information": "dcterms:medium", "Bibliographic Information.発行日": "dcterms:medium.datePublished", "Bibliographic Information.発行日.日付": "dcterms:medium.datePublished.value", "Bibliographic Information.発行日.日付タイプ": "dcterms:medium.datePublished.dateType", "Bibliographic Information.号": "dcterms:medium.issueNumber", "Bibliographic Information.ページ数": "dcterms:medium.numberOfPages", "Bibliographic Information.終了ページ": "dcterms:medium.pageEnd", "Bibliographic Information.開始ページ": "dcterms:medium.pageStart", "Bibliographic Information.巻": "dcterms:medium.volumeNumber", "Bibliographic Information.雑誌名": "dcterms:medium.name", "Bibliographic Information.雑誌名.タイトル": "dcterms:medium.name.value", "Bibliographic Information.雑誌名.言語": "dcterms:medium.name.language", "Dissertation Number": "dcndl:dissertationNumber", "Dissertation Number.学位授与番号": "dcndl:dissertationNumber.value", "Degree Name": "dcndl:degreeName", "Degree Name.学位名": "dcndl:degreeName.value", "Degree Name.言語": "dcndl:degreeName.language", "Date Granted": "dcndl:dateGranted", "Date Granted.学位授与年月日": "dcndl:dateGranted.value", "Degree Grantor": "jpcoar:degreeGrantor", "Degree Grantor.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName", "Degree Grantor.学位授与機関名.言語": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.language", "Degree Grantor.学位授与機関名.学位授与機関名": "jpcoar:degreeGrantor.jpcoar:degreeGrantorName.value", "Degree Grantor.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier", "Degree Grantor.学位授与機関識別子.学位授与機関識別子": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.value", "Degree Grantor.学位授与機関識別子.学位授与機関識別子Scheme": "jpcoar:degreeGrantor.jpcoar:nameIdentifier.nameIdentifierScheme", "Conference": "jpcoar:conference", "Conference.開催国": "jpcoar:conference.jpcoar:conferenceCountry", "Conference.開催期間": "jpcoar:conference.jpcoar:conferenceDate.language", "Conference.開催期間.言語": "jpcoar:conference.jpcoar:conferenceDate", "Conference.開催期間.終了日": "jpcoar:conference.jpcoar:conferenceDate.endDay", "Conference.開催期間.終了月": "jpcoar:conference.jpcoar:conferenceDate.endMonth", "Conference.開催期間.終了年": "jpcoar:conference.jpcoar:conferenceDate.endYear", "Conference.開催期間.開催期間": "jpcoar:conference.jpcoar:conferenceDate.value", "Conference.開催期間.開始日": "jpcoar:conference.jpcoar:conferenceDate.startDay", "Conference.開催期間.開始月": "jpcoar:conference.jpcoar:conferenceDate.startMonth", "Conference.開催期間.開始年": "jpcoar:conference.jpcoar:conferenceDate.startYear", "Conference.会議名": "jpcoar:conference.jpcoar:conferenceName", "Conference.会議名.会議名": "jpcoar:conference.jpcoar:conferenceName.value", "Conference.会議名.言語": "jpcoar:conference.jpcoar:conferenceName.language", "Conference.開催地": "jpcoar:conference.jpcoar:conferencePlace", "Conference.開催地.開催地": "jpcoar:conference.jpcoar:conferencePlace.value", "Conference.開催地.言語": "jpcoar:conference.jpcoar:conferencePlace.language", "Conference.回次": "jpcoar:conference.jpcoar:conferenceSequence", "Conference.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor", "Conference.主催機関.主催機関": "jpcoar:conference.jpcoar:conferenceSponsor.value", "Conference.主催機関.言語": "jpcoar:conference.jpcoar:conferenceSponsor.language", "Conference.開催会場": "jpcoar:conference.jpcoar:conferenceVenue", "Conference.開催会場.開催会場": "jpcoar:conference.jpcoar:conferenceVenue.value", "Conference.開催会場.言語": "jpcoar:conference.jpcoar:conferenceVenue.language", "File": "hasPart", "File.アクセス": "hasPart.dcterms:accessRights", "File.公開日.タイプ": "$Available", "File.公開日.公開日": "hasPart.datePublished", "File.表示形式": "hasPart.jpcoar:format", "File.日付": "hasPart.datacite:date", "File.日付.日付タイプ": "hasPart.datacite:date.dateType", "File.日付.日付": "hasPart.datacite:date.value", "File.ファイル名": "hasPart.name", "File.サイズ": "hasPart.jpcoar:extent", "File.サイズ.サイズ": "hasPart.jpcoar:extent.value", "File.フォーマット": "hasPart.jpcoar:mimeType", "File.グループ": "hasPart.department", "File.ライセンス": "hasPart.license", "File.本文URL": "hasPart.jpcoar:URI", "File.本文URL.ラベル": "hasPart.@id", "File.本文URL.オブジェクトタイプ": "hasPart.jpcoar:URI.objectType", "File.本文URL.本文URL": "hasPart.jpcoar:URI.value", "File.バージョン情報": "hasPart.datacite:version", "Heading": "headline", "Heading.大見出し": "headline.value", "Heading.小見出し": "headline.alternativeHeadline", "Heading.言語": "headline.language", "所蔵機関": "jpcoar:holdingAgent", "所蔵機関.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier", "所蔵機関.所蔵機関識別子.所蔵機関識別子スキーマ": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierScheme", "所蔵機関.所蔵機関識別子.所蔵機関識別子URI": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.nameIdentifierURI", "所蔵機関.所蔵機関識別子.所蔵機関識別子": "jpcoar:holdingAgent.jpcoar:holdingAgentNameIdentifier.value", "所蔵機関.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName", "所蔵機関.所蔵機関名.所蔵機関名": "jpcoar:holdingAgent.jpcoar:holdingAgentName.value", "所蔵機関.所蔵機関名.Language": "jpcoar:holdingAgent.jpcoar:holdingAgentName.language", "日付(リテラル)": "dcterms:date", "日付(リテラル).日付(リテラル)": "dcterms:date.value", "日付(リテラル).言語": "dcterms:date.language", "データセットシリーズ": "jpcoar:datasetSeries", "データセットシリーズ.Dataset Series": "jpcoar:datasetSeries.value", "出版者情報": "jpcoar:publisher", "出版者情報.出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace", "出版者情報.出版地(国名コード).出版地(国名コード)": "jpcoar:publisher.dcndl:publicationPlace.value", "出版者情報.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription", "出版者情報.出版者注記.出版者注記": "jpcoar:publisher.jpcoar:publisherDescription.value", "出版者情報.出版者注記.言語": "jpcoar:publisher.jpcoar:publisherDescription.language", "出版者情報.出版地": "jpcoar:publisher.dcndl:location", "出版者情報.出版地.出版地": "jpcoar:publisher.dcndl:location.value", "出版者情報.出版者名": "jpcoar:publisher.jpcoar:publisherName", "出版者情報.出版者名.出版者名": "jpcoar:publisher.jpcoar:publisherName.value", "出版者情報.出版者名.言語": "jpcoar:publisher.jpcoar:publisherName.language", "大きさ": "dcterms:extent", "大きさ.Extent": "dcterms:extent.value", "大きさ.Language": "dcterms:extent.language", "カタログ": "jpcoar:catalog", "カタログ.Access Rights": "jpcoar:catalog.dcterms:accessRights", "カタログ.Access Rights.Access Rights": "jpcoar:catalog.dcterms:accessRights.value", "カタログ.Access Rights.RDF Resource": "jpcoar:catalog.dcterms:accessRights.rdf:resource", "カタログ.Contributor": "jpcoar:catalog.jpcoar:contributor", "カタログ.Contributor.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName", "カタログ.Contributor.Contributor Name.Contributor Name": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.value", "カタログ.Contributor.Contributor Name.Language": "jpcoar:catalog.jpcoar:contributor.jpcoar:contributorName.language", "カタログ.Contributor.Contributor Type": "jpcoar:catalog.jpcoar:contributor.contributorType", "カタログ.Description": "jpcoar:catalog.datacite:description", "カタログ.Description.Description": "jpcoar:catalog.datacite:description.value", "カタログ.Description.Language": "jpcoar:catalog.datacite:description.language", "カタログ.Description.Description Type": "jpcoar:catalog.datacite:description.descriptionType", "カタログ.File": "jpcoar:catalog.jpcoar:file", "カタログ.File.Object Type": "jpcoar:catalog.jpcoar:file.objectType", "カタログ.File.File URI": "jpcoar:catalog.jpcoar:file.jpcoar:URI", "カタログ.Identifier": "jpcoar:catalog.jpcoar:identifier", "カタログ.Identifier.Identifier": "jpcoar:catalog.jpcoar:identifier.value", "カタログ.Identifier.Identifier Type": "jpcoar:catalog.jpcoar:identifier.identifierType", "カタログ.License": "jpcoar:catalog.jpcoar:license", "カタログ.License.License": "jpcoar:catalog.jpcoar:license.value", "カタログ.License.Language": "jpcoar:catalog.jpcoar:license.language", "カタログ.License.RDF Resource": "jpcoar:catalog.jpcoar:license.rdf:resource", "カタログ.License.License Type": "jpcoar:catalog.jpcoar:license.licenseType", "カタログ.Rights": "jpcoar:catalog.dc:rights", "カタログ.Rights.Language": "jpcoar:catalog.dc:rights.language", "カタログ.Rights.RDF Resource": "jpcoar:catalog.dc:rights.rdf:resource", "カタログ.Rights.Rights": "jpcoar:catalog.dc:rights.value", "カタログ.Subject": "jpcoar:catalog.jpcoar:subject", "カタログ.Subject.Subject": "jpcoar:catalog.jpcoar:subject.value", "カタログ.Subject.Language": "jpcoar:catalog.jpcoar:subject.language", "カタログ.Subject.Subject Scheme": "jpcoar:catalog.jpcoar:subject.subjectScheme", "カタログ.Subject.Subject URI": "jpcoar:catalog.jpcoar:subject.subjectURI", "カタログ.Title": "jpcoar:catalog.dc:title", "カタログ.Title.Title": "jpcoar:catalog.dc:title.value", "カタログ.Title.Language": "jpcoar:catalog.dc:title.language", "原文の言語": "dcndl:originalLanguage", "原文の言語.Original Language": "dcndl:originalLanguage.value", "原文の言語.Language": "dcndl:originalLanguage.language", "部編名": "dcndl:volumeTitle", "部編名.部編名": "dcndl:volumeTitle.value", "部編名.Language": "dcndl:volumeTitle.language", "版": "dcndl:edition", "版.版": "dcndl:edition.value", "版.言語": "dcndl:edition.language", "物理的形態": "jpcoar:format", "物理的形態.物理的形態": "jpcoar:format.value", "物理的形態.Language": "jpcoar:format.language"}' , 30002, 1, false); -- diff --git a/scripts/instance.cfg b/scripts/instance.cfg index 53a4241989..fbe9ce2fa0 100644 --- a/scripts/instance.cfg +++ b/scripts/instance.cfg @@ -577,8 +577,8 @@ WEKO_ITEMS_UI_HIDE_AUTO_FILL_METADATA=['利用申請', '二段階利用申請', INVENIO_RESYNC_SAVE_PATH='{{ environ('TMPDIR') }}/resync' -WEKO_AUTHORS_EXPORT_TEMP_FOLDER_PATH='{{ environ('TMPDIR') }}/authors_export' -WEKO_AUTHORS_IMPORT_TEMP_FOLDER_PATH='{{ environ('TMPDIR') }}/authors_import' +WEKO_AUTHORS_EXPORT_TMP_DIR='authors_export' +WEKO_AUTHORS_IMPORT_TMP_DIR='authors_import' # Robot txt