From d4e73d99621072996315220cfa3bf8fa398c1ce9 Mon Sep 17 00:00:00 2001 From: asaoka Date: Thu, 3 Sep 2026 03:05:47 +0000 Subject: [PATCH] weko#62759 fix title auto-fill leaving the title blank for item types whose title subitem key isn't subitem_item_title autoSetTitle()/isExistingTitle()/updateTitleForOutputReport() in weko_items_ui/app.js hardcoded the title subitem key names (subitem_item_title / subitem_item_title_language). Item types whose title property uses a different subitem key (e.g. subitem_restricted_access_item_title, used by the restricted-access reference item type from scripts/demo/resticted_access.sql) never match `value.items.properties.hasOwnProperty(titleSubKey)`, so the autofilled title is silently never written into the record model and the title field is left blank. Resolve the subitem key names dynamically instead, from the item type's own item_type_mapping (the same jpcoar_mapping.title source weko_deposit.api.WekoDeposit.get_titles already uses), via a new weko_workflow.views.get_title_subitem_keys() passed through display_activity()'s render_template call and two new hidden inputs in item_edit.html (title_subitem_key / title_language_subitem_key), falling back to the standard subitem_item_title(/_language) names if nothing can be resolved. Also guard the language sub-key assignment in autoSetTitle() behind an explicit schema check: some title properties (e.g. item_1578299480500 on item types 3007/3008) declare no language sibling field at all, and unconditionally assigning one produced an array item the record could not be saved with. Adds weko_workflow/tests/test_views.py::test_get_title_subitem_keys(_no_mapping) covering the new helper. --- .../static/js/weko_items_ui/app.js | 31 +++++++++++---- .../weko_items_ui/iframe/item_edit.html | 2 + modules/weko-workflow/tests/test_views.py | 25 +++++++++++- modules/weko-workflow/weko_workflow/views.py | 39 ++++++++++++++++++- 4 files changed, 87 insertions(+), 10 deletions(-) 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,