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 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 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_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 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 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 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')
def upgrade(): """Fix incorrectly set patron communication channels.""" query = PatronsSearch()\ .filter('term', patron__communication_channel=CommunicationChannel.EMAIL)\ .filter('bool', must_not=[ Q('exists', field='patron.additional_communication_email')])\ .filter('bool', must_not=[Q('exists', field='email')])\ .source(includes='pid') pids = [(hit['pid'], hit.meta.id) for hit in query.scan()] errors = 0 ids = [] for idx, (pid, id) in enumerate(pids, 1): if patron := Patron.get_record_by_pid(pid): ids.append(id) try: patron['patron']['communication_channel'] = \ CommunicationChannel.MAIL db.session.query(patron.model_cls).filter_by( id=patron.id).update({patron.model_cls.json: patron}) except Exception as err: LOGGER.error(f'{idx} * Update patron: {pid} {err}') errors += 1
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_patrons_dirty_barcode(client, patron_martigny_no_email, librarian_martigny_no_email): """Test patron update with dirty barcode.""" barcode = patron_martigny_no_email.get('barcode') patron_martigny_no_email['barcode'] = ' {barcode} '.format(barcode=barcode) patron_martigny_no_email.update(patron_martigny_no_email, dbcommit=True, reindex=True) patron = Patron.get_record_by_pid(patron_martigny_no_email.pid) assert patron.get('barcode') == barcode # Ensure that users with no patron role will not have a barcode librarian_martigny_no_email.update(librarian_martigny_no_email, dbcommit=True, reindex=True) assert not librarian_martigny_no_email.get('barcode')
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 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 librarian_martigny( app, roles, lib_martigny, librarian_martigny_data): """Create Martigny librarian record.""" ptrn = Patron.get_record_by_pid(librarian_martigny_data['pid']) if ptrn: ptrn = ptrn.update( data=librarian_martigny_data, dbcommit=True, reindex=True ) else: ptrn = Patron.create( data=librarian_martigny_data, delete_pid=False, dbcommit=True, reindex=True ) flush_index(PatronsSearch.Meta.index) return ptrn
def test_patron_pending_subscription(client, patron_type_grown_sion, patron_sion_no_email, librarian_sion_no_email, patron_transaction_overdue_event_martigny, lib_sion): """Test get pending subscription for patron.""" # At the beginning, `patron_sion_no_email` should have one pending # subscription. pending_subscription = patron_sion_no_email.get_pending_subscriptions() assert len(pending_subscription) == 1 # Pay this subscription. login_user_via_session(client, librarian_sion_no_email.user) post_entrypoint = 'invenio_records_rest.ptre_list' trans_pid = extracted_data_from_ref( pending_subscription[0]['patron_transaction'], data='pid') transaction = PatronTransaction.get_record_by_pid(trans_pid) payment = deepcopy(patron_transaction_overdue_event_martigny) del payment['pid'] payment['type'] = 'payment' payment['subtype'] = 'cash' payment['amount'] = transaction.total_amount payment['operator'] = { '$ref': get_ref_for_pid('patrons', librarian_sion_no_email.pid) } payment['library'] = {'$ref': get_ref_for_pid('libraries', lib_sion.pid)} payment['parent'] = pending_subscription[0]['patron_transaction'] res, _ = postdata(client, post_entrypoint, payment) assert res.status_code == 201 transaction = PatronTransaction.get_record_by_pid(transaction.pid) assert transaction.status == 'closed' # reload the patron and check the pending subscription. As we paid the # previous subscription, there will be none pending subscription patron_sion_no_email = Patron.get_record_by_pid(patron_sion_no_email.pid) pending_subscription = patron_sion_no_email.get_pending_subscriptions() assert len(pending_subscription) == 0
def item(item_barcode): """HTTP GET request for requested loans for a library item and patron.""" item = Item.get_item_by_barcode(item_barcode, current_librarian.organisation_pid) if not item: abort(404) loan = get_loan_for_item(item_pid_to_object(item.pid)) if loan: loan = Loan.get_record_by_pid(loan.get('pid')).dumps_for_circulation() item_dumps = item.dumps_for_circulation() patron_pid = flask_request.args.get('patron_pid') if patron_pid: patron = Patron.get_record_by_pid(patron_pid) organisation_pid = item.organisation_pid library_pid = item.library_pid patron_type_pid = patron.patron_type_pid item_type_pid = item.item_type_circulation_category_pid circ_policy = CircPolicy.provide_circ_policy( organisation_pid=organisation_pid, library_pid=library_pid, patron_type_pid=patron_type_pid, item_type_pid=item_type_pid) new_actions = [] # If circulation policy doesn't allow checkout operation no need to # perform special check describe below. if circ_policy.can_checkout: for action in item_dumps.get('actions', []): if action == 'checkout': if (item.number_of_requests() > 0 and item.patron_request_rank(patron) == 1 or item.number_of_requests() <= 0): new_actions.append(action) elif action == 'receive' and item.number_of_requests() == 0: new_actions.append('checkout') item_dumps['actions'] = new_actions return jsonify({'metadata': {'item': item_dumps, 'loan': loan}})
def test_user_profile_updates(client, patron_martigny, system_librarian_martigny, json_header, mailbox): """Test users profile updates.""" # login with a patron has only the patron role, this means we are logging # into the public interface assert patron_martigny.patron['communication_channel'] == \ CommunicationChannel.MAIL login_user_via_session(client, patron_martigny.user) # mailbox is empty assert not (len(mailbox)) user_metadata = User.get_by_id(patron_martigny.user.id).dumpsMetadata() # changing the email by another does not send any reset_password # notification user_metadata['email'] = '*****@*****.**' res = client.put(url_for('api_users.users_item', id=patron_martigny.user.id), data=json.dumps(user_metadata), headers=json_header) assert res.status_code == 200 assert not (len(mailbox)) patron_martigny = Patron.get_record_by_pid(patron_martigny.pid) # an email was added to patron, communication_channel will change # automatically to email assert patron_martigny.patron.get('communication_channel') == \ CommunicationChannel.EMAIL # removing the email from profile does not send any reset_password # notification user_metadata.pop('email', None) res = client.put(url_for('api_users.users_item', id=patron_martigny.user.id), data=json.dumps(user_metadata), headers=json_header) assert res.status_code == 200 assert not (len(mailbox)) # the corresponding patron changes its communication_channel to mail # autmoatically if user has no email configured and patron has no # additional_communication_email configured patron_martigny = Patron.get_record_by_pid(patron_martigny.pid) assert patron_martigny.patron.get('communication_channel') == \ CommunicationChannel.MAIL # login as a system_librarian this means we are logging into the # professional interface login_user_via_session(client, system_librarian_martigny.user) # adding an email to a profile does not send any reset_password # notification user_metadata['email'] = '*****@*****.**' res = client.put(url_for('api_users.users_item', id=patron_martigny.user.id), data=json.dumps(user_metadata), headers=json_header) assert res.status_code == 200 assert not (len(mailbox)) # removing the email from profile does not send any reset_password # notification user_metadata.pop('email', None) res = client.put(url_for('api_users.users_item', id=patron_martigny.user.id), data=json.dumps(user_metadata), headers=json_header) assert res.status_code == 200 assert not (len(mailbox)) patron_martigny = Patron.get_record_by_pid(patron_martigny.pid) assert patron_martigny.patron.get('communication_channel') == \ CommunicationChannel.MAIL
def test_patron_create(app, roles, librarian_martigny_data_tmp, patron_type_adults_martigny, 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 wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) wrong_librarian_martigny_data_tmp.pop('first_name') with pytest.raises(ValidationError): ptrn = Patron.create( wrong_librarian_martigny_data_tmp, dbcommit=True, delete_pid=True ) wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) wrong_librarian_martigny_data_tmp.pop('library') with pytest.raises(RecordValidationError): ptrn = Patron.create( wrong_librarian_martigny_data_tmp, dbcommit=True, delete_pid=True ) wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) wrong_librarian_martigny_data_tmp['subscriptions'] = [{ 'start_date': '2000-01-01', 'end_date': '2001-01-01', 'patron_type': {'$ref': 'https://ils.rero.ch/api/patron_types/xxx'}, 'patron_transaction': { '$ref': 'https://ils.rero.ch/api/patron_transactions/xxx' }, }] with pytest.raises(RecordValidationError): ptrn = Patron.create( wrong_librarian_martigny_data_tmp, dbcommit=True, delete_pid=True ) ptrn = Patron.create( librarian_martigny_data_tmp, dbcommit=True, delete_pid=False ) 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 re.search(r'localhost/lost-password', mailbox[0].body) assert ptrn.get('email') in mailbox[0].recipients assert ptrn == librarian_martigny_data_tmp assert ptrn.get('pid') == 'ptrn2' ptrn = Patron.get_record_by_pid('ptrn2') assert ptrn == librarian_martigny_data_tmp fetched_pid = patron_id_fetcher(ptrn.id, ptrn) assert fetched_pid.pid_value == 'ptrn2' 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 data = { 'roles': Patron.available_roles, 'patron_type': {'$ref': 'https://ils.rero.ch/api/patron_types/ptty2'} } ptrn.update(data, 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('ptrn2') assert ptrn is None ptrn = Patron.get_record_by_pid('ptrn2', with_deleted=True) assert ptrn == {} assert ptrn.persistent_identifier.pid_value == 'ptrn2'
def transaction_user(self): """Shortcut for transaction user of the notification.""" return Patron.get_record_by_pid(self.transaction_user_pid)
def patron(self): """Shortcut for patron of the notification.""" return Patron.get_record_by_pid(self.patron_pid)
def test_patron_create(app, roles, lib_martigny, librarian_martigny_data_tmp, patron_type_adults_martigny, mailbox): """Test Patron creation.""" ds = app.extensions['invenio-accounts'].datastore email = librarian_martigny_data_tmp.get('email') # sanity checks assert len(mailbox) == 0 assert User.query.count() == 0 assert UserProfile.query.count() == 0 l_martigny_data_tmp = librarian_martigny_data_tmp librarian_martigny_data_tmp = create_user_from_data( librarian_martigny_data_tmp) # wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) # wrong_librarian_martigny_data_tmp.pop('first_name') # with pytest.raises(ValidationError): # ptrn = Patron.create( # wrong_librarian_martigny_data_tmp, # dbcommit=True, # delete_pid=True # ) wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) wrong_librarian_martigny_data_tmp.pop('libraries') with pytest.raises(ValidationError): ptrn = Patron.create(wrong_librarian_martigny_data_tmp, dbcommit=True, delete_pid=True) wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp) wrong_librarian_martigny_data_tmp.setdefault( 'patron', { 'expiration_date': '2023-10-07', 'barcode': ['2050124311'], 'type': { '$ref': 'https://bib.rero.ch/api/patron_types/ptty2' }, 'communication_channel': 'email', 'communication_language': 'ita' }) wrong_librarian_martigny_data_tmp['patron']['subscriptions'] = [{ 'start_date': '2000-01-01', 'end_date': '2001-01-01', 'patron_type': { '$ref': 'https://bib.rero.ch/api/patron_types/xxx' }, 'patron_transaction': { '$ref': 'https://bib.rero.ch/api/patron_transactions/xxx' }, }] with pytest.raises(ValidationError): ptrn = Patron.create(wrong_librarian_martigny_data_tmp, dbcommit=True, delete_pid=True) # no data has been created assert len(mailbox) == 0 # assert User.query.count() == 0 # assert UserProfile.query.count() == 0 ptrn = Patron.create(librarian_martigny_data_tmp, dbcommit=True, delete_pid=False) user = User.query.filter_by(id=ptrn.get('user_id')).first() user_id = ptrn.get('user_id') assert user assert user.active for field in [ 'first_name', 'last_name', 'street', 'postal_code', 'city', 'username', 'home_phone' ]: assert getattr(user.profile, field) == l_martigny_data_tmp.get(field) user.profile.birth_date == datetime.strptime( l_martigny_data_tmp.get('birth_date'), '%Y-%m-%d') user_roles = [r.name for r in user.roles] assert set(user_roles) == set(ptrn.get('roles')) # TODO: make these checks during the librarian POST creation # assert len(mailbox) == 1 # assert re.search(r'localhost/lost-password', mailbox[0].body) # assert re.search( # r'Someone requested that the password' + # ' for your RERO ID account be reset.', mailbox[0].body # ) # assert re.search( # r'Best regards', mailbox[0].body # ) # assert ptrn.get('email') in mailbox[0].recipients librarian_martigny_data_tmp['user_id'] = 1 assert ptrn == librarian_martigny_data_tmp assert ptrn.get('pid') == 'ptrn2' ptrn = Patron.get_record_by_pid('ptrn2') # assert ptrn == librarian_martigny_data_tmp fetched_pid = patron_id_fetcher(ptrn.id, ptrn) assert fetched_pid.pid_value == 'ptrn2' 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 data = { 'roles': Patron.available_roles, 'patron': { 'expiration_date': '2023-10-07', 'barcode': ['2050124311'], 'type': { '$ref': 'https://bib.rero.ch/api/patron_types/ptty2' }, 'communication_channel': 'email', 'communication_language': 'ita' } } ptrn.update(data, dbcommit=True) user_roles = [r.name for r in user.roles] assert set(user_roles) == set(Patron.available_roles) # remove patron ptrn.delete(False, True, True) # 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('ptrn2') assert ptrn is None ptrn = Patron.get_record_by_pid('ptrn2', with_deleted=True) assert ptrn == {} assert ptrn.persistent_identifier.pid_value == 'ptrn2' # remove patron ptrn.delete(True, True, True) # clean up the user ds.delete_user(user)