diff --git a/rero_ils/config.py b/rero_ils/config.py index b485d4ae26..83f6390893 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -1340,6 +1340,26 @@ def _(x): 'documents', RERO_ILS_DEFAULT_AGGREGATION_SIZE) RECORDS_REST_FACETS = dict( documents=dict( + i18n_aggs=dict( + author=dict( + en=dict( + terms=dict(field='facet_contribution_en', + size=DOCUMENTS_AGGREGATION_SIZE) + ), + fr=dict( + terms=dict(field='facet_contribution_fr', + size=DOCUMENTS_AGGREGATION_SIZE) + ), + de=dict( + terms=dict(field='facet_contribution_de', + size=DOCUMENTS_AGGREGATION_SIZE) + ), + it=dict( + terms=dict(field='facet_contribution_it', + size=DOCUMENTS_AGGREGATION_SIZE) + ), + ), + ), aggs=dict( # The organisation or library facet is defined # dynamically during the query (query.py) @@ -1347,22 +1367,6 @@ def _(x): terms=dict(field='type', size=DOCUMENTS_AGGREGATION_SIZE) ), - contribution__en=dict( - terms=dict(field='facet_contribution_en', - size=DOCUMENTS_AGGREGATION_SIZE) - ), - contribution__fr=dict( - terms=dict(field='facet_contribution_fr', - size=DOCUMENTS_AGGREGATION_SIZE) - ), - contribution__de=dict( - terms=dict(field='facet_contribution_de', - size=DOCUMENTS_AGGREGATION_SIZE) - ), - contribution__it=dict( - terms=dict(field='facet_contribution_it', - size=DOCUMENTS_AGGREGATION_SIZE) - ), language=dict( terms=dict(field='language.value', size=DOCUMENTS_AGGREGATION_SIZE) diff --git a/rero_ils/facets.py b/rero_ils/facets.py new file mode 100644 index 0000000000..8fda8c9816 --- /dev/null +++ b/rero_ils/facets.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019 RERO +# Copyright (C) 2020 UCLOUVAIN +# +# 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 . + +"""Facets and factories for result aggregation.""" + +from __future__ import absolute_import, print_function + +from flask import current_app, request +from invenio_i18n.ext import current_i18n + + +def i18n_facets_factory(search, index): + """Add a i18n facets to search query. + + It's possible to select facets which should be added to query + by passing their name in `facets` parameter. + + :param search: Basic search object. + :param index: Index name. + :returns: the new search object. + """ + facets_config = current_app.config['RECORDS_REST_FACETS'].get(index, {}) + # i18n Aggregations. + for name, agg in facets_config.get("i18n_aggs", {}).items(): + i18n_agg = agg.get( + request.args.get("lang", current_i18n.language), + agg.get(current_app.config.get('BABEL_DEFAULT_LANGUAGE')) + ) + search.aggs[name] = i18n_agg if not callable(i18n_agg) \ + else i18n_agg() + return search diff --git a/rero_ils/query.py b/rero_ils/query.py index 176060d94d..ee326c6a89 100644 --- a/rero_ils/query.py +++ b/rero_ils/query.py @@ -2,6 +2,7 @@ # # RERO ILS # Copyright (C) 2019 RERO +# Copyright (C) 2020 UCLOUVAIN # # 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 @@ -25,6 +26,7 @@ from flask import current_app, request from invenio_records_rest.errors import InvalidQueryRESTError +from .facets import i18n_facets_factory from .modules.organisations.api import Organisation, current_organisation from .modules.patrons.api import current_patron @@ -270,6 +272,8 @@ def _boosting_parser(query_boosting, search_index): raise InvalidQueryRESTError() search, urlkwargs = default_facets_factory(search, search_index) + # i18n translated facets + search = i18n_facets_factory(search, search_index) search, sortkwargs = default_sorter_factory(search, search_index) for key, value in sortkwargs.items(): urlkwargs.add(key, value) diff --git a/tests/api/documents/test_documents_rest.py b/tests/api/documents/test_documents_rest.py index 4d55ccd1e3..ab9423d80d 100644 --- a/tests/api/documents/test_documents_rest.py +++ b/tests/api/documents/test_documents_rest.py @@ -173,8 +173,7 @@ def test_documents_facets( aggs = data['aggregations'] # check all facets are present for facet in [ - 'document_type', 'contribution__en', 'contribution__fr', - 'contribution__de', 'contribution__it', 'language', 'subject', 'status' + 'document_type', 'author', 'language', 'subject', 'status' ]: assert aggs[facet] diff --git a/tests/api/items/test_item_rest.py b/tests/api/items/test_item_rest.py deleted file mode 100644 index 855bed0db7..0000000000 --- a/tests/api/items/test_item_rest.py +++ /dev/null @@ -1,1008 +0,0 @@ -# -*- 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 . - -"""Tests REST API items.""" - -import json -from copy import deepcopy -from datetime import datetime, timezone - -import ciso8601 -import mock -import pytest -from flask import url_for -from invenio_accounts.testutils import login_user_via_session -from utils import VerifyRecordPermissionPatch, flush_index, get_json, postdata - -from rero_ils.modules.circ_policies.api import CircPoliciesSearch -from rero_ils.modules.errors import RecordValidationError -from rero_ils.modules.items.api import Item -from rero_ils.modules.items.models import ItemNoteTypes, ItemStatus -from rero_ils.modules.loans.api import Loan, LoanAction -from rero_ils.modules.loans.utils import get_extension_params - - -def test_items_permissions(client, item_lib_martigny, - patron_martigny_no_email, - json_header): - """Test record retrieval.""" - item_url = url_for('invenio_records_rest.item_item', pid_value='item1') - - res = client.get(item_url) - assert res.status_code == 200 - - res, _ = postdata( - client, - 'invenio_records_rest.item_list', - {} - ) - assert res.status_code == 401 - - res = client.put( - url_for('invenio_records_rest.item_item', pid_value='item1'), - data={}, - headers=json_header - ) - - res = client.delete(item_url) - assert res.status_code == 401 - - views = [ - 'api_item.checkout', - 'api_item.checkin', - 'api_item.cancel_item_request', - 'api_item.validate_request', - 'api_item.receive', - 'api_item.return_missing', - 'api_item.extend_loan', - 'api_item.librarian_request' - ] - for view in views: - res, _ = postdata(client, view, {}) - assert res.status_code == 401 - res = client.get( - url_for('api_item.requested_loans', library_pid='test'), - data={} - ) - assert res.status_code == 401 - login_user_via_session(client, patron_martigny_no_email.user) - for view in views: - res, _ = postdata(client, view, {}) - assert res.status_code == 403 - res = client.get( - url_for('api_item.requested_loans', library_pid='test'), - data={} - ) - assert res.status_code == 403 - - -@mock.patch('invenio_records_rest.views.verify_record_permission', - mock.MagicMock(return_value=VerifyRecordPermissionPatch)) -def test_items_post_put_delete(client, document, loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny_data, json_header): - """Test record retrieval.""" - # Create record / POST - item_url = url_for('invenio_records_rest.item_item', pid_value='1') - list_url = url_for('invenio_records_rest.item_list', q='pid:1') - - # test when item has no barcode - item_record_with_no_barcode = deepcopy(item_lib_martigny_data) - item_record_with_no_barcode['pid'] = 'pid' - del item_record_with_no_barcode['barcode'] - res, data = postdata( - client, - 'invenio_records_rest.item_list', - item_record_with_no_barcode - ) - assert res.status_code == 201 - item_barcode = data['metadata']['barcode'] - assert item_barcode.startswith('f-') - # test updating an item with no barcode, keeps the old barcode - created_item = Item.get_record_by_pid('pid') - assert created_item.pid == 'pid' - item_to_update = deepcopy(created_item) - del item_to_update['barcode'] - updated_item = created_item.update( - data=item_to_update, dbcommit=True, reindex=True) - assert updated_item['barcode'].startswith('f-') - - # test replacing an item with no barcode, regenerates a new barcode - item_to_replace = deepcopy(updated_item) - del item_to_replace['barcode'] - replaced_item = created_item.replace( - data=item_to_replace, dbcommit=True, reindex=True) - assert replaced_item['barcode'].startswith('f-') - - # test when item has a dirty barcode - item_lib_martigny_data['pid'] = '1' - item_record_with_dirty_barcode = deepcopy(item_lib_martigny_data) - - item_record_with_dirty_barcode['barcode'] = ' {barcode} '.format( - barcode=item_record_with_dirty_barcode.get('barcode') - ) - res, data = postdata( - client, - 'invenio_records_rest.item_list', - item_record_with_dirty_barcode - ) - assert res.status_code == 201 - - # Check that the returned record matches the given data - data['metadata'].pop('available') - assert data['metadata'] == item_lib_martigny_data - - res = client.get(item_url) - assert res.status_code == 200 - - data = get_json(res) - data['metadata'].pop('available') - assert item_lib_martigny_data == data['metadata'] - - # Update record/PUT - data = item_lib_martigny_data - data['call_number'] = 'Test Name' - res = client.put( - item_url, - data=json.dumps(data), - headers=json_header - ) - assert res.status_code == 200 - # assert res.headers['ETag'] != '"{}"'.format(librarie.revision_id) - - # Check that the returned record matches the given data - data = get_json(res) - assert data['metadata']['call_number'] == 'Test Name' - - res = client.get(item_url) - assert res.status_code == 200 - - data = get_json(res) - assert data['metadata']['call_number'] == 'Test Name' - - res = client.get(list_url) - assert res.status_code == 200 - - data = get_json(res)['hits']['hits'][0] - assert data['metadata']['call_number'] == 'Test Name' - - # Delete record/DELETE - res = client.delete(item_url) - assert res.status_code == 204 - - res = client.get(item_url) - assert res.status_code == 410 - - -def test_checkout_default_policy(client, lib_martigny, - librarian_martigny_no_email, - patron_martigny_no_email, - loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circulation_policies): - """Test circ policy parameters""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - - from rero_ils.modules.circ_policies.api import CircPolicy - circ_policy = CircPolicy.provide_circ_policy( - item.library_pid, - 'ptty1', - 'itty1' - ) - - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - actions = data.get('action_applied') - loan = actions[LoanAction.CHECKOUT] - end_date = loan.get('end_date') - start_date = loan.get('start_date') - checkout_duration = (ciso8601.parse_datetime( - end_date) - ciso8601.parse_datetime(start_date)).days - - assert checkout_duration >= circ_policy.get('checkout_duration') - - # checkin - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan.get('pid'), - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_checkout_library_level_policy(client, lib_martigny, - librarian_martigny_no_email, - patron_martigny_no_email, - loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circ_policy_short_martigny): - """Test circ policy parameters""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - actions = data.get('action_applied') - loan = actions[LoanAction.CHECKOUT] - end_date = loan.get('end_date') - start_date = loan.get('start_date') - checkout_duration = (ciso8601.parse_datetime( - end_date) - ciso8601.parse_datetime(start_date)).days - assert checkout_duration >= circ_policy_short_martigny.get( - 'checkout_duration') - - # checkin - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan.get('pid'), - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_checkout_organisation_policy(client, lib_martigny, - librarian_martigny_no_email, - patron_martigny_no_email, - loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circ_policy_short_martigny): - """Test circ policy parameters""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - actions = data.get('action_applied') - loan = actions[LoanAction.CHECKOUT] - end_date = loan.get('end_date') - start_date = loan.get('start_date') - checkout_duration = (ciso8601.parse_datetime( - end_date) - ciso8601.parse_datetime(start_date)).days - assert checkout_duration >= circ_policy_short_martigny.get( - 'checkout_duration') - - # checkin - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan.get('pid'), - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_items_receive(client, librarian_martigny_no_email, - patron_martigny_no_email, loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circulation_policies): - """Test item receive.""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - assert not item.patron_has_an_active_loan_on_item( - patron_martigny_no_email.get('barcode')) - location = loc_public_martigny - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - item_data = data.get('metadata') - actions = data.get('action_applied') - assert item_data.get('status') == ItemStatus.ON_LOAN - assert actions.get(LoanAction.CHECKOUT) - assert item.patron_has_an_active_loan_on_item( - patron_martigny_no_email.get('barcode')) - loan_pid = actions[LoanAction.CHECKOUT].get('pid') - - # checkin - res, data = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_location_pid='fake', - transaction_user_pid=librarian_martigny_no_email.pid, - ) - ) - assert res.status_code == 200 - item_data = data.get('metadata') - actions = data.get('action_applied') - assert item_data.get('status') == ItemStatus.IN_TRANSIT - assert actions.get(LoanAction.CHECKIN) - - # receive - res, data = postdata( - client, - 'api_item.receive', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - item_data = data.get('metadata') - actions = data.get('action_applied') - assert item_data.get('status') == ItemStatus.ON_SHELF - assert actions.get(LoanAction.RECEIVE) - - -def test_items_no_extend(client, librarian_martigny_no_email, - patron_martigny_no_email, loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circ_policy_short_martigny): - """Test items when no renewals is possible.""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - location = loc_public_martigny - - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - actions = data.get('action_applied') - loan_pid = actions[LoanAction.CHECKOUT].get('pid') - assert not item.get_extension_count() - - circ_policy_short_martigny['number_renewals'] = 0 - - circ_policy_short_martigny.update( - data=circ_policy_short_martigny, - dbcommit=True, - reindex=True) - flush_index(CircPoliciesSearch.Meta.index) - - # extend loan - res, _ = postdata( - client, - 'api_item.extend_loan', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - - assert res.status_code == 403 - - circ_policy_short_martigny['number_renewals'] = 1 - - circ_policy_short_martigny.update( - data=circ_policy_short_martigny, - dbcommit=True, - reindex=True) - flush_index(CircPoliciesSearch.Meta.index) - - # checkin - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_items_deny_requests(client, librarian_martigny_no_email, - patron_martigny_no_email, loc_public_martigny, - item_type_standard_martigny, lib_martigny, - item_lib_martigny, json_header, - circ_policy_short_martigny): - """Test items when requests are denied.""" - location = loc_public_martigny - circ_policy_short_martigny['allow_requests'] = False - circ_policy_short_martigny.update( - data=circ_policy_short_martigny, - dbcommit=True, - reindex=True) - flush_index(CircPoliciesSearch.Meta.index) - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron = patron_martigny_no_email - patron_pid = patron.pid - - # request - res, _ = postdata( - client, - 'api_item.librarian_request', - dict( - item_pid=item_pid, - pickup_location_pid=location.pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 403 - - # test can request because of a circulation policy does not allow request - res = client.get( - url_for( - 'api_item.can_request', - item_pid=item_pid, - library_pid=lib_martigny.pid, - patron_barcode=patron.get('barcode') - ) - ) - assert res.status_code == 200 - data = get_json(res) - assert not data.get('can_request') - - circ_policy_short_martigny['allow_requests'] = True - circ_policy_short_martigny.update( - data=circ_policy_short_martigny, - dbcommit=True, - reindex=True) - flush_index(CircPoliciesSearch.Meta.index) - assert circ_policy_short_martigny.get('allow_requests') - - -def test_extend_possible_actions(client, item_lib_martigny, - loc_public_martigny, - librarian_martigny_no_email, - patron_martigny_no_email, - circ_policy_short_martigny): - """Extend action changes according to params of cipo.""" - login_user_via_session(client, librarian_martigny_no_email.user) - circ_policy = circ_policy_short_martigny - item = item_lib_martigny - patron_pid = patron_martigny_no_email.pid - res, _ = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item.pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - - res = client.get( - url_for('api_item.loans', patron_pid=patron_pid) - ) - assert res.status_code == 200 - data = get_json(res) - assert data.get('hits').get('total') == 1 - actions = data.get('hits').get('hits')[0].get('item').get('actions') - assert 'checkin' in actions - - from rero_ils.modules.circ_policies.api import CircPolicy - circ_policy = CircPolicy.provide_circ_policy( - item.library_pid, - 'ptty1', - 'itty1' - ) - - circ_policy['number_renewals'] = 0 - circ_policy.update( - circ_policy, - dbcommit=True, - reindex=True - ) - res = client.get( - url_for('api_item.loans', patron_pid=patron_pid) - ) - assert res.status_code == 200 - data = get_json(res) - assert data.get('hits').get('total') == 1 - actions = data.get('hits').get('hits')[0].get('item').get('actions') - assert 'extend_loan' not in actions - assert 'checkin' in actions - loan_pid = data.get('hits').get('hits')[0].get('loan').get('pid') - # reset used objects - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item.pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - circ_policy['number_renewals'] = 1 - circ_policy.update( - circ_policy, - dbcommit=True, - reindex=True - ) - assert circ_policy['number_renewals'] == 1 - - -def test_items_extend_end_date(client, librarian_martigny_no_email, - patron_martigny_no_email, - loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circ_policy_short_martigny): - """Test correct renewal due date for items.""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - item_pid = item.pid - patron_pid = patron_martigny_no_email.pid - - # checkout - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item_pid, - patron_pid=patron_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - actions = data.get('action_applied') - loan_pid = actions[LoanAction.CHECKOUT].get('pid') - loan = Loan.get_record_by_pid(loan_pid) - assert not item.get_extension_count() - - max_count = get_extension_params(loan=loan, parameter_name='max_count') - renewal_duration_policy = circ_policy_short_martigny['renewal_duration'] - renewal_duration = get_extension_params( - loan=loan, parameter_name='duration_default') - assert renewal_duration_policy <= renewal_duration.days - - # extend loan - res, data = postdata( - client, - 'api_item.extend_loan', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - - assert res.status_code == 200 - - # Compare expected loan date with processed one - # first get loan UTC date - actions = data.get('action_applied') - loan_pid = actions[LoanAction.EXTEND].get('pid') - loan = Loan.get_record_by_pid(loan_pid) - loan_date = loan.get('end_date') - # then process a date with current UTC date + renewal - current_date = datetime.now(timezone.utc) - calc_date = current_date + renewal_duration - # finally the comparison should give the same date (in UTC)! - assert ( - calc_date.strftime('%Y-%m-%d') == ciso8601.parse_datetime( - loan_date).astimezone(timezone.utc).strftime('%Y-%m-%d') - ) - - # checkin - res, _ = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item_pid, - pid=loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_multiple_loans_on_item_error(client, - patron_martigny_no_email, - patron2_martigny_no_email, - loc_public_martigny, - item_type_standard_martigny, - item_lib_martigny, json_header, - circulation_policies, - loc_public_fully, - librarian_martigny_no_email): - """Test MultipleLoansOnItemError.""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - checked_patron = patron2_martigny_no_email.pid - requested_patron = patron_martigny_no_email.pid - location = loc_public_martigny - # checkout to checked_patron - res, data = postdata( - client, - 'api_item.checkout', - dict( - item_pid=item.pid, - patron_pid=checked_patron, - transaction_location_pid=location.pid, - transaction_user_pid=librarian_martigny_no_email.pid - ) - ) - assert res.status_code == 200 - assert Item.get_record_by_pid(item.pid).get('status') == ItemStatus.ON_LOAN - item_data = data.get('metadata') - actions = data.get('action_applied') - assert item_data.get('status') == ItemStatus.ON_LOAN - assert actions.get(LoanAction.CHECKOUT) - loan_pid = actions[LoanAction.CHECKOUT].get('pid') - item = Item.get_record_by_pid(item.pid) - - # request by requested patron to pick at another location - res, data = postdata( - client, - 'api_item.librarian_request', - dict( - item_pid=item.pid, - pickup_location_pid=loc_public_fully.pid, - patron_pid=requested_patron, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - item_data = data.get('metadata') - actions = data.get('action_applied') - assert item_data.get('status') == ItemStatus.ON_LOAN - assert actions.get(LoanAction.REQUEST) - req_loan_pid = actions[LoanAction.REQUEST].get('pid') - item = Item.get_record_by_pid(item.pid) - - # checkin at the request location - res, data = postdata( - client, - 'api_item.checkin', - dict( - item_pid=item.pid, - pid=loan_pid, - transaction_location_pid=loc_public_fully.pid, - transaction_user_pid=librarian_martigny_no_email.pid - ) - ) - assert res.status_code == 200 - # test the returned three actions - loans = data.get('action_applied') - checked_in_loan = loans.get(LoanAction.CHECKIN) - cancelled_loan = loans.get(LoanAction.CANCEL) - validated_loan = loans.get(LoanAction.VALIDATE) - assert checked_in_loan.get('pid') == cancelled_loan.get('pid') - assert validated_loan.get('pid') == req_loan_pid - - assert Loan.get_record_by_pid(loan_pid).get('state') == 'CANCELLED' - new_loan = Loan.get_record_by_pid(req_loan_pid) - assert new_loan.get('state') == 'ITEM_AT_DESK' - assert Item.get_record_by_pid(item.pid).get('status') == \ - ItemStatus.AT_DESK - # cancel request - res, _ = postdata( - client, - 'api_item.cancel_item_request', - dict( - item_pid=item.pid, - pid=req_loan_pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - -def test_filtered_items_get( - client, librarian_martigny_no_email, item_lib_martigny, - item_lib_saxon, item_lib_fully, - librarian_sion_no_email, item_lib_sion): - """Test items filter by organisation.""" - # Martigny - login_user_via_session(client, librarian_martigny_no_email.user) - list_url = url_for('invenio_records_rest.item_list') - - res = client.get(list_url) - assert res.status_code == 200 - data = get_json(res) - assert data['hits']['total'] == 4 - - # Sion - login_user_via_session(client, librarian_sion_no_email.user) - list_url = url_for('invenio_records_rest.item_list') - - res = client.get(list_url) - assert res.status_code == 200 - data = get_json(res) - assert data['hits']['total'] == 1 - - -def test_items_notes(client, librarian_martigny_no_email, item_lib_martigny, - json_header): - """Test items notes.""" - - item = item_lib_martigny - login_user_via_session(client, librarian_martigny_no_email.user) - - # at start the items have one note - assert len(item.notes) == 1 - - # set one public & one staff note - item['notes'] = [ - {'type': ItemNoteTypes.PUBLIC, 'content': 'Public note'}, - {'type': ItemNoteTypes.STAFF, 'content': 'Staff note'} - ] - res = client.put( - url_for('invenio_records_rest.item_item', pid_value=item.pid), - data=json.dumps(item), - headers=json_header - ) - assert res.status_code == 200 - - # add a second public note -- This should fail because we can only have one - # note of each type for an item - item['notes'].append( - {'type': ItemNoteTypes.PUBLIC, 'content': 'Second public note'} - ) - with pytest.raises(RecordValidationError): - client.put( - url_for('invenio_records_rest.item_item', pid_value=item.pid), - data=json.dumps(item), - headers=json_header - ) - item['notes'] = item.notes[:-1] - - # get a specific type of notes - # --> public : should return a note - # --> checkin : should return nothing - # --> dummy : should never return something ! - assert item.get_note(ItemNoteTypes.PUBLIC) - assert item.get_note(ItemNoteTypes.CHECKIN) is None - assert item.get_note('dummy') is None - - -def test_pending_loans_order(client, librarian_martigny_no_email, - patron_martigny_no_email, loc_public_martigny, - item_type_standard_martigny, - item2_lib_martigny, json_header, - patron2_martigny_no_email, patron_sion_no_email, - circulation_policies): - """Test sort of pending loans.""" - login_user_via_session(client, librarian_martigny_no_email.user) - library_pid = librarian_martigny_no_email.replace_refs()['library']['pid'] - - res, _ = postdata( - client, - 'api_item.librarian_request', - dict( - item_pid=item2_lib_martigny.pid, - patron_pid=patron_sion_no_email.pid, - pickup_location_pid=loc_public_martigny.pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - - res, _ = postdata( - client, - 'api_item.librarian_request', - dict( - item_pid=item2_lib_martigny.pid, - patron_pid=patron_martigny_no_email.pid, - pickup_location_pid=loc_public_martigny.pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - res, _ = postdata( - client, - 'api_item.librarian_request', - dict( - item_pid=item2_lib_martigny.pid, - patron_pid=patron2_martigny_no_email.pid, - pickup_location_pid=loc_public_martigny.pid, - transaction_user_pid=librarian_martigny_no_email.pid, - transaction_location_pid=loc_public_martigny.pid - ) - ) - assert res.status_code == 200 - - # sort by pid asc - res = client.get( - url_for( - 'api_item.requested_loans', library_pid=library_pid, - sort='pid')) - assert res.status_code == 200 - data = get_json(res) - loans = data['hits']['hits'][0]['item']['pending_loans'] - assert loans[2]['pid'] > loans[1]['pid'] > loans[0]['pid'] - - # sort by pid desc - res = client.get( - url_for( - 'api_item.requested_loans', library_pid=library_pid, - sort='-pid')) - assert res.status_code == 200 - data = get_json(res) - loans = data['hits']['hits'][0]['item']['pending_loans'] - assert loans[2]['pid'] < loans[1]['pid'] < loans[0]['pid'] - - # sort by transaction desc - res = client.get( - url_for( - 'api_item.requested_loans', library_pid=library_pid, - sort='-transaction_date')) - assert res.status_code == 200 - data = get_json(res) - loans = data['hits']['hits'][0]['item']['pending_loans'] - assert loans[2]['pid'] < loans[1]['pid'] < loans[0]['pid'] - - # sort by patron_pid asc - res = client.get( - url_for( - 'api_item.requested_loans', library_pid=library_pid, - sort='patron_pid')) - assert res.status_code == 200 - data = get_json(res) - loans = data['hits']['hits'][0]['item']['pending_loans'] - assert loans[0]['patron_pid'] == patron_sion_no_email.pid - assert loans[1]['patron_pid'] == patron_martigny_no_email.pid - assert loans[2]['patron_pid'] == patron2_martigny_no_email.pid - - # sort by invalid field - res = client.get( - url_for( - 'api_item.requested_loans', library_pid=library_pid, - sort='does not exist')) - assert res.status_code == 500 - data = get_json(res) - assert 'RequestError(400' in data['status'] - - -def test_item_possible_actions(client, item_lib_martigny, - librarian_martigny_no_email, - patron_martigny_no_email, - circulation_policies): - """Possible action changes according to params of cipo.""" - login_user_via_session(client, librarian_martigny_no_email.user) - item = item_lib_martigny - patron_pid = patron_martigny_no_email.pid - res = client.get( - url_for( - 'api_item.item', - item_barcode=item.get('barcode'), - patron_pid=patron_pid - ) - ) - assert res.status_code == 200 - data = get_json(res) - - actions = data.get('metadata').get('item').get('actions') - assert 'checkout' in actions - - from rero_ils.modules.circ_policies.api import CircPolicy - circ_policy = CircPolicy.provide_circ_policy( - item.library_pid, - 'ptty1', - 'itty1' - ) - - circ_policy['allow_checkout'] = False - circ_policy.update( - circ_policy, - dbcommit=True, - reindex=True - ) - res = client.get( - url_for( - 'api_item.item', - item_barcode=item.get('barcode'), - patron_pid=patron_pid - ) - ) - assert res.status_code == 200 - data = get_json(res) - - actions = data.get('metadata').get('item').get('actions') - assert 'checkout' not in actions - - circ_policy['allow_checkout'] = True - circ_policy.update( - circ_policy, - dbcommit=True, - reindex=True - ) - assert circ_policy['allow_checkout']