def test_delete_member(reindex, db, minimal_member_record, minimal_location_record): """Test Member delete.""" memb = MemberWithLocations.create(minimal_member_record) location1 = Location.create(minimal_location_record) memb.add_location(location1) pid1 = PersistentIdentifier.get_by_object('loc', 'rec', location1.id) location2 = Location.create(minimal_location_record) memb.add_location(location2) pid2 = PersistentIdentifier.get_by_object('loc', 'rec', location2.id) location3 = Location.create(minimal_location_record) memb.add_location(location3) pid3 = PersistentIdentifier.get_by_object('loc', 'rec', location3.id) memb.dbcommit() assert MembersLocationsMetadata.query.count() == 3 assert RecordMetadata.query.count() == 4 assert pid1.is_registered() assert pid2.is_registered() assert pid3.is_registered() memb.delete(force=True) assert MembersLocationsMetadata.query.count() == 0 assert RecordMetadata.query.count() == 0 assert pid1.is_deleted() assert pid2.is_deleted() assert pid3.is_deleted()
def get_pickup_location_options(): """Get all pickup location for all patron accounts.""" for ptrn_pid in [ptrn.pid for ptrn in current_patrons]: for pid in Location.get_pickup_location_pids(ptrn_pid): location = Location.get_record_by_pid(pid) location_name = location.get('pickup_name', location.get('name')) yield (location.pid, location_name)
def test_ilsrecord_pid_after_validationerror(client, loc_online_martigny_data, librarian_martigny_no_email): """Check PID before and after a ValidationError: it should be the same""" loc = Location.create(loc_online_martigny_data, delete_pid=True) next_pid = str(int(loc.pid) + 1) # post invalid data and post them login_user_via_session(client, librarian_martigny_no_email.user) res, _ = postdata( client, 'invenio_records_rest.loc_list', { '$schema': 'https://ils.rero.ch/schemas/locations/location-v0.0.1.json', 'library': { '$ref': 'https://ils.rero.ch/api/libraries/lib1' }, 'name': 'Library of Foo' }) # check http status for invalid record assert res.status_code == 400 # the pid should be unchanged loc.provider.identifier.query.first().recid == loc.pid # check that we can create a new location loc2 = Location.create(loc_online_martigny_data, delete_pid=True) loc2.pid == next_pid
def test_location_get_all_pickup_locations( patron_martigny, loc_public_martigny, loc_public_sion): """Test pickup locations retrieval.""" locations = Location.get_pickup_location_pids() assert set(locations) == {loc_public_martigny.pid, loc_public_sion.pid} locations = Location.get_pickup_location_pids(patron_martigny.pid) assert set(locations) == {loc_public_martigny.pid}
def test_location_es_mapping(es_clear, db, loc_public_martigny_data, lib_martigny, org_martigny): """Test library elasticsearch mapping.""" search = LocationsSearch() mapping = get_mapping(search.Meta.index) assert mapping Location.create( loc_public_martigny_data, dbcommit=True, reindex=True, delete_pid=True) new_mapping = get_mapping(search.Meta.index) assert mapping == new_mapping
def test_location_es_mapping(es, db, library, location_data): """.""" search = LocationsSearch() mapping = get_mapping(search.Meta.index) assert mapping Location.create(location_data, dbcommit=True, reindex=True, delete_pid=True) assert mapping == get_mapping(search.Meta.index)
def test_location_pickup_locations(locations, patron_martigny_no_email, patron_sion_no_email, loc_public_martigny, item2_lib_martigny): """Test for pickup locations.""" # At the beginning, if we load all locations from fixtures, there are 4 # pickup locations (loc1, loc3, loc5, loc7) pickup_locations = Location.get_pickup_location_pids() assert set(pickup_locations) == set(['loc1', 'loc3', 'loc5', 'loc7']) # check pickup restrictions by patron_pid pickup_locations = Location.get_pickup_location_pids( patron_pid=patron_martigny_no_email.pid) assert set(pickup_locations) == set(['loc1', 'loc3', 'loc5']) pickup_locations = Location.get_pickup_location_pids( patron_pid=patron_sion_no_email.pid) assert set(pickup_locations) == set(['loc7']) # check pickup restrictions by item_barcode # * update `loc1` to restrict_pickup_to 'loc3' and 'loc6' # --> 'loc6' isn't a pickup location... it's just for test loc_public_martigny['restrict_pickup_to'] = [ {'$ref': 'https://ils.rero.ch/api/locations/loc3'}, {'$ref': 'https://ils.rero.ch/api/locations/loc6'}, ] loc_public_martigny.update( loc_public_martigny, dbcommit=True, reindex=True ) flush_index(LocationsSearch.Meta.index) pickup_locations = Location.get_pickup_location_pids( item_pid=item2_lib_martigny.pid) assert set(pickup_locations) == set(['loc3']) pickup_locations = Location.get_pickup_location_pids( patron_pid=patron_sion_no_email.pid, item_pid=item2_lib_martigny.pid) assert set(pickup_locations) == set([]) # check document.views::item_library_pickup_locations # As we limit pickup to two specific location, this tests will also # return only these two records instead of all pickups for the # organisation picks = item_library_pickup_locations(item2_lib_martigny) assert len(picks) == 2 # reset the location to default value before leaving del loc_public_martigny['restrict_pickup_to'] loc_public_martigny.update( loc_public_martigny, dbcommit=True, reindex=True ) flush_index(LocationsSearch.Meta.index)
def get_notification_context(cls, notifications=None): """Get the context to render the notification template.""" context = {} notifications = notifications or [] if not notifications: return context context['loans'] = [] doc_dumper = DocumentGenericDumper() item_dumper = ItemNotificationDumper() patron_dumper = PatronNotificationDumper() for notification in notifications: loan = notification.loan creation_date = format_date_filter( notification.get('creation_date'), date_format='medium', locale=language_iso639_2to1( notification.get_language_to_use())) request_expire_date = format_date_filter( loan.get('request_expire_date'), date_format='medium', locale=language_iso639_2to1( notification.get_language_to_use())) # merge doc and item metadata preserving document key item_data = notification.item.dumps(dumper=item_dumper) doc_data = notification.document.dumps(dumper=doc_dumper) doc_data = {**item_data, **doc_data} # pickup location name --> !! pickup is on notif.request_loan, not # on notif.loan request_loan = notification.request_loan pickup_location = Location.get_record_by_pid( request_loan.get('pickup_location_pid')) if not pickup_location: pickup_location = Location.get_record_by_pid( request_loan.get('transaction_location_pid')) # request_patron request_patron = Patron.get_record_by_pid( request_loan.get('patron_pid')) loan_context = { 'creation_date': creation_date, 'document': doc_data, 'pickup_name': pickup_location.get('pickup_name', pickup_location.get('name')), 'request_expire_date': request_expire_date, 'patron': request_patron.dumps(dumper=patron_dumper) } context['loans'].append(loan_context) return context
def get_locations(): """Get one pickup_location for each organisation. :return: A dict of locations pids by organisation """ location_data = {} for pid in Location.get_pickup_location_pids(): record = Location.get_record_by_pid(pid) if record.organisation_pid not in location_data: location_data[record.organisation_pid] = pid return location_data
def test_location_create(db, es_clear, loc_public_martigny_data): """Test location creation.""" loc = Location.create(loc_public_martigny_data, delete_pid=True) assert loc == loc_public_martigny_data assert loc.get('pid') == '1' loc = Location.get_record_by_pid('1') assert loc == loc_public_martigny_data fetched_pid = fetcher(loc.id, loc) assert fetched_pid.pid_value == '1' assert fetched_pid.pid_type == 'loc'
def create_items(verbose, count, itemscount, missing, reindex): """Create circulation items.""" uids = DocumentsWithItems.get_all_ids() if count == -1: count = len(uids) click.secho('Starting generating {0} items, random {1} ...'.format( count, itemscount), fg='green') locations_pids = Location.get_all_pids() patrons_barcodes = get_patrons_barcodes() missing *= len(patrons_barcodes) members_pids = Member.get_all_pids() with click.progressbar(reversed(uids[:count]), length=count) as bar: for id in bar: document = DocumentsWithItems.get_record_by_id(id) if document.get('type') == 'ebook': continue for i in range(0, randint(1, itemscount)): missing, item = create_random_item( locations_pids=locations_pids, patrons_barcodes=patrons_barcodes, members_pids=members_pids, missing=missing, verbose=False) document.add_item(item, dbcommit=True) document.dbcommit(reindex=reindex) RecordIndexer().client.indices.flush()
def post_process_serialize_search(self, results, pid_fetcher): """Post process the search results. :param results: Elasticsearch search result. :param pid_fetcher: Persistent identifier fetcher. """ records = results.get('hits', {}).get('hits', {}) for record in records: metadata = record.get('metadata', {}) document = search_document_by_pid( metadata.get('document').get('pid')) metadata['ui_title_text'] = title_format_text_head( document['title'], with_subtitle=True) # Add library name for lib_term in results.get('aggregations', {}).get('library', {}).get('buckets', []): lib = Library.get_record_by_pid(lib_term.get('key')) lib_term['name'] = lib.get('name') # Add location name for loc_term in results.get('aggregations', {}).get('location', {}).get('buckets', []): loc = Location.get_record_by_pid(loc_term.get('key')) loc_term['name'] = loc.get('name') # Add library name for item_type_term in results.get('aggregations', {}).get('item_type', {}).get('buckets', []): item_type = ItemType.get_record_by_pid(item_type_term.get('key')) item_type_term['name'] = item_type.get('name') return super(ItemsJSONSerializer, self).post_process_serialize_search(results, pid_fetcher)
def store_location(location, store_location_data): """.""" loc = Location.create(data=store_location_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def location(app, library, location_data): """.""" loc = Location.create(data=location_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def loc_restricted_martigny(app, lib_martigny, loc_restricted_martigny_data): """Create restricted space location for Martigny ville.""" loc = Location.create(data=loc_restricted_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def loc_online_aproz(app, lib_aproz, loc_online_aproz_data): """Create online space location for aproz.""" loc = Location.create(data=loc_online_aproz_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def test_location_cannot_delete(item_lib_martigny): """Test cannot delete.""" location_pid = item_lib_martigny.location_pid location = Location.get_record_by_pid(location_pid) can, reasons = location.can_delete assert not can assert reasons['links']['holdings'] == 1 assert reasons['links']['items'] == 1
def loc_restricted_sion(app, lib_sion, loc_restricted_sion_data): """Create restricted space location for sion.""" loc = Location.create(data=loc_restricted_sion_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def loc_public_fully(app, lib_fully, loc_public_fully_data): """Create public space location for fully.""" loc = Location.create(data=loc_public_fully_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def test_location_create(db, es_clear, loc_public_martigny_data, lib_martigny, loc_online_martigny): """Test location creation.""" loc_public_martigny_data['is_online'] = True with pytest.raises(RecordValidationError): loc = Location.create(loc_public_martigny_data, delete_pid=True) del loc_public_martigny_data['is_online'] loc = Location.create(loc_public_martigny_data, delete_pid=True) assert loc == loc_public_martigny_data assert loc.get('pid') == '2' loc = Location.get_record_by_pid('2') assert loc == loc_public_martigny_data fetched_pid = fetcher(loc.id, loc) assert fetched_pid.pid_value == '2' assert fetched_pid.pid_type == 'loc'
def loc_public_martigny_bourg(app, lib_martigny_bourg, loc_public_martigny_bourg_data): """Create public space location for Martigny bourg.""" loc = Location.create(data=loc_public_martigny_bourg_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(LocationsSearch.Meta.index) return loc
def test_location_create(db, es, loc_public_martigny_data, lib_martigny, loc_online_martigny): """Test location creation.""" loc_public_martigny_data['is_online'] = True with pytest.raises(ValidationError): Location.create(loc_public_martigny_data, delete_pid=True) db.session.rollback() next_pid = Location.provider.identifier.next() del loc_public_martigny_data['is_online'] loc = Location.create(loc_public_martigny_data, delete_pid=True) next_pid += 1 assert loc == loc_public_martigny_data assert loc.get('pid') == str(next_pid) loc = Location.get_record_by_pid(loc.pid) assert loc == loc_public_martigny_data fetched_pid = fetcher(loc.id, loc) assert fetched_pid.pid_value == str(next_pid) assert fetched_pid.pid_type == 'loc'
def test_delete_location(reindex, db, minimal_member_record, minimal_location_record): """Test MembersLocations delete.""" memb = MemberWithLocations.create(minimal_member_record, dbcommit=True) location = Location.create(minimal_location_record, dbcommit=True) memb.add_location(location, dbcommit=True) pid = PersistentIdentifier.get_by_object('loc', 'rec', location.id) assert pid.is_registered() memb.remove_location(location) assert pid.is_deleted() assert memb.locations == [] location1 = Location.create(minimal_location_record, dbcommit=True) memb.add_location(location1, dbcommit=True) location2 = Location.create(minimal_location_record, dbcommit=True) memb.add_location(location2, dbcommit=True) location3 = Location.create(minimal_location_record, dbcommit=True) memb.add_location(location3, dbcommit=True) memb.remove_location(location2) assert len(memb.locations) == 2 assert memb.locations[0]['pid'] == '2' assert memb.locations[1]['pid'] == '4'
def create_minimal_resources(db, minimal_member_record, minimal_location_record, minimal_item_record, minimal_document_record): """Simple patron record.""" member = MemberWithLocations.create(minimal_member_record, dbcommit=True) location = Location.create(minimal_location_record, dbcommit=True) member.add_location(location) doc = DocumentsWithItems.create(minimal_document_record, dbcommit=True) item = Item.create({}) item.update(minimal_item_record, dbcommit=True) doc.add_item(item, dbcommit=True) db.session.commit() yield doc, item, member, location
def _process_loan_pending_at_desk_in_transit_for_pickup( self, metadata, item_pid): """Process for PENDING, ITEM_AT_DESK, ITEM_IN_TRANSIT_FOR_PICKUP.""" pickup_loc = Location.get_record_by_pid( metadata['pickup_location_pid']) metadata['pickup_name'] = \ pickup_loc.get('pickup_name', pickup_loc.get('name')) if metadata['state'] == LoanState.ITEM_AT_DESK: metadata['rank'] = 0 if metadata['state'] in [ LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP ]: patron = Patron.get_record_by_pid(metadata['patron_pid']) item = Item.get_record_by_pid(item_pid) metadata['rank'] = item.patron_request_rank(patron)
def test_create(db, minimal_document_record, minimal_item_record, minimal_member_record, minimal_location_record): """Test DocumentWithItems creation.""" memb = MemberWithLocations.create(minimal_member_record, dbcommit=True) loc = Location.create(minimal_location_record, dbcommit=True) memb.add_location(loc, dbcommit=True) minimal_item_record['location_pid'] = loc.pid item = Item.create(minimal_item_record, dbcommit=True) doc = DocumentsWithItems.create(minimal_document_record, dbcommit=True) assert doc.itemslist == [] doc.add_item(item, dbcommit=True) assert doc.itemslist[0] == item dump = doc.dumps() assert dump['itemslist'][0] == item.dumps()
def _build_notification_email_context(loan, item, location): """Build the context used by the send_notification_to_location function. :param loan : the loan for which build context :param item : the item for which build context :param location : the item location """ document_pid = Item.get_document_pid_by_item_pid(loan.item_pid) document = Document.get_record_by_pid(document_pid) pickup_location = Location.get_record_by_pid( loan.get('pickup_location_pid')) patron = Patron.get_record_by_pid(loan.patron_pid) # inherit holdings call number when possible issue_call_number = item.issue_inherited_first_call_number if issue_call_number: item['call_number'] = issue_call_number ctx = { 'loan': loan.replace_refs().dumps(), 'item': item.replace_refs().dumps(), 'document': document.replace_refs().dumps(), 'pickup_location': pickup_location, 'item_location': location.dumps(), 'patron': patron } library = pickup_location.get_library() ctx['pickup_location']['library'] = library ctx['item']['item_type'] = \ ItemType.get_record_by_pid(item.item_type_circulation_category_pid) titles = [ title for title in ctx['document'].get('title', []) if title['type'] == 'bf:Title' ] ctx['document']['title_text'] = \ next(iter(titles or []), {}).get('_text') responsibility_statement = create_title_responsibilites( document.get('responsibilityStatement', [])) ctx['document']['responsibility_statement'] = \ next(iter(responsibility_statement or []), '') trans_date = ciso8601.parse_datetime(loan.get('transaction_date')) trans_date = trans_date\ .replace(tzinfo=timezone.utc)\ .astimezone(tz=library.get_timezone()) ctx['loan']['transaction_date'] = \ trans_date.strftime("%d.%m.%Y - %H:%M:%S") return ctx
def test_member_name(db, minimal_member_record, minimal_item_record, minimal_location_record): """Test member names.""" member = MemberWithLocations.create(minimal_member_record, dbcommit=True) assert member location = Location.create(minimal_location_record, dbcommit=True) assert location member.add_location(location, dbcommit=True) assert member.locations item = Item.create({}) item.update(minimal_item_record, dbcommit=True) assert item data = item.dumps() assert data.get('member_pid') == '1' assert data.get('member_name') == 'MV Sion' holding = data.get('_circulation').get('holdings')[1] assert holding['pickup_member_name'] == 'MV Sion'
def calculate_notification_amount(notification): """Return amount due for a notification. :param notification: the notification for which to compute the amount. At this time, this is not yet a `Notification`, only a dict of structured data. :return the amount due for this notification. 0 if no amount could be compute. """ # Find the reminder type to use based on the notification that we would # sent. If no reminder type is found, then no amount could be calculated # and we can't return '0' notif_type = notification.get('notification_type') reminder_type_mapping = { NotificationType.DUE_SOON: DUE_SOON_REMINDER_TYPE, NotificationType.OVERDUE: OVERDUE_REMINDER_TYPE } reminder_type = reminder_type_mapping.get(notif_type) if not notif_type or not reminder_type: return 0 # to find the notification due amount, we firstly need to get the # circulation policy linked to the parent loan. location_pid = notification.transaction_location_pid location = Location.get_record_by_pid(location_pid) cipo = CircPolicy.provide_circ_policy( location.organisation_pid, location.library_pid, notification.patron.patron_type_pid, notification.item.holding_circulation_category_pid) # now we get the circulation policy, search the correct reminder depending # of the reminder_counter from the notification context. reminder = cipo.get_reminder(reminder_type=reminder_type, idx=notification.get('context', {}).get( 'reminder_counter', 0)) return reminder.get('fee_amount', 0) if reminder else 0
def test_item_can_request(client, document, holding_lib_martigny, item_lib_martigny, librarian_martigny, lib_martigny, patron_martigny, circulation_policies, patron_type_children_martigny, loc_public_martigny_data, system_librarian_martigny, item_lib_martigny_data): """Test item can request API.""" # test no logged user res = client.get( url_for('api_item.can_request', item_pid=item_lib_martigny.pid, library_pid=lib_martigny.pid, patron_barcode=patron_martigny.get('patron', {}).get('barcode')[0])) assert res.status_code == 401 can, _ = can_request(item_lib_martigny) assert not can login_user_via_session(client, librarian_martigny.user) # valid test res = client.get( url_for('api_item.can_request', item_pid=item_lib_martigny.pid, library_pid=lib_martigny.pid, patron_barcode=patron_martigny.get('patron', {}).get('barcode')[0])) assert res.status_code == 200 data = get_json(res) assert data.get('can') # test no valid item res = client.get( url_for('api_item.can_request', item_pid='no_item', library_pid=lib_martigny.pid, patron_barcode=patron_martigny.get('patron', {}).get('barcode')[0])) assert res.status_code == 404 # test no valid library res = client.get( url_for('api_item.can_request', item_pid=item_lib_martigny.pid, library_pid='no_library', patron_barcode=patron_martigny.get('patron', {}).get('barcode')[0])) assert res.status_code == 404 # test no valid patron res = client.get( url_for('api_item.can_request', item_pid=item_lib_martigny.pid, library_pid=lib_martigny.pid, patron_barcode='no_barcode')) assert res.status_code == 404 # test no valid item status item_lib_martigny['status'] = ItemStatus.MISSING item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True) res = client.get( url_for('api_item.can_request', item_pid=item_lib_martigny.pid, library_pid=lib_martigny.pid, patron_barcode=patron_martigny.get('patron', {}).get('barcode')[0])) assert res.status_code == 200 data = get_json(res) assert not data.get('can') item_lib_martigny['status'] = ItemStatus.ON_SHELF item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True) # Location :: allow_request == false # create a new location and set 'allow_request' to false. Assign a new # item to this location. Chek if this item can be requested : it couldn't # with 'Item location doesn't allow request' reason. new_location = deepcopy(loc_public_martigny_data) del new_location['pid'] new_location['allow_request'] = False new_location = Location.create(new_location, dbcommit=True, reindex=True) assert new_location new_item = deepcopy(item_lib_martigny_data) del new_item['pid'] new_item['barcode'] = 'dummy_barcode_allow_request' new_item['location']['$ref'] = get_ref_for_pid(Location, new_location.pid) new_item = Item.create(new_item, dbcommit=True, reindex=True) assert new_item res = client.get(url_for('api_item.can_request', item_pid=new_item.pid)) assert res.status_code == 200 data = get_json(res) assert not data.get('can') # remove created data item_url = url_for('invenio_records_rest.item_item', pid_value=new_item.pid) hold_url = url_for('invenio_records_rest.hold_item', pid_value=new_item.holding_pid) loc_url = url_for('invenio_records_rest.loc_item', pid_value=new_location.pid) client.delete(item_url) client.delete(hold_url) client.delete(loc_url)