From 728d3ecbcd2b8be85d108d577c25da06b1101636 Mon Sep 17 00:00:00 2001 From: Bertrand Zuchuat Date: Thu, 29 Oct 2020 16:00:40 +0100 Subject: [PATCH] data: implements local fields * Adds local fields on document, item and holdings (serial type). * Adds the type column in the refs resolve. Co-Authored-by: Bertrand Zuchuat Co-Authored-by: Aly Badr --- data/local_fields.json | 62 +++++++ rero_ils/config.py | 63 ++++++- .../acq_order_line-v0.0.1.json | 6 + .../v7/circ_policies/circ_policy-v0.0.1.json | 12 ++ rero_ils/modules/documents/listener.py | 16 ++ .../v7/documents/document-v0.0.1.json | 132 ++++++++++++++ rero_ils/modules/holdings/listener.py | 7 + .../mappings/v7/holdings/holding-v0.0.1.json | 42 +++++ .../v7/item_types/item_type-v0.0.1.json | 3 + rero_ils/modules/items/listener.py | 7 + .../items/mappings/v7/items/item-v0.0.1.json | 63 +++++++ rero_ils/modules/jsonresolver.py | 5 +- .../mappings/v7/libraries/library-v0.0.1.json | 3 + rero_ils/modules/local_fields/__init__.py | 18 ++ rero_ils/modules/local_fields/api.py | 137 +++++++++++++++ rero_ils/modules/local_fields/jsonresolver.py | 35 ++++ .../local_fields/jsonschemas/__init__.py | 20 +++ .../local_fields/local_field-v0.0.1.json | 163 ++++++++++++++++++ .../modules/local_fields/mappings/__init__.py | 20 +++ .../local_fields/mappings/v7/__init__.py | 20 +++ .../v7/local_fields/local_field-v0.0.1.json | 79 +++++++++ rero_ils/modules/local_fields/models.py | 42 +++++ rero_ils/modules/local_fields/permissions.py | 98 +++++++++++ .../v7/locations/location-v0.0.1.json | 9 + .../patron_transaction-v0.0.1.json | 15 ++ .../v7/patron_types/patron_type-v0.0.1.json | 3 + .../v7/templates/template-v0.0.1.json | 6 + scripts/setup | 6 +- setup.py | 6 + .../test_local_fields_permissions.py | 55 ++++++ tests/api/test_monitoring_rest.py | 1 + tests/conftest.py | 8 + tests/data/local_fields.json | 36 ++++ tests/fixtures/metadata.py | 40 +++++ .../documents/test_documents_jsonresolver.py | 2 +- .../ui/holdings/test_holdings_jsonresolver.py | 4 +- .../test_ill_requests_jsonresolver.py | 4 +- .../test_item_types_jsonresolver.py | 4 +- tests/ui/items/test_items_jsonresolver.py | 2 +- .../libraries/test_libraries_jsonresolver.py | 2 +- tests/ui/loans/test_loans_jsonresolver.py | 2 +- tests/ui/local_fields/conftest.py | 28 +++ .../ui/local_fields/test_local_fields_api.py | 67 +++++++ .../test_local_fields_jsonresolver.py | 43 +++++ .../local_fields/test_local_fields_mapping.py | 54 ++++++ .../locations/test_locations_jsonresolver.py | 2 +- .../test_organisations_jsonresolver.py | 4 +- .../test_patron_types_jsonresolver.py | 4 +- tests/ui/patrons/test_patrons_jsonresolver.py | 4 +- tests/ui/test_monitoring.py | 2 + tests/unit/conftest.py | 9 + tests/unit/test_local_fields_jsonschema.py | 64 +++++++ 52 files changed, 1523 insertions(+), 16 deletions(-) create mode 100644 data/local_fields.json create mode 100644 rero_ils/modules/local_fields/__init__.py create mode 100644 rero_ils/modules/local_fields/api.py create mode 100644 rero_ils/modules/local_fields/jsonresolver.py create mode 100644 rero_ils/modules/local_fields/jsonschemas/__init__.py create mode 100644 rero_ils/modules/local_fields/jsonschemas/local_fields/local_field-v0.0.1.json create mode 100644 rero_ils/modules/local_fields/mappings/__init__.py create mode 100644 rero_ils/modules/local_fields/mappings/v7/__init__.py create mode 100644 rero_ils/modules/local_fields/mappings/v7/local_fields/local_field-v0.0.1.json create mode 100644 rero_ils/modules/local_fields/models.py create mode 100644 rero_ils/modules/local_fields/permissions.py create mode 100644 tests/api/local_fields/test_local_fields_permissions.py create mode 100644 tests/data/local_fields.json create mode 100644 tests/ui/local_fields/conftest.py create mode 100644 tests/ui/local_fields/test_local_fields_api.py create mode 100644 tests/ui/local_fields/test_local_fields_jsonresolver.py create mode 100644 tests/ui/local_fields/test_local_fields_mapping.py create mode 100644 tests/unit/test_local_fields_jsonschema.py diff --git a/data/local_fields.json b/data/local_fields.json new file mode 100644 index 0000000000..c0129c2ea3 --- /dev/null +++ b/data/local_fields.json @@ -0,0 +1,62 @@ +[ + { + "pid": "1", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/1" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/1" + }, + "fields": { + "field_1": [ + "Auteur vald\u00f4tain", + "Bibliographie vald\u00f4taine" + ] + } + }, + { + "pid": "2", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/2" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/1" + }, + "fields": { + "field_1": [ + "Production by Hogwarts' students" + ] + } + }, + { + "pid": "3", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/1" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/93" + }, + "fields": { + "field_1": [ + "Don \u00c9mile Chanoux" + ], + "field_2": [ + "Fonds de la Fondation Grand Paradis" + ] + } + }, + { + "pid": "4", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/2" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/93" + }, + "fields": { + "field_1": [ + "Donation Albus Dumbledore" + ] + } + } +] diff --git a/rero_ils/config.py b/rero_ils/config.py index 332b6d211e..8e626b3a28 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -76,6 +76,8 @@ get_extension_params, is_item_available_for_checkout, \ loan_build_document_ref, loan_build_item_ref, loan_build_patron_ref, \ loan_satisfy_circ_policies, validate_item_pickup_transaction_locations +from .modules.local_fields.api import LocalField +from .modules.local_fields.permissions import LocalFieldPermission from .modules.locations.api import Location from .modules.locations.permissions import LocationPermission from .modules.notifications.api import Notification @@ -463,7 +465,8 @@ def _(x): }, record_class='rero_ils.modules.collections.api:Collection', list_route='/collections/', - item_route='/collections/', + item_route='/collections/', default_media_type='application/json', max_result_window=10000, search_factory_imp='rero_ils.query:view_search_collection_factory', @@ -507,7 +510,8 @@ def _(x): ), }, record_loaders={ - 'application/marcxml+xml': 'rero_ils.modules.documents.loaders:marcxml_loader', + 'application/marcxml+xml': + 'rero_ils.modules.documents.loaders:marcxml_loader', 'application/json': lambda: Document(request.get_json()), }, @@ -568,7 +572,8 @@ def _(x): }, record_class='rero_ils.modules.ill_requests.api:ILLRequest', list_route='/ill_requests/', - item_route='/ill_requests/', + item_route='/ill_requests/', default_media_type='application/json', max_result_window=10000, search_factory_imp='rero_ils.query:loans_search_factory', @@ -720,6 +725,48 @@ def _(x): delete_permission_factory_imp=lambda record: record_permission_factory( action='delete', record=record, cls=HoldingPermission) ), + lofi=dict( + pid_type='lofi', + pid_minter='local_field_id', + pid_fetcher='local_field_id', + search_class='rero_ils.modules.local_fields.api:LocalFieldsSearch', + search_index='local_fields', + search_type=None, + indexer_class='rero_ils.modules.local_fields.api:LocalFieldsIndexer', + record_serializers={ + 'application/json': ( + 'rero_ils.modules.serializers:json_v1_response' + ) + }, + record_serializers_aliases={ + 'json': 'application/json', + }, + search_serializers={ + 'application/json': ( + 'rero_ils.modules.serializers:json_v1_search' + ) + }, + record_loaders={ + 'application/json': lambda: LocalField(request.get_json()), + }, + record_class='rero_ils.modules.local_fields.api:LocalField', + list_route='/local_fields/', + item_route='/local_fields/', + default_media_type='application/json', + max_result_window=10000, + search_factory_imp='rero_ils.query:organisation_search_factory', + list_permission_factory_imp=lambda record: record_permission_factory( + action='list', record=record, cls=LocalFieldPermission), + read_permission_factory_imp=lambda record: record_permission_factory( + action='read', record=record, cls=LocalFieldPermission), + create_permission_factory_imp=lambda record: record_permission_factory( + action='create', record=record, cls=LocalFieldPermission), + update_permission_factory_imp=lambda record: record_permission_factory( + action='update', record=record, cls=LocalFieldPermission), + delete_permission_factory_imp=lambda record: record_permission_factory( + action='delete', record=record, cls=LocalFieldPermission) + ), ptrn=dict( pid_type='ptrn', pid_minter='patron_id', @@ -1824,6 +1871,7 @@ def _(x): 'item_types', 'ill_requests', 'libraries', + 'local_fields', 'loans', 'locations', 'organisations', @@ -2022,6 +2070,14 @@ def _(x): record_class='rero_ils.modules.contributions.api:Contribution', view_imp='rero_ils.modules.contributions.' 'views.contribution_view_method' + ), + 'lofi': dict( + pid_type='lofi', + route='/local_fields/', + template='rero_ils/detailed_view_local_fields.html', + record_class='rero_ils.modules.local_fields.api:LocalField', + permission_factory_imp='rero_ils.permissions.' + 'librarian_permission_factory', ) } @@ -2051,6 +2107,7 @@ def _(x): 'itty': '/item_types/item_type-v0.0.1.json', 'lib': '/libraries/library-v0.0.1.json', 'loc': '/locations/location-v0.0.1.json', + 'lofi': '/local_fields/local_field-v0.0.1.json', 'notif': '/notifications/notification-v0.0.1.json', 'org': '/organisations/organisation-v0.0.1.json', 'pttr': '/patron_transactions/patron_transaction-v0.0.1.json', diff --git a/rero_ils/modules/acq_order_lines/mappings/v7/acq_order_lines/acq_order_line-v0.0.1.json b/rero_ils/modules/acq_order_lines/mappings/v7/acq_order_lines/acq_order_line-v0.0.1.json index 36bd65a55f..47797bca91 100644 --- a/rero_ils/modules/acq_order_lines/mappings/v7/acq_order_lines/acq_order_line-v0.0.1.json +++ b/rero_ils/modules/acq_order_lines/mappings/v7/acq_order_lines/acq_order_line-v0.0.1.json @@ -30,6 +30,9 @@ }, "document": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -37,6 +40,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/circ_policies/mappings/v7/circ_policies/circ_policy-v0.0.1.json b/rero_ils/modules/circ_policies/mappings/v7/circ_policies/circ_policy-v0.0.1.json index 25b9741686..443793ab20 100644 --- a/rero_ils/modules/circ_policies/mappings/v7/circ_policies/circ_policy-v0.0.1.json +++ b/rero_ils/modules/circ_policies/mappings/v7/circ_policies/circ_policy-v0.0.1.json @@ -26,6 +26,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -63,6 +66,9 @@ }, "libraries": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -73,6 +79,9 @@ "properties": { "patron_type": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -80,6 +89,9 @@ }, "item_type": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/documents/listener.py b/rero_ils/modules/documents/listener.py index 761fba1708..42b8689c02 100644 --- a/rero_ils/modules/documents/listener.py +++ b/rero_ils/modules/documents/listener.py @@ -22,6 +22,7 @@ from ..holdings.api import Holding, HoldingsSearch from ..items.api import ItemsSearch from ..items.models import ItemNoteTypes +from ..local_fields.api import LocalField from ..utils import extracted_data_from_ref @@ -56,6 +57,10 @@ def enrich_document_data(sender, json=None, record=None, index=None, 'library_pid': holding['library']['pid'] } } + # Local fields on the holding + if 'local_fields' in holding: + data['local_fields'] = holding.to_dict()['local_fields'] + # items linked to the holding es_items = list( ItemsSearch().filter('term', holding__pid=holding.pid).scan() @@ -67,6 +72,12 @@ def enrich_document_data(sender, json=None, record=None, index=None, 'status': item.status, 'available': item.available } + + # Local fields on the item + if 'local_fields' in item: + item_record['local_fields'] =\ + item.to_dict()['local_fields'] + call_number = item.to_dict().get('call_number') if call_number: item_record['call_number'] = call_number @@ -126,3 +137,8 @@ def enrich_document_data(sender, json=None, record=None, index=None, json.get('title', []), with_subtitle=True ) + # Local fields in JSON + local_fields = LocalField.get_local_fields_by_resource( + 'doc', document_pid) + if local_fields: + json['local_fields'] = local_fields 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 e79eb0f4bc..804cd85112 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 @@ -196,6 +196,9 @@ "document": { "type": "object", "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -702,6 +705,48 @@ } } }, + "local_fields": { + "properties": { + "organisation_pid": { + "type": "keyword" + }, + "fields": { + "type": "object", + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" + } + } + } + } + }, "items": { "type": "object", "properties": { @@ -740,6 +785,90 @@ }, "notes": { "type": "text" + }, + "local_fields": { + "properties": { + "organisation_pid": { + "type": "keyword" + }, + "fields": { + "type": "object", + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" + } + } + } + } + } + } + } + } + }, + "local_fields": { + "properties": { + "organisation_pid": { + "type": "keyword" + }, + "fields": { + "type": "object", + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" } } } @@ -750,6 +879,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/holdings/listener.py b/rero_ils/modules/holdings/listener.py index 5af694458b..f6751b4c10 100644 --- a/rero_ils/modules/holdings/listener.py +++ b/rero_ils/modules/holdings/listener.py @@ -18,6 +18,7 @@ """Signals connector for Holding.""" from .api import HoldingsSearch +from ..local_fields.api import LocalField from ..locations.api import LocationsSearch @@ -41,3 +42,9 @@ def enrich_holding_data(sender, json=None, record=None, index=None, json['library'] = { 'pid': es_loc.library.pid } + + # Local fields in JSON + local_fields = LocalField.get_local_fields_by_resource( + 'hold', record['pid']) + if local_fields: + json['local_fields'] = local_fields diff --git a/rero_ils/modules/holdings/mappings/v7/holdings/holding-v0.0.1.json b/rero_ils/modules/holdings/mappings/v7/holdings/holding-v0.0.1.json index 44d7b3e776..717f6bcc45 100644 --- a/rero_ils/modules/holdings/mappings/v7/holdings/holding-v0.0.1.json +++ b/rero_ils/modules/holdings/mappings/v7/holdings/holding-v0.0.1.json @@ -150,6 +150,48 @@ "missing_issues": { "type": "keyword" }, + "local_fields": { + "properties": { + "organisation_pid": { + "type": "keyword" + }, + "fields": { + "type": "object", + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" + } + } + } + } + }, "_created": { "type": "date" }, diff --git a/rero_ils/modules/item_types/mappings/v7/item_types/item_type-v0.0.1.json b/rero_ils/modules/item_types/mappings/v7/item_types/item_type-v0.0.1.json index cf5e101504..a41c09cfbf 100644 --- a/rero_ils/modules/item_types/mappings/v7/item_types/item_type-v0.0.1.json +++ b/rero_ils/modules/item_types/mappings/v7/item_types/item_type-v0.0.1.json @@ -29,6 +29,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/items/listener.py b/rero_ils/modules/items/listener.py index 95e66d9a22..c9b17f9b36 100644 --- a/rero_ils/modules/items/listener.py +++ b/rero_ils/modules/items/listener.py @@ -18,6 +18,7 @@ """Signals connector for Item.""" from .api import Item, ItemsSearch +from ..local_fields.api import LocalField def enrich_item_data(sender, json=None, record=None, index=None, @@ -47,3 +48,9 @@ def enrich_item_data(sender, json=None, record=None, index=None, json['vendor'] = { 'pid': item.vendor_pid } + + # Local fields in JSON + local_fields = LocalField.get_local_fields_by_resource( + 'item', item.get('pid')) + if local_fields: + json['local_fields'] = local_fields diff --git a/rero_ils/modules/items/mappings/v7/items/item-v0.0.1.json b/rero_ils/modules/items/mappings/v7/items/item-v0.0.1.json index c00d6fe668..de6c7849f5 100644 --- a/rero_ils/modules/items/mappings/v7/items/item-v0.0.1.json +++ b/rero_ils/modules/items/mappings/v7/items/item-v0.0.1.json @@ -53,6 +53,9 @@ }, "location": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -60,6 +63,9 @@ }, "library": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -67,6 +73,9 @@ }, "document": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -74,6 +83,9 @@ }, "item_type": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -112,6 +124,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -119,6 +134,9 @@ }, "holding": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -139,11 +157,56 @@ }, "vendor": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } } }, + "local_fields": { + "properties": { + "organisation_pid": { + "type": "keyword" + }, + "fields": { + "type": "object", + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" + } + } + } + } + }, "_created": { "type": "date" }, diff --git a/rero_ils/modules/jsonresolver.py b/rero_ils/modules/jsonresolver.py index ffc06ef074..060631b8c1 100644 --- a/rero_ils/modules/jsonresolver.py +++ b/rero_ils/modules/jsonresolver.py @@ -39,7 +39,10 @@ def resolve_json_refs(pid_type, pid, raise_on_error=True): ) else: if persistent_id.status == PIDStatus.REGISTERED: - return dict(pid=persistent_id.pid_value) + return dict( + pid=persistent_id.pid_value, + type=pid_type + ) base_item_route = current_app.config.get( 'RECORDS_REST_ENDPOINTS' ).get(pid_type, {}).get('item_route', '/???') diff --git a/rero_ils/modules/libraries/mappings/v7/libraries/library-v0.0.1.json b/rero_ils/modules/libraries/mappings/v7/libraries/library-v0.0.1.json index d8bfb85a20..33b3fd4c52 100644 --- a/rero_ils/modules/libraries/mappings/v7/libraries/library-v0.0.1.json +++ b/rero_ils/modules/libraries/mappings/v7/libraries/library-v0.0.1.json @@ -97,6 +97,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/local_fields/__init__.py b/rero_ils/modules/local_fields/__init__.py new file mode 100644 index 0000000000..b9df372db7 --- /dev/null +++ b/rero_ils/modules/local_fields/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""LocalField Records.""" diff --git a/rero_ils/modules/local_fields/api.py b/rero_ils/modules/local_fields/api.py new file mode 100644 index 0000000000..4ae860e188 --- /dev/null +++ b/rero_ils/modules/local_fields/api.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""API for manipulating local_fields.""" + +from functools import partial + +from elasticsearch_dsl import Q +from flask_babelex import gettext as _ + +from .models import LocalFieldIdentifier, LocalFieldMetadata +from ..api import IlsRecord, IlsRecordsIndexer, IlsRecordsSearch +from ..fetchers import id_fetcher +from ..minters import id_minter +from ..providers import Provider +from ...modules.utils import extracted_data_from_ref + +# provider +LocalFieldProvider = type( + 'LocalFieldProvider', + (Provider,), + dict(identifier=LocalFieldIdentifier, pid_type='lofi') +) +# minter +local_field_id_minter = partial(id_minter, provider=LocalFieldProvider) +# fetcher +local_field_id_fetcher = partial(id_fetcher, provider=LocalFieldProvider) + + +class LocalFieldsSearch(IlsRecordsSearch): + """LocalFieldsSearch.""" + + class Meta: + """Search only on local_field index.""" + + index = 'local_fields' + doc_types = None + fields = ('*', ) + facets = {} + + default_filter = None + + +class LocalField(IlsRecord): + """LocalField class.""" + + minter = local_field_id_minter + fetcher = local_field_id_fetcher + provider = LocalFieldProvider + model_cls = LocalFieldMetadata + + def extended_validation(self, **kwargs): + """Extended validation.""" + if len(self.get('fields', {}).keys()) == 0: + return _('Missing fields.') + return True + + @classmethod + def get_local_fields_by_resource(cls, type, pid, organisation_pid=None): + """Get all local fields linked to a resource. + + :param type: type of record (Ex: doc). + :param pid: pid of record type. + :param organisation_pid: current organisation pid. + :return: list of local fields record. + """ + from .api import LocalFieldsSearch + + queryFilters = [ + Q('term', parent__type=type), + Q('term', parent__pid=pid) + ] + if (organisation_pid): + queryFilters.append(Q('term', organisation__pid=organisation_pid)) + query = LocalFieldsSearch()\ + .query('bool', filter=queryFilters)\ + .sort( + {'organisation__pid': {'order': 'asc'}}, + {'parent__type': {'order': 'asc'}} + )\ + .source(['organisation', 'fields']).scan() + local_fields = [] + for local_field in query: + data = local_field.to_dict() + local_fields.append({ + 'organisation_pid': data['organisation']['pid'], + 'fields': data['fields'] + }) + return local_fields + + +class LocalFieldsIndexer(IlsRecordsIndexer): + """Local fields indexing class.""" + + record_cls = LocalField + + def index(self, record): + """Reindex a resource (documents, items, holdings). + + :param record: Record instance. + """ + return_value = super(LocalFieldsIndexer, self).index(record) + resource = extracted_data_from_ref(record['parent']['$ref'], 'record') + resource.reindex() + return return_value + + def delete(self, record): + """Reindex a resource (documents, items, holdings). + + :param record: Record instance. + """ + return_value = super(LocalFieldsIndexer, self).delete(record) + resource = extracted_data_from_ref(record['parent']['$ref'], 'record') + resource.reindex() + return return_value + + def bulk_index(self, record_id_iterator): + """Bulk index records. + + :param record_id_iterator: Iterator yielding record UUIDs. + """ + super(LocalFieldsIndexer, self).bulk_index( + record_id_iterator, doc_type='lofi') diff --git a/rero_ils/modules/local_fields/jsonresolver.py b/rero_ils/modules/local_fields/jsonresolver.py new file mode 100644 index 0000000000..3a7cb65025 --- /dev/null +++ b/rero_ils/modules/local_fields/jsonresolver.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""LocalField resolver.""" + +import jsonresolver +from flask import current_app +from invenio_pidstore.models import PersistentIdentifier, PIDStatus + + +@jsonresolver.route('/api/local_fields/', host='ils.rero.ch') +def local_field_resolver(pid): + """Resolver for local_field record.""" + persistent_id = PersistentIdentifier.get('lofi', pid) + if persistent_id.status == PIDStatus.REGISTERED: + return dict(pid=persistent_id.pid_value) + current_app.logger.error( + 'Local fields resolver error: /api/local_fields/{pid} {persistent_id}' + .format(pid=pid, persistent_id=persistent_id) + ) + raise Exception('unable to resolve') diff --git a/rero_ils/modules/local_fields/jsonschemas/__init__.py b/rero_ils/modules/local_fields/jsonschemas/__init__.py new file mode 100644 index 0000000000..6950e6e40e --- /dev/null +++ b/rero_ils/modules/local_fields/jsonschemas/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""JSON schemas.""" + +from __future__ import absolute_import, print_function diff --git a/rero_ils/modules/local_fields/jsonschemas/local_fields/local_field-v0.0.1.json b/rero_ils/modules/local_fields/jsonschemas/local_fields/local_field-v0.0.1.json new file mode 100644 index 0000000000..c107a90341 --- /dev/null +++ b/rero_ils/modules/local_fields/jsonschemas/local_fields/local_field-v0.0.1.json @@ -0,0 +1,163 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "Local fields", + "description": "Local fields", + "additionalProperties": false, + "required": [ + "$schema", + "pid", + "fields" + ], + "propertiesOrder": [ + "fields" + ], + "properties": { + "$schema": { + "title": "Schema", + "description": "Schema to validate local_field records against.", + "type": "string", + "minLength": 9, + "default": "https://ils.rero.ch/schema/local_fields/local_field-v0.0.1.json" + }, + "pid": { + "title": "Local field ID", + "type": "string", + "minLength": 1 + }, + "organisation": { + "title": "Organisation", + "type": "object", + "properties": { + "$ref": { + "title": "Organisation", + "type": "string", + "pattern": "^https://ils.rero.ch/api/organisations/.*?$" + } + } + }, + "parent": { + "title": "Parent", + "type": "object", + "additionalProperties": false, + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "title": "Patron type URI", + "type": "string", + "pattern": "^https://ils.rero.ch/api/documents|items|holdings/.*?$" + } + } + }, + "fields": { + "type": "object", + "additionalProperties": false, + "propertiesOrder": [ + "field_1", + "field_2", + "field_3", + "field_4", + "field_5", + "field_6", + "field_7", + "field_8", + "field_9", + "field_10" + ], + "properties": { + "field_1": { + "title": "Field 1", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_2": { + "title": "Field 2", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_3": { + "title": "Field 3", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_4": { + "title": "Field 4", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_5": { + "title": "Field 5", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_6": { + "title": "Field 6", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_7": { + "title": "Field 7", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_8": { + "title": "Field 8", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_9": { + "title": "Field 9", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + }, + "field_10": { + "title": "Field 10", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 3 + } + } + } + } + } +} diff --git a/rero_ils/modules/local_fields/mappings/__init__.py b/rero_ils/modules/local_fields/mappings/__init__.py new file mode 100644 index 0000000000..b2c554268f --- /dev/null +++ b/rero_ils/modules/local_fields/mappings/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Elasticsearch mappings.""" + +from __future__ import absolute_import, print_function diff --git a/rero_ils/modules/local_fields/mappings/v7/__init__.py b/rero_ils/modules/local_fields/mappings/v7/__init__.py new file mode 100644 index 0000000000..b2c554268f --- /dev/null +++ b/rero_ils/modules/local_fields/mappings/v7/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Elasticsearch mappings.""" + +from __future__ import absolute_import, print_function diff --git a/rero_ils/modules/local_fields/mappings/v7/local_fields/local_field-v0.0.1.json b/rero_ils/modules/local_fields/mappings/v7/local_fields/local_field-v0.0.1.json new file mode 100644 index 0000000000..cd669fcf6e --- /dev/null +++ b/rero_ils/modules/local_fields/mappings/v7/local_fields/local_field-v0.0.1.json @@ -0,0 +1,79 @@ +{ + "settings": { + "number_of_shards": 1, + "number_of_replicas": 0, + "max_result_window": 20000 + }, + "mappings": { + "date_detection": false, + "numeric_detection": false, + "properties": { + "$schema": { + "type": "keyword" + }, + "pid": { + "type": "keyword" + }, + "organisation": { + "properties": { + "pid": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "parent": { + "properties": { + "type": { + "type": "keyword" + }, + "pid": { + "type": "keyword" + } + } + }, + "fields": { + "properties": { + "field_1": { + "type": "text" + }, + "field_2": { + "type": "text" + }, + "field_3": { + "type": "text" + }, + "field_4": { + "type": "text" + }, + "field_5": { + "type": "text" + }, + "field_6": { + "type": "text" + }, + "field_7": { + "type": "text" + }, + "field_8": { + "type": "text" + }, + "field_9": { + "type": "text" + }, + "field_10": { + "type": "text" + } + } + }, + "_created": { + "type": "date" + }, + "_updated": { + "type": "date" + } + } + } +} diff --git a/rero_ils/modules/local_fields/models.py b/rero_ils/modules/local_fields/models.py new file mode 100644 index 0000000000..259212e0ae --- /dev/null +++ b/rero_ils/modules/local_fields/models.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Define relation between records and buckets.""" + +from __future__ import absolute_import + +from invenio_db import db +from invenio_pidstore.models import RecordIdentifier +from invenio_records.models import RecordMetadataBase + + +class LocalFieldIdentifier(RecordIdentifier): + """Sequence generator for LocalField identifiers.""" + + __tablename__ = 'local_field_id' + __mapper_args__ = {'concrete': True} + + recid = db.Column( + db.BigInteger().with_variant(db.Integer, 'sqlite'), + primary_key=True, autoincrement=True, + ) + + +class LocalFieldMetadata(db.Model, RecordMetadataBase): + """Local field record metadata.""" + + __tablename__ = 'local_field_metadata' diff --git a/rero_ils/modules/local_fields/permissions.py b/rero_ils/modules/local_fields/permissions.py new file mode 100644 index 0000000000..a04fe26e9b --- /dev/null +++ b/rero_ils/modules/local_fields/permissions.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Permissions of Local field.""" + +from rero_ils.modules.organisations.api import current_organisation +from rero_ils.modules.patrons.api import current_patron +from rero_ils.modules.permissions import RecordPermission + + +class LocalFieldPermission(RecordPermission): + """Local fields permissions.""" + + @classmethod + def list(cls, user, record=None): + """List permission check. + + :param user: Logged user. + :param record: Record to check. + :return: True is action can be done. + """ + # List local fields allowed only for staff members (lib, sys_lib) + return current_patron and current_patron.is_librarian + + @classmethod + def read(cls, user, record): + """Read permission check. + + :param user: Logged user. + :param record: Record to check. + :return: True is action can be done. + """ + if not current_patron: + return False + # only staff members (lib, sys_lib) are allowed to read + if not current_patron.is_librarian: + return False + # For staff users, they can read only own organisation + return current_organisation['pid'] == record.organisation_pid + + @classmethod + def create(cls, user, record=None): + """Create permission check. + + :param user: Logged user. + :param record: Record to check. + :return: True is action can be done. + """ + # only sys_lib user can create local fields + if not current_patron: + return False + # sys_lib can only create local fields for its own organisation + if record and current_organisation['pid'] != record.organisation_pid: + return False + return True + + @classmethod + def update(cls, user, record): + """Update permission check. + + :param user: Logged user. + :param record: Record to check. + :return: True is action can be done. + """ + # only staff members (lib, sys_lib) can update library + # record cannot be null + if not current_patron or not current_patron.is_librarian or not record: + return False + if current_organisation['pid'] == record.organisation_pid: + return True + return False + + @classmethod + def delete(cls, user, record): + """Delete permission check. + + :param user: Logged user. + :param record: Record to check. + :return: True if action can be done. + """ + if not record: + return False + # same as create + return cls.create(user, record) diff --git a/rero_ils/modules/locations/mappings/v7/locations/location-v0.0.1.json b/rero_ils/modules/locations/mappings/v7/locations/location-v0.0.1.json index 6183a453ee..111a02b188 100644 --- a/rero_ils/modules/locations/mappings/v7/locations/location-v0.0.1.json +++ b/rero_ils/modules/locations/mappings/v7/locations/location-v0.0.1.json @@ -40,6 +40,9 @@ }, "library": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -47,6 +50,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -60,6 +66,9 @@ }, "restrict_pickup_to": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/patron_transactions/mappings/v7/patron_transactions/patron_transaction-v0.0.1.json b/rero_ils/modules/patron_transactions/mappings/v7/patron_transactions/patron_transaction-v0.0.1.json index cc14e21551..b4c0b588b4 100644 --- a/rero_ils/modules/patron_transactions/mappings/v7/patron_transactions/patron_transaction-v0.0.1.json +++ b/rero_ils/modules/patron_transactions/mappings/v7/patron_transactions/patron_transaction-v0.0.1.json @@ -28,6 +28,9 @@ }, "patron": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -35,6 +38,9 @@ }, "document": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -42,6 +48,9 @@ }, "loan": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -49,6 +58,9 @@ }, "notification": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -56,6 +68,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/patron_types/mappings/v7/patron_types/patron_type-v0.0.1.json b/rero_ils/modules/patron_types/mappings/v7/patron_types/patron_type-v0.0.1.json index 4f57d75b2e..16009448f2 100644 --- a/rero_ils/modules/patron_types/mappings/v7/patron_types/patron_type-v0.0.1.json +++ b/rero_ils/modules/patron_types/mappings/v7/patron_types/patron_type-v0.0.1.json @@ -26,6 +26,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/rero_ils/modules/templates/mappings/v7/templates/template-v0.0.1.json b/rero_ils/modules/templates/mappings/v7/templates/template-v0.0.1.json index fe6d7ceb8e..aca068a502 100644 --- a/rero_ils/modules/templates/mappings/v7/templates/template-v0.0.1.json +++ b/rero_ils/modules/templates/mappings/v7/templates/template-v0.0.1.json @@ -26,6 +26,9 @@ }, "organisation": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } @@ -33,6 +36,9 @@ }, "creator": { "properties": { + "type": { + "type": "keyword" + }, "pid": { "type": "keyword" } diff --git a/scripts/setup b/scripts/setup index 219733356e..9e57d8706b 100755 --- a/scripts/setup +++ b/scripts/setup @@ -379,11 +379,15 @@ eval ${PREFIX} invenio utils runindex --raise-on-error eval ${PREFIX} invenio utils reindex -t cont --yes-i-know --no-info eval ${PREFIX} invenio utils runindex --raise-on-error +info_msg "- Local fields ${DATA_PATH}/local_fields.json ${CREATE_LAZY} ${DONT_STOP}" +eval ${PREFIX} invenio fixtures create --pid_type lofi ${DATA_PATH}/local_fields.json --append ${CREATE_LAZY} ${DONT_STOP} +eval ${PREFIX} invenio utils reindex -t lofi --yes-i-know --no-info +eval ${PREFIX} invenio utils runindex --raise-on-error + # create vendors info_msg "Acquisition vendors: ${DATA_PATH}/vendors.json ${CREATE_LAZY} ${DONT_STOP}" eval ${PREFIX} invenio fixtures create --pid_type vndr ${DATA_PATH}/vendors.json --append ${CREATE_LAZY} ${DONT_STOP} eval ${PREFIX} invenio utils reindex -t vndr --yes-i-know --no-info - eval ${PREFIX} invenio utils runindex --raise-on-error # create serials patterns diff --git a/setup.py b/setup.py index b70788b63f..3588926366 100644 --- a/setup.py +++ b/setup.py @@ -174,6 +174,7 @@ def run(self): 'item_types = rero_ils.modules.item_types.models', 'items = rero_ils.modules.items.models', 'libraries = rero_ils.modules.libraries.models', + 'local_fields = rero_ils.modules.local_fields.models', 'locations = rero_ils.modules.locations.models', 'mef = rero_ils.modules.contributions.models', 'notifications = rero_ils.modules.notifications.models', @@ -200,6 +201,7 @@ def run(self): 'item_id = rero_ils.modules.items.api:item_id_minter', 'item_type_id = rero_ils.modules.item_types.api:item_type_id_minter', 'library_id = rero_ils.modules.libraries.api:library_id_minter', + 'local_field_id = rero_ils.modules.local_fields.api:local_field_id_minter', 'location_id = rero_ils.modules.locations.api:location_id_minter', 'notification_id = rero_ils.modules.notifications.api:notification_id_minter', 'organisation_id = rero_ils.modules.organisations.api:organisation_id_minter', @@ -225,6 +227,7 @@ def run(self): 'item_id = rero_ils.modules.items.api:item_id_fetcher', 'item_type_id = rero_ils.modules.item_types.api:item_type_id_fetcher', 'library_id = rero_ils.modules.libraries.api:library_id_fetcher', + 'local_field_id = rero_ils.modules.local_fields.api:local_field_id_fetcher', 'location_id = rero_ils.modules.locations.api:location_id_fetcher', 'notification_id = rero_ils.modules.notifications.api:notification_id_fetcher', 'organisation_id = rero_ils.modules.organisations.api:organisation_id_fetcher', @@ -252,6 +255,7 @@ def run(self): 'items = rero_ils.modules.items.jsonschemas', 'libraries = rero_ils.modules.libraries.jsonschemas', 'loans = rero_ils.modules.loans.jsonschemas', + 'local_fields = rero_ils.modules.local_fields.jsonschemas', 'locations = rero_ils.modules.locations.jsonschemas', 'notifications = rero_ils.modules.notifications.jsonschemas', 'organisations = rero_ils.modules.organisations.jsonschemas', @@ -278,6 +282,7 @@ def run(self): 'items = rero_ils.modules.items.mappings', 'libraries = rero_ils.modules.libraries.mappings', 'loans = rero_ils.modules.loans.mappings', + 'local_fields = rero_ils.modules.local_fields.mappings', 'locations = rero_ils.modules.locations.mappings', 'notifications = rero_ils.modules.notifications.mappings', 'organisations = rero_ils.modules.organisations.mappings', @@ -315,6 +320,7 @@ def run(self): 'items = rero_ils.modules.items.jsonresolver', 'libraries = rero_ils.modules.libraries.jsonresolver', 'loans = rero_ils.modules.loans.jsonresolver', + 'local_fields = rero_ils.modules.local_fields.jsonresolver', 'locations = rero_ils.modules.locations.jsonresolver', 'notifications = rero_ils.modules.notifications.jsonresolver', 'organisations = rero_ils.modules.organisations.jsonresolver', diff --git a/tests/api/local_fields/test_local_fields_permissions.py b/tests/api/local_fields/test_local_fields_permissions.py new file mode 100644 index 0000000000..a5640433c7 --- /dev/null +++ b/tests/api/local_fields/test_local_fields_permissions.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from flask import url_for +from invenio_accounts.testutils import login_user_via_session +from utils import get_json + + +def test_local_fields_permissions_api( + client, org_martigny, document, local_field_martigny, + patron_sion_no_email, librarian_martigny_no_email): + """Test local fields permissions api.""" + local_field_permissions_url = url_for( + 'api_blueprint.permissions', + route_name='local_fields' + ) + local_field_martigny_permission_url = url_for( + 'api_blueprint.permissions', + route_name='local_fields', + record_pid=local_field_martigny.pid + ) + + # Not logged + res = client.get(local_field_permissions_url) + assert res.status_code == 401 + + # Logged as patron + login_user_via_session(client, patron_sion_no_email.user) + res = client.get(local_field_permissions_url) + assert res.status_code == 403 + + # Logged as + login_user_via_session(client, librarian_martigny_no_email.user) + res = client.get(local_field_martigny_permission_url) + assert res.status_code == 200 + data = get_json(res) + assert data['create']['can'] + assert data['delete']['can'] + assert data['list']['can'] + assert data['read']['can'] + assert data['update']['can'] diff --git a/tests/api/test_monitoring_rest.py b/tests/api/test_monitoring_rest.py index 7017011301..c6daf0319e 100644 --- a/tests/api/test_monitoring_rest.py +++ b/tests/api/test_monitoring_rest.py @@ -51,6 +51,7 @@ def test_monitoring_es_db_counts(client): 'lib': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'libraries'}, 'loanid': {'db': 0}, 'loc': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'locations'}, + 'lofi': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'local_fields'}, 'notif': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'notifications'}, 'org': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'organisations'}, 'ptre': {'db': 0, 'db-es': 0, 'es': 0, diff --git a/tests/conftest.py b/tests/conftest.py index 48ac253c00..cea65af703 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,6 +92,14 @@ def holdings(): return data +@pytest.fixture(scope="module") +def local_fields(): + """Load local fields file.""" + with open(join(dirname(__file__), 'data/local_fields.json')) as f: + data = json.load(f) + return data + + @pytest.fixture(scope="session") def csv_header(): """Load json headers.""" diff --git a/tests/data/local_fields.json b/tests/data/local_fields.json new file mode 100644 index 0000000000..e70090d310 --- /dev/null +++ b/tests/data/local_fields.json @@ -0,0 +1,36 @@ +{ + "lofi1": { + "$schema": "https://ils.rero.ch/schemas/local_fields/local_field-v0.0.1.json", + "pid": "lofi1", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/org1" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/doc1" + }, + "fields": { + "field_1": [ + "Auteur vald\u00f4tain", + "Bibliographie vald\u00f4taine" + ] + } + }, + "lofi2": { + "$schema": "https://ils.rero.ch/schemas/local_fields/local_field-v0.0.1.json", + "pid": "lofi2", + "organisation": { + "$ref": "https://ils.rero.ch/api/organisations/org2" + }, + "parent": { + "$ref": "https://ils.rero.ch/api/documents/doc1" + }, + "fields": { + "field_1": [ + "Auteur anglais" + ], + "field_2": [ + "Production by Hogwarts' students" + ] + } + } +} diff --git a/tests/fixtures/metadata.py b/tests/fixtures/metadata.py index 354b0f0f96..e36ade9871 100644 --- a/tests/fixtures/metadata.py +++ b/tests/fixtures/metadata.py @@ -30,6 +30,7 @@ from rero_ils.modules.documents.api import Document, DocumentsSearch from rero_ils.modules.holdings.api import Holding, HoldingsSearch from rero_ils.modules.items.api import Item, ItemsSearch +from rero_ils.modules.local_fields.api import LocalField, LocalFieldsSearch from rero_ils.modules.templates.api import Template, TemplatesSearch @@ -965,3 +966,42 @@ def templ_patron_public_martigny( reindex=True) flush_index(TemplatesSearch.Meta.index) return template + + +# --- LOCAL FIELDS +@pytest.fixture(scope="module") +def local_field_martigny_data(local_fields): + """Load Local field 1 data.""" + return deepcopy(local_fields.get('lofi1')) + + +@pytest.fixture(scope="module") +def local_field_martigny( + app, org_martigny, document, local_field_martigny_data): + """Load local field.""" + local_field = LocalField.create( + data=local_field_martigny_data, + delete_pid=False, + dbcommit=True, + reindex=True) + flush_index(LocalFieldsSearch.Meta.index) + return local_field + + +@pytest.fixture(scope="module") +def local_field_sion_data(local_fields): + """Load Local field 2 data.""" + return deepcopy(local_fields.get('lofi2')) + + +@pytest.fixture(scope="module") +def local_field_sion( + app, org_martigny, document, local_field_sion_data): + """Load local field.""" + local_field = LocalField.create( + data=local_field_sion_data, + delete_pid=False, + dbcommit=True, + reindex=True) + flush_index(LocalFieldsSearch.Meta.index) + return local_field diff --git a/tests/ui/documents/test_documents_jsonresolver.py b/tests/ui/documents/test_documents_jsonresolver.py index b40669ff1d..23800c543e 100644 --- a/tests/ui/documents/test_documents_jsonresolver.py +++ b/tests/ui/documents/test_documents_jsonresolver.py @@ -27,7 +27,7 @@ def test_documents_jsonresolver(document): rec = Record.create({ 'document': {'$ref': 'https://ils.rero.ch/api/documents/doc1'} }) - assert rec.replace_refs().get('document') == {'pid': 'doc1'} + assert rec.replace_refs().get('document') == {'type': 'doc', 'pid': 'doc1'} # deleted record document.delete() diff --git a/tests/ui/holdings/test_holdings_jsonresolver.py b/tests/ui/holdings/test_holdings_jsonresolver.py index 6b6e77b6f2..a11f023fb4 100644 --- a/tests/ui/holdings/test_holdings_jsonresolver.py +++ b/tests/ui/holdings/test_holdings_jsonresolver.py @@ -28,7 +28,9 @@ def test_holdings_jsonresolver(holding_lib_martigny): rec = Record.create({ 'holding': {'$ref': 'https://ils.rero.ch/api/holdings/holding1'} }) - assert rec.replace_refs().get('holding') == {'pid': 'holding1'} + assert rec.replace_refs().get('holding') == { + 'type': 'hold', 'pid': 'holding1' + } # deleted record holding_lib_martigny.delete() diff --git a/tests/ui/ill_requests/test_ill_requests_jsonresolver.py b/tests/ui/ill_requests/test_ill_requests_jsonresolver.py index c1d5669d1a..17b03ff820 100644 --- a/tests/ui/ill_requests/test_ill_requests_jsonresolver.py +++ b/tests/ui/ill_requests/test_ill_requests_jsonresolver.py @@ -27,7 +27,9 @@ def test_ill_requests_jsonresolver(ill_request_martigny): rec = Record.create({ 'ill_request': {'$ref': 'https://ils.rero.ch/api/ill_requests/illr1'} }) - assert rec.replace_refs().get('ill_request') == {'pid': 'illr1'} + assert rec.replace_refs().get('ill_request') == { + 'type': 'illr', 'pid': 'illr1' + } # deleted record ill_request_martigny.delete() diff --git a/tests/ui/item_types/test_item_types_jsonresolver.py b/tests/ui/item_types/test_item_types_jsonresolver.py index dcc0e4f0e4..0718a9ff2d 100644 --- a/tests/ui/item_types/test_item_types_jsonresolver.py +++ b/tests/ui/item_types/test_item_types_jsonresolver.py @@ -27,7 +27,9 @@ def test_item_types_jsonresolver(item_type_standard_martigny): rec = Record.create({ 'item_type': {'$ref': 'https://ils.rero.ch/api/item_types/itty1'} }) - assert rec.replace_refs().get('item_type') == {'pid': 'itty1'} + assert rec.replace_refs().get('item_type') == { + 'type': 'itty', 'pid': 'itty1' + } # deleted record item_type_standard_martigny.delete() diff --git a/tests/ui/items/test_items_jsonresolver.py b/tests/ui/items/test_items_jsonresolver.py index cc694bac6c..b841c02307 100644 --- a/tests/ui/items/test_items_jsonresolver.py +++ b/tests/ui/items/test_items_jsonresolver.py @@ -27,7 +27,7 @@ def test_items_jsonresolver(item_lib_martigny): rec = Record.create({ 'item': {'$ref': 'https://ils.rero.ch/api/items/item1'} }) - assert rec.replace_refs().get('item') == {'pid': 'item1'} + assert rec.replace_refs().get('item') == {'type': 'item', 'pid': 'item1'} # deleted record item_lib_martigny.delete() diff --git a/tests/ui/libraries/test_libraries_jsonresolver.py b/tests/ui/libraries/test_libraries_jsonresolver.py index b99d1be03e..ab71134a81 100644 --- a/tests/ui/libraries/test_libraries_jsonresolver.py +++ b/tests/ui/libraries/test_libraries_jsonresolver.py @@ -28,7 +28,7 @@ def test_libraries_jsonresolver(lib_martigny): rec = Record.create({ 'library': {'$ref': 'https://ils.rero.ch/api/libraries/lib1'} }) - assert rec.replace_refs().get('library') == {'pid': 'lib1'} + assert rec.replace_refs().get('library') == {'type': 'lib', 'pid': 'lib1'} # deleted record library.delete() diff --git a/tests/ui/loans/test_loans_jsonresolver.py b/tests/ui/loans/test_loans_jsonresolver.py index 7f8cdc52a5..6024d17b5f 100644 --- a/tests/ui/loans/test_loans_jsonresolver.py +++ b/tests/ui/loans/test_loans_jsonresolver.py @@ -27,7 +27,7 @@ def test_loans_jsonresolver(loan_pending_martigny): rec = Record.create({ 'loan': {'$ref': 'https://ils.rero.ch/api/loans/1'} }) - assert rec.replace_refs().get('loan') == {'pid': '1'} + assert rec.replace_refs().get('loan') == {'type': 'loanid', 'pid': '1'} # deleted record loan_pending_martigny.delete() diff --git a/tests/ui/local_fields/conftest.py b/tests/ui/local_fields/conftest.py new file mode 100644 index 0000000000..0a697ee8c9 --- /dev/null +++ b/tests/ui/local_fields/conftest.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Common pytest libraries.""" + +import pytest + + +@pytest.yield_fixture(scope='module') +def local_fields_records( + local_field_martigny, + local_field_sion, +): + """Local fields for test mapping.""" diff --git a/tests/ui/local_fields/test_local_fields_api.py b/tests/ui/local_fields/test_local_fields_api.py new file mode 100644 index 0000000000..3c4f97d61c --- /dev/null +++ b/tests/ui/local_fields/test_local_fields_api.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Local fields Record tests.""" + +from __future__ import absolute_import, print_function + +from utils import flush_index, get_mapping + +from rero_ils.modules.documents.api import DocumentsSearch +from rero_ils.modules.local_fields.api import LocalField, LocalFieldsSearch +from rero_ils.modules.local_fields.api import local_field_id_fetcher as fetcher + + +def test_local_fields_es(client, local_field_martigny): + """Test.""" + local_field = LocalField.get_local_fields_by_resource('doc', 'doc1') + assert len(local_field) == 1 + + local_field = LocalField.get_local_fields_by_resource( + 'doc', 'doc1', 'org2') + assert len(local_field) == 0 + + +def test_local_fields_es_mapping(db, org_sion, document, + local_field_sion_data): + """Test local fields elasticsearch mapping.""" + search = LocalFieldsSearch() + mapping = get_mapping(search.Meta.index) + assert mapping + lofi = LocalField.create(local_field_sion_data, dbcommit=True, + reindex=True, delete_pid=True) + flush_index(LocalFieldsSearch.Meta.index) + assert mapping == get_mapping(search.Meta.index) + + assert lofi == local_field_sion_data + assert lofi.get('pid') == '1' + + lofi = LocalField.get_record_by_pid('1') + assert lofi == local_field_sion_data + + fetched_pid = fetcher(lofi.id, lofi) + assert fetched_pid.pid_value == '1' + assert fetched_pid.pid_type == 'lofi' + + document_pid = lofi.replace_refs()['parent']['pid'] + search = DocumentsSearch().filter( + 'term', pid=document_pid) + document = list(search.scan())[0].to_dict() + for field in document['local_fields']: + if field['organisation_pid'] == document_pid: + assert field['fields'] ==\ + local_field_sion_data['fields']['field_1'] diff --git a/tests/ui/local_fields/test_local_fields_jsonresolver.py b/tests/ui/local_fields/test_local_fields_jsonresolver.py new file mode 100644 index 0000000000..06bc0789b3 --- /dev/null +++ b/tests/ui/local_fields/test_local_fields_jsonresolver.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Local fields JSONResolver tests.""" + +import pytest +from invenio_records.api import Record +from jsonref import JsonRefError + + +def test_local_field_jsonresolver(local_field_martigny): + """Test local fields json resolver.""" + local_field = local_field_martigny + rec = Record.create({ + 'local_field': {'$ref': 'https://ils.rero.ch/api/local_fields/lofi1'} + }) + assert rec.replace_refs().get('local_field') == {'pid': 'lofi1'} + + # deleted record + local_field.delete() + with pytest.raises(JsonRefError): + rec.replace_refs().dumps() + + # non existing record + rec = Record.create({ + 'local_fields': {'$ref': 'https://ils.rero.ch/api/local_fields/n_e'} + }) + with pytest.raises(JsonRefError): + rec.replace_refs().dumps() diff --git a/tests/ui/local_fields/test_local_fields_mapping.py b/tests/ui/local_fields/test_local_fields_mapping.py new file mode 100644 index 0000000000..2a4047e6c5 --- /dev/null +++ b/tests/ui/local_fields/test_local_fields_mapping.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Libraries elasticsearch mapping tests.""" + +from utils import get_mapping + +from rero_ils.modules.local_fields.api import LocalField, LocalFieldsSearch + + +def test_local_field_es_mapping( + es_clear, db, org_martigny, document, local_field_martigny_data): + """Test local field elasticsearch mapping.""" + search = LocalFieldsSearch() + mapping = get_mapping(search.Meta.index) + assert mapping + LocalField.create( + local_field_martigny_data, + dbcommit=True, + reindex=True, + delete_pid=True) + assert mapping == get_mapping(search.Meta.index) + + +def test_libraries_search_mapping( + app, org_martigny, org_sion, document, local_fields_records): + """Test local field search mapping.""" + search = LocalFieldsSearch() + + c = search.query( + 'query_string', query='Auteur' + ).count() + assert c == 2 + + c = search.query('query_string', query='Bibliographie').count() + assert c == 1 + + pids = [r.pid for r in search.query( + 'match', fields__field_2='students').source(['pid']).scan()] + assert 'lofi2' in pids diff --git a/tests/ui/locations/test_locations_jsonresolver.py b/tests/ui/locations/test_locations_jsonresolver.py index b51e0b4d44..1d414ddadd 100644 --- a/tests/ui/locations/test_locations_jsonresolver.py +++ b/tests/ui/locations/test_locations_jsonresolver.py @@ -27,7 +27,7 @@ def test_locations_jsonresolver(loc_public_martigny): rec = Record.create({ 'location': {'$ref': 'https://ils.rero.ch/api/locations/loc1'} }) - assert rec.replace_refs().get('location') == {'pid': 'loc1'} + assert rec.replace_refs().get('location') == {'type': 'loc', 'pid': 'loc1'} # deleted record loc_public_martigny.delete() diff --git a/tests/ui/organisations/test_organisations_jsonresolver.py b/tests/ui/organisations/test_organisations_jsonresolver.py index bd0e98e9eb..d7159a05fc 100644 --- a/tests/ui/organisations/test_organisations_jsonresolver.py +++ b/tests/ui/organisations/test_organisations_jsonresolver.py @@ -27,7 +27,9 @@ def test_organisations_jsonresolver(app, organisation_temp): rec = Record.create({ 'organisation': {'$ref': 'https://ils.rero.ch/api/organisations/1'} }) - assert rec.replace_refs().get('organisation') == {'pid': '1'} + assert rec.replace_refs().get('organisation') == { + 'type': 'org', 'pid': '1' + } # deleted record organisation_temp.delete() diff --git a/tests/ui/patron_types/test_patron_types_jsonresolver.py b/tests/ui/patron_types/test_patron_types_jsonresolver.py index 414474dba1..78b4c523ed 100644 --- a/tests/ui/patron_types/test_patron_types_jsonresolver.py +++ b/tests/ui/patron_types/test_patron_types_jsonresolver.py @@ -27,7 +27,9 @@ def test_patron_types_jsonresolver(app, patron_type_tmp): rec = Record.create({ 'patron_type': {'$ref': 'https://ils.rero.ch/api/patron_types/1'} }) - assert rec.replace_refs().get('patron_type') == {'pid': '1'} + assert rec.replace_refs().get('patron_type') == { + 'type': 'ptty', 'pid': '1' + } # deleted record patron_type_tmp.delete() diff --git a/tests/ui/patrons/test_patrons_jsonresolver.py b/tests/ui/patrons/test_patrons_jsonresolver.py index 6ea2800559..9e074b5868 100644 --- a/tests/ui/patrons/test_patrons_jsonresolver.py +++ b/tests/ui/patrons/test_patrons_jsonresolver.py @@ -27,7 +27,9 @@ def test_patrons_jsonresolver(system_librarian_martigny_no_email): rec = Record.create({ 'patron': {'$ref': 'https://ils.rero.ch/api/patrons/ptrn1'} }) - assert rec.replace_refs().get('patron') == {'pid': 'ptrn1'} + assert rec.replace_refs().get('patron') == { + 'type': 'ptrn', 'pid': 'ptrn1' + } # deleted record system_librarian_martigny_no_email.delete() diff --git a/tests/ui/test_monitoring.py b/tests/ui/test_monitoring.py index b2ca707852..3f42190603 100644 --- a/tests/ui/test_monitoring.py +++ b/tests/ui/test_monitoring.py @@ -46,6 +46,7 @@ def test_monitoring(app, document_sion_items_data, script_info): ' 0 lib 0 libraries 0', ' loanid 0', ' 0 loc 0 locations 0', + ' 0 lofi 0 local_fields 0', ' 0 notif 0 notifications 0', ' 0 org 0 organisations 0', ' 0 ptre 0 patron_transaction_events 0', @@ -87,6 +88,7 @@ def test_monitoring(app, document_sion_items_data, script_info): 'lib': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'libraries'}, 'loanid': {'db': 0}, 'loc': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'locations'}, + 'lofi': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'local_fields'}, 'notif': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'notifications'}, 'org': {'db': 0, 'db-es': 0, 'es': 0, 'index': 'organisations'}, 'ptre': {'db': 0, 'db-es': 0, 'es': 0, diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d0600977ea..b9827d7b7b 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -103,6 +103,15 @@ def library_schema(monkeypatch): return get_schema(monkeypatch, schema_in_bytes) +@pytest.fixture() +def local_fields_schema(monkeypatch): + """Local fields Jsonschema for records.""" + schema_in_bytes = resource_string( + 'rero_ils.modules.local_fields.jsonschemas', + 'local_fields/local_field-v0.0.1.json') + return get_schema(monkeypatch, schema_in_bytes) + + @pytest.fixture() def location_schema(monkeypatch): """Location Jsonschema for records.""" diff --git a/tests/unit/test_local_fields_jsonschema.py b/tests/unit/test_local_fields_jsonschema.py new file mode 100644 index 0000000000..a9befd9327 --- /dev/null +++ b/tests/unit/test_local_fields_jsonschema.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2020 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Local fields JSON schema tests.""" + +from __future__ import absolute_import, print_function + +import copy + +import pytest +from jsonschema import validate +from jsonschema.exceptions import ValidationError + + +def test_local_fields_fields_required( + local_fields_schema, local_field_martigny_data): + """Test required for local fields jsonschemas.""" + record = copy.deepcopy(local_field_martigny_data) + validate(record, local_fields_schema) + + # Check minlength + with pytest.raises(ValidationError): + record['fields'] = ['12'] + validate(record, local_fields_schema) + + # Check missing fields + with pytest.raises(ValidationError): + del record['fields'] + validate(record, local_fields_schema) + + # Check empty schema + with pytest.raises(ValidationError): + validate({}, local_fields_schema) + + +def test_local_fields_all_jsonschema_keys_values( + local_fields_schema, local_field_martigny_data): + """Test all keys and values for local fields jsonschema.""" + record = copy.deepcopy(local_field_martigny_data) + validate(record, local_fields_schema) + validator = [ + {'key': 'pid', 'value': 25}, + {'key': 'organisation', 'value': 25}, + {'key': 'parent', 'value': 25}, + + ] + for element in validator: + with pytest.raises(ValidationError): + record[element['key']] = element['value'] + validate(record, local_fields_schema)