diff --git a/modules/weko-items-ui/weko_items_ui/static/js/weko_items_ui/app.js b/modules/weko-items-ui/weko_items_ui/static/js/weko_items_ui/app.js index 2cf6e7b653..03be6f626a 100644 --- a/modules/weko-items-ui/weko_items_ui/static/js/weko_items_ui/app.js +++ b/modules/weko-items-ui/weko_items_ui/static/js/weko_items_ui/app.js @@ -2176,6 +2176,14 @@ function validateThumbnails(rootScope, scope, itemSizeCheckFlg, files) { }; $scope.isExistingTitle = function () { + // The subitem key used for an item type's title varies by item + // type (e.g. `subitem_item_title` vs. + // `subitem_restricted_access_item_title`). It is resolved + // server-side (weko_workflow.views.get_title_subitem_keys) and + // passed down via these hidden inputs, falling back to the + // standard JPCOAR name if unresolved. + let titleSubKey = $("#title_subitem_key").val() || "subitem_item_title"; + let titleLanguageKey = $("#title_language_subitem_key").val() || "subitem_item_title_language"; let model = $rootScope.recordsVM.invenioRecordsModel; if (Object.keys(model).length === 0 && model.constructor === Object) { return false; @@ -2183,7 +2191,7 @@ function validateThumbnails(rootScope, scope, itemSizeCheckFlg, files) { let isExisted = false; for (let key in model) { if (model.hasOwnProperty(key) && model[key].length > 0) { - let title = model[key][0]['subitem_item_title']; + let title = model[key][0][titleSubKey]; if (title){ $scope.item_tile_key = key let activity_id= title.match(/A-[0-9]{8}-[0-9]{5}/g); @@ -2194,7 +2202,7 @@ function validateThumbnails(rootScope, scope, itemSizeCheckFlg, files) { if (title && $("#auto_fill_title").val() !== '""') { $scope.setFormReadOnly(key); setTimeout(function () { - $("input[name='subitem_item_title'], select[name='subitem_item_title_language']").attr("disabled", "disabled"); + $("input[name='" + titleSubKey + "'], select[name='" + titleLanguageKey + "']").attr("disabled", "disabled"); }, 3000); isExisted = true; break; @@ -2226,21 +2234,28 @@ function validateThumbnails(rootScope, scope, itemSizeCheckFlg, files) { userName = JSON.parse(userInfoData).results["subitem_displayname"]; } } - let titleSubKey = "subitem_item_title"; - let titleLanguageKey = "subitem_item_title_language"; + let titleSubKey = $("#title_subitem_key").val() || "subitem_item_title"; + let titleLanguageKey = $("#title_language_subitem_key").val() || "subitem_item_title_language"; let recordsVM = $rootScope["recordsVM"]; Object.entries(recordsVM["invenioRecordsSchema"].properties).forEach( function ([key, value]) { if (value && value.type === "array" && value.items) { if (value.items.properties && value.items.properties.hasOwnProperty(titleSubKey)) { + // Not every title property declares a language sub-key + // (e.g. item_1578299480500 on item type 3007/3008) -- + // assigning an undeclared property breaks the whole + // array item and the record fails to save with a title. + let hasLanguageKey = value.items.properties.hasOwnProperty(titleLanguageKey); $scope.item_tile_key = key; let enTitle = {}; let jaTitle = {}; // TitleData and Username are mandatory, dataType either way enTitle[titleSubKey] = dataType ? [dataType, titleData['en'], userName].join(" - ") : [titleData['en'], userName].join(" - "); - enTitle[titleLanguageKey] = "en"; jaTitle[titleSubKey] = dataType ? [dataType, titleData['ja'], userName].join(" - ") : [titleData['ja'], userName].join(" - "); - jaTitle[titleLanguageKey] = "ja"; + if (hasLanguageKey) { + enTitle[titleLanguageKey] = "en"; + jaTitle[titleLanguageKey] = "ja"; + } recordsVM["invenioRecordsModel"][key] = [jaTitle, enTitle]; } } @@ -5266,8 +5281,8 @@ function validateThumbnails(rootScope, scope, itemSizeCheckFlg, files) { let defaultTitleEn = titleData['en'] + userName; let defaultTitleJa = titleData['ja'] + userName; - let titleSubKey = "subitem_item_title"; - let titleLanguageKey = "subitem_item_title_language"; + let titleSubKey = $("#title_subitem_key").val() || "subitem_item_title"; + let titleLanguageKey = $("#title_language_subitem_key").val() || "subitem_item_title_language"; let selectedUsageApplicationIDs = [] let model = $rootScope["recordsVM"].invenioRecordsModel; diff --git a/modules/weko-items-ui/weko_items_ui/templates/weko_items_ui/iframe/item_edit.html b/modules/weko-items-ui/weko_items_ui/templates/weko_items_ui/iframe/item_edit.html index 590b886464..69983f3627 100644 --- a/modules/weko-items-ui/weko_items_ui/templates/weko_items_ui/iframe/item_edit.html +++ b/modules/weko-items-ui/weko_items_ui/templates/weko_items_ui/iframe/item_edit.html @@ -489,6 +489,8 @@

Contributor

+ + diff --git a/modules/weko-workflow/tests/test_views.py b/modules/weko-workflow/tests/test_views.py index 8ab92284b0..ce7c374612 100644 --- a/modules/weko-workflow/tests/test_views.py +++ b/modules/weko-workflow/tests/test_views.py @@ -35,7 +35,8 @@ check_authority, display_guest_activity, display_guest_activity_item_application, - render_guest_workflow) + render_guest_workflow, + get_title_subitem_keys) from marshmallow.exceptions import ValidationError from weko_records_ui.models import FileOnetimeDownload, FilePermission from weko_records.models import ItemMetadata, ItemReference @@ -4627,6 +4628,28 @@ def prepare_activity(act_id, recid, with_item=False, is_deleted=False): assert res.status_code == 200 assert json.loads(res.data) == {"code": 200, 'for_delete': False, "is_deleted": True} +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_views.py::test_get_title_subitem_keys -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko_workflow/.tox/c1/tmp +def test_get_title_subitem_keys(app, item_type): + """get_title_subitem_keys() should resolve the title subitem key names + from the item type's own jpcoar_mapping, not a hardcoded name -- the + title subitem key varies by item type (e.g. subitem_item_title vs. + subitem_restricted_access_item_title).""" + item_type_id = item_type[0]["id"] + # tests/data/item_type/item_type_mapping.json maps + # item_1617186331708.jpcoar_mapping.title to these subitem keys. + title_subitem_key, title_language_subitem_key = \ + get_title_subitem_keys(item_type_id) + assert title_subitem_key == "subitem_1551255647225" + assert title_language_subitem_key == "subitem_1551255648112" + + +def test_get_title_subitem_keys_no_mapping(app, db): + """Falls back to empty strings (never raises) when there is no + item_type_mapping for the given id, or no id at all.""" + assert get_title_subitem_keys(None) == ("", "") + assert get_title_subitem_keys(999999) == ("", "") + + # .tox/c1/bin/pytest --cov=weko_workflow tests/test_views.py::test_display_activity_nologin -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko_workflow/.tox/c1/tmp def test_display_activity_nologin(client,db_register2,mocker): """Test of display activity.""" diff --git a/modules/weko-workflow/weko_workflow/views.py b/modules/weko-workflow/weko_workflow/views.py index 8d4135ee57..fef6110c29 100644 --- a/modules/weko-workflow/weko_workflow/views.py +++ b/modules/weko-workflow/weko_workflow/views.py @@ -58,7 +58,7 @@ from weko_items_ui.utils import check_item_is_being_edit, get_workflow_by_item_type_id, \ get_current_user from weko_logging.activity_logger import UserActivityLogger -from weko_records.api import FeedbackMailList, RequestMailList, ItemLink, ItemTypes, ItemApplication +from weko_records.api import FeedbackMailList, RequestMailList, ItemLink, ItemTypes, ItemApplication, Mapping from weko_records.models import ItemMetadata from weko_records.serializers.utils import get_item_type_name from weko_records_ui.models import FilePermission @@ -797,6 +797,37 @@ def verify_deletion(activity_id="0"): return jsonify(res), 200 +def get_title_subitem_keys(item_type_id): + """Resolve the subitem key names used for an item type's title. + + Different item types name their "title" subitem differently + (e.g. ``subitem_item_title`` for the standard JPCOAR title + property, ``subitem_restricted_access_item_title`` for the + restricted-access reference item type). Rather than hardcoding + one name, resolve it the same way + :meth:`weko_deposit.api.WekoDeposit.get_titles` does: via the + item type's ``jpcoar_mapping.title`` mapping entry. + + :param item_type_id: ID of the item type. + :returns: (title_subitem_key, title_language_subitem_key), each + an empty string if it could not be resolved. + """ + title_subitem_key = "" + title_language_subitem_key = "" + item_type_mapping = Mapping.get_record(item_type_id) if item_type_id else None + if item_type_mapping: + for mapping_value in item_type_mapping.values(): + if not isinstance(mapping_value, dict): + continue + jpcoar_title = (mapping_value.get('jpcoar_mapping') or {}).get('title') + if isinstance(jpcoar_title, dict) and jpcoar_title.get('@value'): + title_subitem_key = jpcoar_title.get('@value') + title_language_subitem_key = jpcoar_title.get( + '@attributes', {}).get('xml:lang') or "" + break + return title_subitem_key, title_language_subitem_key + + @workflow_blueprint.route('/activity/detail/', methods=['GET', 'POST']) @login_required_customize @@ -955,6 +986,8 @@ def display_activity(activity_id="0", community_id=None): step_item_login_url = None term_and_condition_content = '' title = "" + title_subitem_key = "" + title_language_subitem_key = "" user_lock_key = "workflow_userlock_activity_{}".format(str(current_user.get_id())) if action_endpoint in ['item_login', 'item_login_application', @@ -1007,6 +1040,8 @@ def display_activity(activity_id="0", community_id=None): title = auto_fill_title(item_type_name) + title_subitem_key, title_language_subitem_key = \ + get_title_subitem_keys(workflow_detail.itemtype_id) show_autofill_metadata = is_show_autofill_metadata(item_type_name) is_hidden_pubdate_value = is_hidden_pubdate(item_type_name) @@ -1209,6 +1244,8 @@ def display_activity(activity_id="0", community_id=None): approval_preview=approval_preview, auto_fill_data_type=data_type, auto_fill_title=title, + title_subitem_key=title_subitem_key, + title_language_subitem_key=title_language_subitem_key, community_id=community_id, cur_step=cur_step, contributors=contributors,