From 1947830b38373907c3f03269804207ccdc80edcf Mon Sep 17 00:00:00 2001 From: Bertrand Zuchuat Date: Tue, 10 Oct 2023 17:51:09 +0200 Subject: [PATCH] config: add document advanced seach flag * Adds configuration for enabled/disabled advanced search. * Adds a new field in the document schema to group call number for holdings and items. * Adds a new endpoint for advanced search config. Co-Authored-by: Bertrand Zuchuat --- rero_ils/config.py | 110 +++++++++++++++++- rero_ils/modules/documents/api_views.py | 56 ++++++++- .../v7/documents/document-v0.0.1.json | 15 ++- rero_ils/modules/patrons/views.py | 2 + rero_ils/modules/utils.py | 11 ++ tests/api/documents/test_documents_rest.py | 28 +++++ 6 files changed, 215 insertions(+), 7 deletions(-) diff --git a/rero_ils/config.py b/rero_ils/config.py index a9be9ac806..e01420f08e 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -159,6 +159,9 @@ def _(x): RERO_ILS_ILL_REQUEST_ON_GLOBAL_VIEW = True RERO_ILS_ILL_DEFAULT_SOURCE = 'RERO +' +# DOCUMENT ADVANCED SEARCH +RERO_ILS_APP_DOCUMENT_ADVANCED_SEARCH = True + # Rate limiting # ============= #: Storage for ratelimiter. @@ -3038,7 +3041,7 @@ def _(x): 'base_url': os.environ.get('RERO_ILS_MEF_CONCEPTS_URL', 'https://mef.rero.ch/api/concepts'), 'sources': ['idref'], 'filters': [ - {'idref.bnf_type': 'genre/forme Rameau'} + {'idref.bnf_type': 'genre/forme Rameau'} ] }, 'places': { @@ -3632,3 +3635,108 @@ def _(x): RERO_ILS_PASSWORD_SPECIAL_CHAR = False RERO_ILS_PASSWORD_GENERATOR = 'rero_ils.modules.utils:password_generator' RERO_ILS_PASSWORD_VALIDATOR = 'rero_ils.modules.utils:password_validator' + +# ADVANCED SEARCH CONFIG +# ====================== +RERO_ILS_APP_ADVANCED_SEARCH_CONFIG = [ + { + 'label': 'Title', + 'value': 'title', + 'field': 'title.*', + }, + { + 'label': 'Responsibility statement', + 'value': 'responsibilityStatement', + 'field': 'responsibilityStatement.value', + }, + { + 'label': 'Contribution', + 'value': 'contribution', + 'field': 'contribution.entity.*', + }, + { + 'label': 'Country', + 'value': 'country', + 'field': 'provisionActivity.country', + }, + { + 'label': 'Canton', + 'value': 'canton', + 'field': 'provisionActivity.canton', + }, + { + 'label': 'Provision activity statement', + 'value': 'provisionActivityStatement', + 'field': 'provisionActivity.statement.label', + }, + { + 'label': 'Series statement', + 'value': 'seriesStatement', + 'field': 'seriesStatement.*', + }, + { + 'label': 'Identifier', + 'value': 'identifiedBy', + 'field': 'identifiedBy.value', + }, + { + 'label': 'ISBN', + 'value': 'isbn', + 'field': 'isbn', + }, + { + 'label': 'ISSN', + 'value': 'issn', + 'field': 'issn', + }, + { + 'label': 'Genre, form', + 'value': 'genreForm', + 'field': 'genreForm.entity.*', + }, + { + 'label': 'Subject', + 'value': 'subjects', + 'field': 'subjects.entity.*', + }, + { + 'label': 'Call number', + 'value': 'callNumber', + 'field': 'call_numbers', + }, + { + 'label': 'Holdings local fields', + 'value': 'holdingsLocalFields', + 'field': 'holdings.local_fields.*', + }, + { + 'label': 'Classification', + 'value': 'classification', + 'field': 'classification.*', + }, + { + 'label': 'Item local fields', + 'value': 'itemLocalFields', + 'field': 'holdings.items.local_fields.*', + }, + { + 'label': 'Document local fields', + 'value': 'documentLocalFields', + 'field': 'local_fields.*', + }, + { + 'label': 'RDA content type', + 'value': 'rdaContentType', + 'field': 'contentmediaCarrier.contentType', + }, + { + 'label': 'RDA media type', + 'value': 'rdaMediaType', + 'field': 'contentmediaCarrier.mediaType', + }, + { + 'label': 'RDA carrier type', + 'value': 'rdaCarrierType', + 'field': 'contentmediaCarrier.carrierType', + }, +] diff --git a/rero_ils/modules/documents/api_views.py b/rero_ils/modules/documents/api_views.py index d1a6b2eed9..ff26368005 100644 --- a/rero_ils/modules/documents/api_views.py +++ b/rero_ils/modules/documents/api_views.py @@ -18,12 +18,16 @@ """Blueprint for document api.""" -from flask import Blueprint, abort, jsonify +from functools import cmp_to_key + +from flask import Blueprint, abort, current_app, jsonify from flask import request as flask_request +from rero_ils.modules.decorators import check_logged_as_librarian + from .api import Document from .utils import get_remote_cover -from ..utils import cached +from ..utils import cached, read_json_file api_blueprint = Blueprint( 'api_documents', @@ -50,3 +54,51 @@ def document_availability(pid): return jsonify({ 'available': Document.is_available(pid, view_code) }) + + +@api_blueprint.route('/advanced-search-config') +# @cached(timeout=300, query_string=True) +@check_logged_as_librarian +def advanced_search_config(): + """Advanced search config.""" + + def sort_medias(a, b): + """Sort only media start with rda in label.""" + a, b = a['label'], b['label'] + if a.startswith('rda') and b.startswith('rda'): + return a > b + elif a.startswith('rda'): + return -1 + elif b.startswith('rda'): + return 1 + else: + return a > b + + cantons = read_json_file('jsonschemas/common/cantons-v0.0.1.json') + countries = read_json_file('jsonschemas/common/countries-v0.0.1.json') + medias = read_json_file('modules/documents/jsonschemas/documents' + '/document_content_media_carrier-v0.0.1.json') + media_items = medias['contentMediaCarrier']['items']['oneOf'] + media_types = [] + carrier_types = [] + for item in media_items: + if rda_type := item.get('properties', {}).get('mediaType', {}): + data = rda_type.get('title') + media_types.append({'label': data, 'value': data}) + if rda_type := item.get('properties', {}).get('carrierType', {}): + for option in rda_type.get('form', {}).get('options'): + if option not in carrier_types: + carrier_types.append(option) + return jsonify({ + 'fieldsConfig': current_app.config.get( + 'RERO_ILS_APP_ADVANCED_SEARCH_CONFIG', []), + 'fieldsData': { + 'country': countries['country']['form']['options'], + 'canton': cantons['canton']['form']['options'], + 'rdaContentType': medias['definitions']['contentType']['items'] + ['form']['options'], + 'rdaMediaType': sorted(media_types, key=cmp_to_key(sort_medias)), + 'rdaCarrierType': sorted( + carrier_types, key=cmp_to_key(sort_medias)) + } + }) diff --git a/rero_ils/modules/documents/mappings/v7/documents/document-v0.0.1.json b/rero_ils/modules/documents/mappings/v7/documents/document-v0.0.1.json index d85f87ee35..da6dcb0beb 100644 --- a/rero_ils/modules/documents/mappings/v7/documents/document-v0.0.1.json +++ b/rero_ils/modules/documents/mappings/v7/documents/document-v0.0.1.json @@ -1139,10 +1139,12 @@ } }, "call_number": { - "type": "text" + "type": "text", + "copy_to": "call_numbers" }, "second_call_number": { - "type": "text" + "type": "text", + "copy_to": "call_numbers" }, "index": { "type": "text" @@ -1166,10 +1168,12 @@ "type": "keyword" }, "call_number": { - "type": "text" + "type": "text", + "copy_to": "call_numbers" }, "second_call_number": { - "type": "text" + "type": "text", + "copy_to": "call_numbers" }, "status": { "type": "keyword" @@ -2135,6 +2139,9 @@ } } }, + "call_numbers": { + "type": "text" + }, "_draft": { "type": "boolean" }, diff --git a/rero_ils/modules/patrons/views.py b/rero_ils/modules/patrons/views.py index 621321d1b8..23e9ae6a7e 100644 --- a/rero_ils/modules/patrons/views.py +++ b/rero_ils/modules/patrons/views.py @@ -134,6 +134,8 @@ def logged_user(): 'agentLabelOrder': config.get('RERO_ILS_AGENTS_LABEL_ORDER', {}), 'agentSources': config.get('RERO_ILS_AGENTS_SOURCES', []), 'operationLogs': config.get('RERO_ILS_ENABLE_OPERATION_LOG', []), + 'documentAdvancedSearch': config.get( + 'RERO_ILS_APP_DOCUMENT_ADVANCED_SEARCH', False), 'userProfile': { 'readOnly': config.get( 'RERO_PUBLIC_USERPROFILES_READONLY', False), diff --git a/rero_ils/modules/utils.py b/rero_ils/modules/utils.py index 33f61ca259..6238a446d7 100644 --- a/rero_ils/modules/utils.py +++ b/rero_ils/modules/utils.py @@ -19,6 +19,7 @@ """Utilities for rero-ils editor.""" import cProfile +import json import os import pstats import random @@ -1300,3 +1301,13 @@ def password_generator(length=8, special_char=False): random.shuffle(password) return ''.join(password) + + +def read_json_file(file_path): + """Read json file with full path. + + :param file_path: the relative path. + :returns: the contents of the json file. + """ + path = os.path.dirname(os.path.realpath('__file__')) + return json.load(open(f'{path}/rero_ils/{file_path}')) diff --git a/tests/api/documents/test_documents_rest.py b/tests/api/documents/test_documents_rest.py index fe15918cae..16482c780f 100644 --- a/tests/api/documents/test_documents_rest.py +++ b/tests/api/documents/test_documents_rest.py @@ -728,3 +728,31 @@ def test_document_current_library_on_request_parameter( .scan()) assert oplg.library.value == lib_martigny_bourg.pid db.session.rollback() + + +def test_document_advanced_search_config(app, db, client, + system_librarian_martigny, document): + """Test for advanced search config.""" + config_url = url_for('api_documents.advanced_search_config') + + res = client.get(config_url) + assert res.status_code == 401 + + login_user_via_session(client, system_librarian_martigny.user) + + res = client.get(config_url) + assert res.status_code == 200 + + json = res.json + assert 'fieldsConfig' in json + assert 'fieldsData' in json + field_data = json.get('fieldsData') + data_keys = [ + 'canton', 'country', 'rdaCarrierType', + 'rdaContentType', 'rdaMediaType' + ] + assert data_keys == list(field_data.keys()) + + assert 0 < len(json.get('fieldsConfig')) + for key in data_keys: + assert 0 < len(field_data.get(key))