def create_ill_requests(input_file): """Create ILL request for each organisation.""" locations = get_locations() patron_pids = {} with open(input_file, 'r', encoding='utf-8') as request_file: requests = json.load(request_file) for request_data in requests: for organisation_pid, location_pid in locations.items(): if 'pid' in request_data: del request_data['pid'] if organisation_pid not in patron_pids: patron_pids[organisation_pid] = [ pid for pid in Patron.get_all_pids_for_organisation( organisation_pid) if Patron.get_record_by_pid(pid).is_patron ] patron_pid = random.choice(patron_pids[organisation_pid]) request_data['patron'] = { '$ref': get_ref_for_pid('patrons', patron_pid) } request_data['pickup_location'] = { '$ref': get_ref_for_pid('locations', location_pid) } request = ILLRequest.create(request_data, dbcommit=True, reindex=True) click.echo('\tRequest: #{pid} \tfor org#{org_id}'.format( pid=request.pid, org_id=request.organisation_pid))
def test_patron(reindex, get_borrowed_documents_pids, get_uuid_pid_by_email, record_indexer, url_for1, url_for2, send_email, confirm_user, app, db, minimal_patron_record, minimal_document_record, minimal_item_record): """Test patron""" # Convenient references security = LocalProxy(lambda: app.extensions['security']) datastore = LocalProxy(lambda: security.datastore) # hack the return value get_uuid_pid_by_email.return_value = None, None next, pid = save_patron(minimal_patron_record, Patron.provider.pid_type, Patron.fetcher, Patron.minter, record_indexer, Patron, None) email = minimal_patron_record.get('email') # Verify that user exists in app's datastore user = datastore.get_user(email) assert user # hack the return value get_uuid_pid_by_email.return_value = pid.object_uuid, pid.id patron = Patron.get_patron_by_email(email) assert patron.get('email') == email patron = Patron.get_patron_by_user(user) assert patron.get('email') == email doc = DocumentsWithItems.create(minimal_document_record, dbcommit=True) # hack the return value get_borrowed_documents_pids.return_value = [doc.pid] docs = patron.get_borrowed_documents() assert docs[0] == doc
def test_patrons_post_without_email(app, client, lib_martigny, patron_type_children_martigny, patron_martigny_data_tmp, json_header, roles, mailbox): """Test record retrieval.""" patron_data = deepcopy(patron_martigny_data_tmp) patron_data['email'] = '*****@*****.**' patron_data['username'] = '******' del patron_data['pid'] del patron_data['email'] patron_data['patron']['communication_channel'] = 'mail' patron_data = create_user_from_data(patron_data) pids = Patron.count() assert len(mailbox) == 0 # Create record / POST res, _ = postdata(client, 'invenio_records_rest.ptrn_list', patron_data) assert res.status_code == 201 assert Patron.count() == pids + 1 assert len(mailbox) == 0 # Check that the returned record matches the given data data = get_json(res) data['metadata']['patron']['communication_channel'] = 'mail' ds = app.extensions['invenio-accounts'].datastore ds.delete_user(ds.find_user(id=patron_data['user_id']))
def test_patrons_post_without_email(client, lib_martigny, patron_type_children_martigny, patron_martigny_data_tmp, json_header, roles, mailbox): """Test record retrieval.""" patron_data = patron_martigny_data_tmp pids = Patron.count() assert len(mailbox) == 0 # Create record / POST del patron_data['pid'] del patron_data['email'] patron_data['patron']['communication_channel'] = 'mail' patron_data['patron']['additional_communication_email'] = '*****@*****.**' patron_data['username'] = '******' res, _ = postdata(client, 'invenio_records_rest.ptrn_list', patron_data) assert res.status_code == 201 assert Patron.count() == pids + 1 assert len(mailbox) == 0 # Check that the returned record matches the given data data = get_json(res) # remove dynamic property del data['metadata']['user_id'] del data['metadata']['pid'] assert data['metadata'] == patron_data
def test_get_patron_for_organisation(patron_martigny, patron_sion, org_martigny, org_sion): """Test get patron_pid for organisation.""" pids = Patron.get_all_pids_for_organisation(org_martigny.pid) assert len(list(pids)) > 0 pids = Patron.get_all_pids_for_organisation(org_sion.pid) assert len(list(pids)) > 0
def test_get_reachable_roles(): """Test get roles covered by the given role.""" roles = Patron.get_reachable_roles(Patron.ROLE_SYSTEM_LIBRARIAN) assert len(roles) == 2 assert Patron.ROLE_LIBRARIAN in roles assert Patron.ROLE_SYSTEM_LIBRARIAN in roles roles = Patron.get_reachable_roles('unknown_role') assert not roles
def test_clear_and_renew_subscription(patron_type_grown_sion, patron_sion_no_email): """Test the `task patrons.tasks.clear_and_renew_subscription`.""" patron_sion = patron_sion_no_email # To test correctly all the code we need to disconnect the listener # `create_subscription_patron_transaction` method. Otherwise, the # first part of the task (clean_obsolete_subscriptions) will automatically # create new subscriptions because the last instruction is a # `patron.update()` call. after_record_update.disconnect(create_subscription_patron_transaction) # first step : clear all subscription for the patron and crate an new # obsolete subscription. if 'subscriptions' in patron_sion_no_email.get('patron', {}): del patron_sion_no_email['patron']['subscriptions'] start = datetime.now() - timedelta(days=200) end = start + timedelta(days=100) patron_sion.add_subscription(patron_type_grown_sion, start, end) assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1 assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] == \ end.strftime('%Y-%m-%d') # clean old subscription - Reload the patron and check they are no more # subscriptions clean_obsolete_subscriptions() patron_sion = Patron.get_record_by_pid(patron_sion.pid) assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 0 # check for patron needed subscriptions and create new subscription if # needed. As our patron has no subscription and is still connected to # a patron type requiring subscription, this task should create a # new subscription for this patron check_patron_types_and_add_subscriptions() patron_sion = Patron.get_record_by_pid(patron_sion.pid) assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1 assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] == \ add_years(datetime.now(), 1).strftime('%Y-%m-%d') # run both operation using task_clear_and_renew_subscriptions` and check # the result. The patron should still have one subscription but end_date # must be today. del patron_sion['patron']['subscriptions'] start = datetime.now() - timedelta(days=200) end = start + timedelta(days=100) patron_sion.add_subscription(patron_type_grown_sion, start, end) task_clear_and_renew_subscriptions() patron_sion = Patron.get_record_by_pid(patron_sion.pid) assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1 assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] != \ end.strftime('%Y-%m-%d') # as we disconnect the `create_subscription_patron_transaction` listener # at the beginning, we need to connect it now. after_record_update.connect(create_subscription_patron_transaction)
def get_patrons_barcodes(): """Get all barcodes of patrons.""" ids = Patron.get_all_ids() barcodes = [] for id in ids: patron = Patron.get_record_by_id(id) barcode = patron.get('barcode') if barcode: barcodes.append(barcode) return barcodes
def test_get_patron(patron_martigny_no_email): """Test patron retrieval.""" patron = patron_martigny_no_email assert Patron.get_patron_by_email(patron.get('email')) == patron assert not Patron.get_patron_by_email('not exists') assert Patron.get_patron_by_barcode(patron.get('barcode')) == patron assert not Patron.get_patron_by_barcode('not exists') class user: email = patron.get('email') assert Patron.get_patron_by_user(user) == patron
def test_patron_create(app, roles, librarian_martigny_data_tmp, mailbox): """Test Patron creation.""" ds = app.extensions['invenio-accounts'].datastore email = librarian_martigny_data_tmp.get('email') assert not ds.find_user(email=email) assert len(mailbox) == 0 ptrn = Patron.create( librarian_martigny_data_tmp, dbcommit=True, delete_pid=True ) user = ds.find_user(email=email) assert user user_roles = [r.name for r in user.roles] assert set(user_roles) == set(ptrn.get('roles')) assert len(mailbox) == 1 assert ptrn.get('email') in mailbox[0].recipients assert ptrn == librarian_martigny_data_tmp assert ptrn.get('pid') == '1' ptrn = Patron.get_record_by_pid('1') assert ptrn == librarian_martigny_data_tmp fetched_pid = patron_id_fetcher(ptrn.id, ptrn) assert fetched_pid.pid_value == '1' assert fetched_pid.pid_type == 'ptrn' # set librarian roles = ['librarian'] ptrn.update({'roles': roles}, dbcommit=True) user_roles = [r.name for r in user.roles] assert set(user_roles) == set(roles) roles = Patron.available_roles ptrn.update({'roles': Patron.available_roles}, dbcommit=True) user_roles = [r.name for r in user.roles] assert set(user_roles) == set(Patron.available_roles) # remove patron ptrn.delete() # user still exist in the invenio db user = ds.find_user(email=email) assert user # all roles has been removed assert not user.roles assert len(mailbox) == 1 # patron does not exists anymore ptrn = Patron.get_record_by_pid('1') assert ptrn is None ptrn = Patron.get_record_by_pid('1', with_deleted=True) assert ptrn == {} assert ptrn.persistent_identifier.pid_value == '1'
def test_patron_multiple(patron_sion_multiple, patron2_martigny, lib_martigny): """Test changing roles for multiple patron accounts.""" assert patron2_martigny.user == patron_sion_multiple.user data = dict(patron_sion_multiple) assert set(patron2_martigny.user.roles) == set(['librarian', 'patron']) data['roles'] = ['patron'] del data['libraries'] patron_sion_multiple.update(data, dbcommit=True, reindex=True) assert patron2_martigny.user.roles == ['patron'] assert Patron.get_record_by_pid(patron_sion_multiple.pid).get('roles') == \ ['patron'] assert Patron.get_record_by_pid(patron2_martigny.pid).get('roles') == \ ['patron']
def test_get_all_roles_for_role(): """Test get roles covering by roles hierarchy.""" roles = Patron.get_all_roles_for_role(Patron.ROLE_PATRON) assert len(roles) == 1 assert Patron.ROLE_PATRON in roles roles = Patron.get_all_roles_for_role(Patron.ROLE_SYSTEM_LIBRARIAN) assert len(roles) == 1 assert Patron.ROLE_SYSTEM_LIBRARIAN in roles roles = Patron.get_all_roles_for_role(Patron.ROLE_LIBRARIAN) assert len(roles) == 2 assert Patron.ROLE_LIBRARIAN in roles assert Patron.ROLE_SYSTEM_LIBRARIAN in roles
def test_extend_loan(client, librarian_martigny_no_email, lib_martigny, loc_public_martigny, circulation_policies, item_on_loan_martigny_patron_and_loan_on_loan): """Test frontend extend action.""" login_user_via_session(client, librarian_martigny_no_email.user) item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan assert item.status == ItemStatus.ON_LOAN # Test extend for a blocked patron patron['patron']['blocked'] = True patron['patron']['blocked_note'] = 'Dummy reason' patron.update(patron, dbcommit=True, reindex=True) patron = Patron.get_record_by_pid(patron.pid) res, _ = postdata( client, 'api_item.extend_loan', dict(item_pid=item.pid, transaction_user_pid=librarian_martigny_no_email.pid, transaction_location_pid=loc_public_martigny.pid)) assert res.status_code == 403 del patron['patron']['blocked'] del patron['patron']['blocked_note'] patron.update(patron, dbcommit=True, reindex=True) # With only needed parameters res, _ = postdata( client, 'api_item.extend_loan', dict(item_pid=item.pid, transaction_user_pid=librarian_martigny_no_email.pid, transaction_location_pid=loc_public_martigny.pid)) assert res.status_code == 200
def clean_obsolete_subscriptions(): """Clean obsoletes subscriptions from all patrons. Search for all patron with obsolete subscriptions. For each found patron clean the subscription array keeping only subscription with a end-time grower than now(). Update patron to commit change """ def is_obsolete(subscription, end_date=None): """Check if a subscription is obsolete by checking end date.""" if end_date is None: end_date = datetime.now() sub_end_date = subscription.get('end_date', '1970-01-01') sub_end_date = datetime.strptime(sub_end_date, '%Y-%m-%d') return sub_end_date < end_date for patron in Patron.patrons_with_obsolete_subscription_pids(): subscriptions = patron.get('subscriptions', []) subscriptions = [sub for sub in subscriptions if not is_obsolete(sub)] if not subscriptions and 'subscriptions' in patron: del patron['subscriptions'] else: patron['subscriptions'] = subscriptions # NOTE : this update will trigger the listener # `create_subscription_patron_transaction`. This listener will # create a new subscription if needed patron.update(patron.dumps(), dbcommit=True, reindex=True)
def item_record_to_a_specific_loan_state(item=None, loan_state=None, params=None, copy_item=True): """Put an item into a specific circulation loan state. :param item: the item record :param loan_state: the desired loan state and attached to the given item :param params: the required parameters to perform the circ transactions :param copy_item: an option to perform transaction on a copy of the item :return: the item and its loan """ if copy_item: item = create_new_item_from_existing_item(item=item) # complete missing parameters params.setdefault('transaction_date', datetime.now(timezone.utc).isoformat()) params.setdefault('document_pid', item.document_pid) # a parameter to allow in_transit returns checkin_transaction_location_pid = \ params.pop('checkin_transaction_location_pid', None) patron = Patron.get_record_by_pid(params.get('patron_pid')) # perform circulation actions if loan_state in [ LoanState.PENDING, LoanState.ITEM_AT_DESK, LoanState.ITEM_ON_LOAN, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP, LoanState.ITEM_IN_TRANSIT_TO_HOUSE ]: item, actions = item.request(**params) loan = Loan.get_record_by_pid(actions[LoanAction.REQUEST].get('pid')) assert item.number_of_requests() >= 1 assert item.is_requested_by_patron( patron.get('patron', {}).get('barcode')) if loan_state in [ LoanState.ITEM_AT_DESK, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP, LoanState.ITEM_IN_TRANSIT_TO_HOUSE ]: item, actions = item.validate_request(**params, pid=loan.pid) loan = Loan.get_record_by_pid(actions[LoanAction.VALIDATE].get('pid')) if loan_state in [ LoanState.ITEM_ON_LOAN, LoanState.ITEM_IN_TRANSIT_TO_HOUSE ]: item, actions = item.checkout(**params, pid=loan.pid) loan = Loan.get_record_by_pid(actions[LoanAction.CHECKOUT].get('pid')) if loan_state == LoanState.ITEM_IN_TRANSIT_TO_HOUSE: if checkin_transaction_location_pid: params['transaction_location_pid'] = \ checkin_transaction_location_pid item, actions = item.checkin(**params, pid=loan.pid) loan = Loan.get_record_by_pid(actions[LoanAction.CHECKIN].get('pid')) flush_index(ItemsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan['state'] == loan_state return item, loan
def user_patron_no_email(app, roles, library, patron_type, user_patron_data): """.""" ptrn = Patron.create(data=user_patron_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def librarian_sion_no_email(app, roles, lib_sion, librarian_sion_data): """Create sion librarian without sending reset password instruction.""" ptrn = Patron.create(data=librarian_sion_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def patron_sion_no_email(app, roles, patron_type_grown_sion, patron_sion_data): """Create Sion patron without sending reset password instruction.""" ptrn = Patron.create(data=patron_sion_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def librarian_martigny(app, roles, lib_martigny, librarian_martigny_data): """Create Martigny librarian record.""" ptrn = Patron.create(data=librarian_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def system_librarian_sion(app, roles, lib_sion, system_librarian_sion_data): """Create Sion system librarian record.""" ptrn = Patron.create(data=system_librarian_sion_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def patron2_martigny(app, roles, lib_martigny, patron_type_adults_martigny, patron2_martigny_data): """Create Martigny patron record.""" ptrn = Patron.create(data=patron2_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def patron2_martigny_no_email(app, roles, patron_type_adults_martigny, patron2_martigny_data): """Create Martigny patron without sending reset password instruction.""" ptrn = Patron.create(data=patron2_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def system_librarian_martigny_no_email(app, roles, lib_martigny, system_librarian_martigny_data): """Create Martigny system librarian without sending reset password.""" ptrn = Patron.create(data=system_librarian_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def patron_sion(app, roles, lib_sion, patron_type_grown_sion, patron_sion_data): """Create Sion patron record.""" ptrn = Patron.create(data=patron_sion_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def user_patron_specific(app, roles, library, user_librarian_type, user_patron_data_specific): """.""" ptrn = Patron.create( data=user_patron_data_specific, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
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 sip2_patron_martigny_no_email(app, roles, patron_type_children_martigny, sip2_patron_martigny_data): """Create Martigny patron without sending reset password instruction.""" # create patron account patron = Patron.create(data=sip2_patron_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return patron
def init_menu_profile(): """Create the profile header menu.""" item = current_menu.submenu('main.profile') account = _('My Account') user_initials = session.get('user_initials') if session.get('user_id'): if user_initials: account = user_initials else: patron = Patron.get_patron_by_email(current_user.email) if patron: account = patron.initial session['user_initials'] = account else: if user_initials: del session['user_initials'] item.register( endpoint=None, text='{icon} <span class="{visible}">{account}</span>'.format( icon='<i class="fa fa-user"></i>', visible='visible-md-inline visible-lg-inline', account=account), order=1) item = current_menu.submenu('main.profile.login') item.register( endpoint='security.login', endpoint_arguments_constructor=lambda: dict(next=request.full_path), visible_when=lambda: not current_user.is_authenticated, text='{icon} {login}'.format(icon='<i class="fa fa-sign-in"></i>', login=_('Login')), order=1) item = current_menu.submenu('main.profile.logout') item.register( endpoint='security.logout', endpoint_arguments_constructor=lambda: dict(next='/{viewcode}'.format( viewcode=request.view_args.get( 'viewcode', current_app.config.get('RERO_ILS_SEARCH_GLOBAL_VIEW_CODE')))), visible_when=lambda: current_user.is_authenticated, text='{icon} {logout}'.format(icon='<i class="fa fa-sign-out"></i>', logout=_('Logout')), order=1) item = current_menu.submenu('main.profile.signup') item.register(endpoint='security.register', visible_when=lambda: not current_user.is_authenticated, text='{icon} {signup}'.format( icon='<i class="fa fa-user-plus"></i>', signup=_('Sign Up')), order=2)
def patron_martigny( app, roles, lib_martigny, patron_type_children_martigny, patron_martigny_data): """Create Martigny patron record.""" ptrn = Patron.get_record_by_pid(patron_martigny_data.get('pid')) if ptrn: ptrn = ptrn.update( data=patron_martigny_data, dbcommit=True, reindex=True ) else: ptrn = Patron.create( data=patron_martigny_data, delete_pid=False, dbcommit=True, reindex=True) flush_index(PatronsSearch.Meta.index) return ptrn
def test_patrons_dirty_barcode(client, patron_martigny, librarian_martigny): """Test patron update with dirty barcode.""" barcode = patron_martigny.get('patron', {}).get('barcode')[0] patron_martigny['patron']['barcode'] = [ ' {barcode} '.format(barcode=barcode) ] patron_martigny.update(patron_martigny, dbcommit=True, reindex=True) patron = Patron.get_record_by_pid(patron_martigny.pid) assert patron.patron.get('barcode') == [barcode] # Ensure that users with no patron role will not have a barcode librarian_martigny.update(librarian_martigny, dbcommit=True, reindex=True) assert not librarian_martigny.get('patron', {}).get('barcode')