def _calnet_user_api_feed(person): def _get(key): return person and person[key] # Array of departments is compatible with BOAC user schema. departments = [] dept_code = _get_dept_code(person) if dept_code: departments.append({ 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code) if dept_code in BERKELEY_DEPT_CODE_TO_NAME else dept_code, }) return { 'campusEmail': _get('campus_email'), 'departments': departments, 'email': _get('email'), 'firstName': _get('first_name'), 'isExpiredPerLdap': _get('expired'), 'lastName': _get('last_name'), 'name': _get('name'), 'csid': _get('csid'), 'title': _get('title'), 'uid': _get('uid'), }
def to_api_json(self, current_user_id): topics = [t.to_api_json() for t in self.topics if not t.deleted_at] departments = None if self.advisor_dept_codes: departments = [{'code': c, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(c, c)} for c in self.advisor_dept_codes] api_json = { 'id': self.id, 'advisorId': AuthorizedUser.get_id_per_uid(self.advisor_uid), 'advisorName': self.advisor_name, 'advisorRole': self.advisor_role, 'advisorUid': self.advisor_uid, 'advisorDepartments': departments, 'appointmentType': self.appointment_type, 'createdAt': _isoformat(self.created_at), 'createdBy': self.created_by, 'deptCode': self.dept_code, 'details': self.details, 'read': AppointmentRead.was_read_by(current_user_id, self.id), 'student': { 'sid': self.student_sid, }, 'topics': topics, 'updatedAt': _isoformat(self.updated_at), 'updatedBy': self.updated_by, } if self.appointment_type == 'Scheduled': api_json.update({ 'scheduledTime': _isoformat(self.scheduled_time), 'studentContactInfo': self.student_contact_info, 'studentContactType': self.student_contact_type, }) return { **api_json, **appointment_event_to_json(self.id, self.status), }
def _create_users(): for test_user in _test_users: # This script can be run more than once. Do not create user if s/he exists in BOAC db. uid = test_user['uid'] # Mock CSIDs and names are random unless we need them to correspond to test data elsewhere. csid = test_user['csid'] or datetime.now().strftime('%H%M%S%f') first_name = test_user.get( 'firstName', ''.join(random.choices(string.ascii_uppercase, k=6))) last_name = test_user.get( 'lastName', ''.join(random.choices(string.ascii_uppercase, k=6))) # Put mock CalNet data in our json_cache for all users EXCEPT the test "no_calnet_record" user. if uid != no_calnet_record_for_uid: calnet_feed = { 'uid': uid, 'csid': csid, 'firstName': first_name, 'lastName': last_name, 'name': f'{first_name} {last_name}', } if 'calnetDeptCodes' in test_user: calnet_feed['departments'] = [] for dept_code in test_user['calnetDeptCodes']: calnet_feed['departments'].append({ 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code), }) if 'title' in test_user: calnet_feed['title'] = test_user['title'] insert_in_json_cache(f'calnet_user_for_uid_{uid}', calnet_feed) # Add user to authorized_users table if not already present. user = AuthorizedUser.find_by_uid(uid=uid) if not user: user = AuthorizedUser( uid=uid, created_by='2040', is_admin=test_user['isAdmin'], in_demo_mode=test_user['inDemoMode'], can_access_advising_data=test_user['canAccessAdvisingData'], can_access_canvas_data=test_user['canAccessCanvasData'], degree_progress_permission=test_user.get( 'degreeProgressPermission'), search_history=test_user.get('searchHistory', []), ) if test_user.get('deleted'): user.deleted_at = utc_now() db.session.add(user) AuthorizedUser.delete(delete_this_admin_uid) AuthorizedUser.delete(delete_this_uid) std_commit(allow_test_environment=True)
def appointment_to_compatible_json(appointment, topics=(), attachments=None, event=None): # We have legacy appointments and appointments created via BOA. The following sets a standard for the front-end. advisor_sid = appointment.get('advisor_sid') advisor_uid = appointment.get('advisor_uid') appointment_id = appointment.get('id') appointment_type = appointment.get('appointment_type') cancelled = appointment.get('cancelled') departments = [] dept_codes = appointment.get('advisor_dept_codes') or [] created_by = appointment.get('created_by') or 'YCBM' for dept_code in dept_codes: departments.append({ 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code), }) api_json = { 'id': appointment_id, 'advisor': { 'id': AuthorizedUser.get_id_per_uid(advisor_uid) if advisor_uid else None, 'name': appointment.get('advisor_name') or join_if_present( ' ', [appointment.get('advisor_first_name'), appointment.get('advisor_last_name')], ), 'sid': advisor_sid, 'title': appointment.get('advisor_role'), 'uid': advisor_uid, 'departments': departments, }, 'appointmentTitle': appointment.get('title'), 'appointmentType': appointment_type, 'attachments': attachments, 'createdAt': resolve_sis_created_at(appointment) or appointment.get('starts_at').isoformat(), 'createdBy': created_by, 'deptCode': appointment.get('dept_code'), 'details': appointment.get('details'), 'endsAt': appointment.get('ends_at').isoformat() if created_by == 'YCBM' and appointment.get('ends_at') else None, 'student': { 'sid': appointment.get('student_sid'), }, 'topics': topics, 'updatedAt': resolve_sis_updated_at(appointment), 'updatedBy': appointment.get('updated_by'), 'cancelReason': appointment.get('cancellation_reason') if created_by == 'YCBM' else None, 'status': 'cancelled' if cancelled else None, } if appointment_type and appointment_type == 'Scheduled': api_json.update({ 'scheduledTime': _isoformat(appointment, 'scheduled_time'), 'studentContactInfo': appointment.get('student_contact_info'), 'studentContactType': appointment.get('student_contact_type'), }) if event: api_json.update(event) return api_json
def _get_api_json(cls, user=None): calnet_profile = None departments = [] if user: calnet_profile = calnet.get_calnet_user_for_uid( app, user.uid, force_feed=False, skip_expired_users=True, ) for m in user.department_memberships: dept_code = m.university_dept.dept_code departments.append( { 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code), 'role': get_dept_role(m), 'isAdvisor': m.is_advisor, 'isDirector': m.is_director, 'isScheduler': m.is_scheduler, }) is_active = False if user: if not calnet_profile: is_active = False elif user.is_admin: is_active = True elif len(user.department_memberships): for m in user.department_memberships: is_active = m.is_advisor or m.is_director or m.is_scheduler if is_active: break is_admin = user and user.is_admin drop_in_advisor_status = [] if user and len(user.drop_in_departments): drop_in_advisor_status = [d.to_api_json() for d in user.drop_in_departments] return { **(calnet_profile or {}), **{ 'id': user and user.id, 'departments': departments, 'isActive': is_active, 'isAdmin': is_admin, 'isAnonymous': not is_active, 'isAuthenticated': is_active, 'inDemoMode': user and user.in_demo_mode, 'canAccessCanvasData': user and user.can_access_canvas_data, 'uid': user and user.uid, 'dropInAdvisorStatus': drop_in_advisor_status, }, }
def _put(_dept_code, _user): if _dept_code not in depts: if _dept_code == 'ADMIN': dept_name = 'Admins' elif _dept_code == 'GUEST': dept_name = 'Guest Access' else: dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(_dept_code) depts[_dept_code] = { 'code': _dept_code, 'name': dept_name, 'users': [], } depts[_dept_code]['users'].append(_user)
def load_development_data(): for name, code in BERKELEY_DEPT_NAME_TO_CODE.items(): UniversityDept.create(code, name, False) for test_user in _test_users: # This script can be run more than once. Do not create user if s/he exists in BOAC db. uid = test_user[0] csid = test_user[1] user = AuthorizedUser.find_by_uid(uid=uid) if uid != no_calnet_record_for_uid: # Put mock CalNet data in our json_cache for all users EXCEPT the test "no_calnet_record" user. first_name = ''.join(random.choices(string.ascii_uppercase, k=6)) last_name = ''.join(random.choices(string.ascii_uppercase, k=6)) calnet_feed = { 'uid': uid, # Mock CSIDs are random unless we need them to correspond to test data elsewhere. 'csid': csid or datetime.now().strftime('%H%M%S%f'), 'firstName': first_name, 'lastName': last_name, 'name': f'{first_name} {last_name}', } if len(test_user) > 4: calnet_feed['departments'] = [ { 'code': test_user[4], 'name': BERKELEY_DEPT_CODE_TO_NAME.get(test_user[4]), }, ] insert_in_json_cache(f'calnet_user_for_uid_{uid}', calnet_feed) if not user: user = AuthorizedUser(uid=uid, is_admin=test_user[2], in_demo_mode=test_user[3]) db.session.add(user) for dept_code, dept_membership in _university_depts.items(): university_dept = UniversityDept.find_by_dept_code(dept_code) university_dept.automate_memberships = dept_membership[ 'automate_memberships'] db.session.add(university_dept) for user in dept_membership['users']: authorized_user = AuthorizedUser.find_by_uid(user['uid']) UniversityDeptMember.create_membership( university_dept, authorized_user, user['is_advisor'], user['is_director'], ) std_commit(allow_test_environment=True)
def note_to_compatible_json(note, topics=(), attachments=None, note_read=False): # We have legacy notes and notes created via BOAC. The following sets a standard for the front-end. departments = [] dept_codes = note.get('dept_code') if 'dept_code' in note else note.get( 'author_dept_codes') or [] for dept_code in dept_codes: departments.append({ 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code), }) return { 'id': note.get('id'), 'sid': note.get('sid'), 'author': { 'id': note.get('author_id'), 'uid': note.get('author_uid'), 'sid': note.get('advisor_sid'), 'name': note.get('author_name'), 'role': note.get('author_role'), 'departments': departments, 'email': note.get('advisor_email'), }, 'subject': note.get('subject'), 'body': note.get('body') or note.get('note_body'), 'category': note.get('note_category'), 'subcategory': note.get('note_subcategory'), 'appointmentId': note.get('appointmentId'), 'createdBy': note.get('created_by'), 'createdAt': resolve_sis_created_at(note), 'updatedBy': note.get('updated_by'), 'updatedAt': resolve_sis_updated_at(note), 'read': True if note_read else False, 'topics': topics, 'attachments': attachments, 'eForm': _eform_to_json(note), }
def get_users_report(dept_code): dept_code = dept_code.upper() dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(dept_code) if dept_name: if current_user.is_admin or _current_user_is_director_of(dept_code): users, total_user_count = AuthorizedUser.get_users( deleted=False, dept_code=dept_code) users = authorized_users_api_feed(users, sort_by='lastName') note_count_per_user = get_note_count_per_user(dept_code) for user in users: user['notesCreated'] = note_count_per_user.get(user['uid'], 0) return tolerant_jsonify({ 'users': users, 'totalUserCount': total_user_count, }) else: raise ForbiddenRequestError( f'You are unauthorized to view {dept_name} reports') else: raise ResourceNotFoundError( f'Unrecognized department code {dept_code}')
def get_notes_report_by_dept(dept_code): dept_code = dept_code.upper() dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(dept_code) if dept_name: if current_user.is_admin or _current_user_is_director_of(dept_code): total_note_count = get_note_count() return tolerant_jsonify({ 'asc': get_asc_advising_note_count(), 'ei': get_e_and_i_advising_note_count(), 'sis': get_sis_advising_note_count(), 'boa': { 'total': total_note_count, 'authors': get_note_author_count(), 'withAttachments': get_note_with_attachments_count(), 'withTopics': get_note_with_topics_count(), }, }) else: raise ForbiddenRequestError( f'You are unauthorized to view {dept_name} reports') else: raise ResourceNotFoundError( f'Unrecognized department code {dept_code}')
def _get_api_json(cls, user=None): calnet_profile = None departments = [] if user: calnet_profile = calnet.get_calnet_user_for_uid( app, user.uid, force_feed=False, skip_expired_users=True, ) for m in user.department_memberships: dept_code = m.university_dept.dept_code departments.append( { 'code': dept_code, 'isDropInEnabled': dept_code in app.config['DEPARTMENTS_SUPPORTING_DROP_INS'], 'isSameDayEnabled': dept_code in app.config['DEPARTMENTS_SUPPORTING_SAME_DAY_APPTS'], 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code), 'role': m.role, }) drop_in_advisor_status = [] same_day_advisor_status = [] is_active = False if user: if not calnet_profile: is_active = False elif user.is_admin: is_active = True elif len(user.department_memberships): for m in user.department_memberships: is_active = True if m.role else False if is_active: break drop_in_advisor_status = [ d.to_api_json() for d in user.drop_in_departments if d.dept_code in app.config['DEPARTMENTS_SUPPORTING_DROP_INS'] ] same_day_advisor_status = [ d.to_api_json() for d in user.same_day_departments if d.dept_code in app.config['DEPARTMENTS_SUPPORTING_SAME_DAY_APPTS'] ] is_admin = user and user.is_admin if app.config['FEATURE_FLAG_DEGREE_CHECK']: degree_progress_permission = 'read_write' if is_admin else (user and user.degree_progress_permission) else: degree_progress_permission = None return { **(calnet_profile or {}), **{ 'id': user and user.id, 'canAccessAdvisingData': user and user.can_access_advising_data, 'canAccessCanvasData': user and user.can_access_canvas_data, 'canEditDegreeProgress': degree_progress_permission == 'read_write', 'canReadDegreeProgress': degree_progress_permission in ['read', 'read_write'], 'degreeProgressPermission': degree_progress_permission, 'departments': departments, 'dropInAdvisorStatus': drop_in_advisor_status, 'inDemoMode': user and user.in_demo_mode, 'isActive': is_active, 'isAdmin': is_admin, 'isAnonymous': not is_active, 'isAuthenticated': is_active, 'sameDayAdvisorStatus': same_day_advisor_status, 'uid': user and user.uid, }, }
def _load_users_and_departments(): for code, name in BERKELEY_DEPT_CODE_TO_NAME.items(): UniversityDept.create(code, name) _create_users() _create_department_memberships()
def _to_json(row): dept_code = row.upper() return { 'code': dept_code, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code), }
def _get_filter_options(scope, cohort_owner_uid): all_dept_codes = list(BERKELEY_DEPT_CODE_TO_NAME.keys()) categories = [ [ { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'enteringTerms', 'label': { 'primary': 'Entering Term', }, 'options': _entering_terms, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'expectedGradTerms', 'label': { 'primary': 'Expected Graduation Term', }, 'options': _grad_terms, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'gpaRanges', 'options': None, 'label': { 'primary': 'GPA (Cumulative)', 'range': ['', '-'], 'rangeMinEqualsMax': '', }, 'type': { 'db': 'json[]', 'ux': 'range', }, 'validation': 'gpa', }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'lastTermGpaRanges', 'options': None, 'label': { 'primary': 'GPA (Last Term)', 'range': ['', '-'], 'rangeMinEqualsMax': '', }, 'type': { 'db': 'json[]', 'ux': 'range', }, 'validation': 'gpa', }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'levels', 'label': { 'primary': 'Level', }, 'options': [ { 'name': 'Freshman (0-29 Units)', 'value': 'Freshman' }, { 'name': 'Sophomore (30-59 Units)', 'value': 'Sophomore' }, { 'name': 'Junior (60-89 Units)', 'value': 'Junior' }, { 'name': 'Senior (90+ Units)', 'value': 'Senior' }, ], 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'majors', 'label': { 'primary': 'Major', }, 'options': _majors, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'midpointDeficient', 'label': { 'primary': 'Midpoint Deficient Grade', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'transfer', 'label': { 'primary': 'Transfer Student', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'unitRanges', 'label': { 'primary': 'Units Completed', }, 'options': [ { 'name': '0 - 29', 'value': 'numrange(NULL, 30, \'[)\')' }, { 'name': '30 - 59', 'value': 'numrange(30, 60, \'[)\')' }, { 'name': '60 - 89', 'value': 'numrange(60, 90, \'[)\')' }, { 'name': '90 - 119', 'value': 'numrange(90, 120, \'[)\')' }, { 'name': '120 +', 'value': 'numrange(120, NULL, \'[)\')' }, ], 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, ], [ { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'ethnicities', 'label': { 'primary': 'Ethnicity', }, 'options': _ethnicities, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'genders', 'label': { 'primary': 'Gender', }, 'options': _genders, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'underrepresented', 'label': { 'primary': 'Underrepresented Minority', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, ], [ { 'availableTo': ['UWASC'], 'defaultValue': False if 'UWASC' in scope else None, 'key': 'isInactiveAsc', 'label': { 'primary': 'Inactive (ASC)', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': ['UWASC'], 'defaultValue': None, 'key': 'inIntensiveCohort', 'label': { 'primary': 'Intensive', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': ['UWASC'], 'defaultValue': None, 'key': 'groupCodes', 'label': { 'primary': 'Team', }, 'options': _team_groups, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, ], [ { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coeAdvisorLdapUids', 'label': { 'primary': 'Advisor (COE)', }, 'options': _get_coe_profiles, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coeEthnicities', 'label': { 'primary': 'Ethnicity (COE)', }, 'options': _coe_ethnicities, 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coeGenders', 'label': { 'primary': 'Gender (COE)', }, 'options': [ { 'name': 'Female', 'value': 'F' }, { 'name': 'Male', 'value': 'M' }, ], 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': ['COENG'], 'defaultValue': False if 'COENG' in scope else None, 'key': 'isInactiveCoe', 'label': { 'primary': 'Inactive (COE)', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'lastNameRanges', 'label': { 'primary': 'Last Name', 'range': ['Initials', 'through'], 'rangeMinEqualsMax': 'Starts with', }, 'type': { 'db': 'json[]', 'ux': 'range', }, 'validation': 'char', }, { 'availableTo': all_dept_codes, 'defaultValue': None, 'key': 'cohortOwnerAcademicPlans', 'label': { 'primary': 'My Students', }, 'options': _academic_plans_for_cohort_owner(cohort_owner_uid), 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coePrepStatuses', 'label': { 'primary': 'PREP', }, 'options': [ { 'name': 'PREP', 'value': 'did_prep' }, { 'name': 'PREP eligible', 'value': 'prep_eligible' }, { 'name': 'T-PREP', 'value': 'did_tprep' }, { 'name': 'T-PREP eligible', 'value': 'tprep_eligible' }, ], 'type': { 'db': 'string[]', 'ux': 'dropdown', }, }, { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coeProbation', 'label': { 'primary': 'Probation', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, { 'availableTo': ['COENG'], 'defaultValue': None, 'key': 'coeUnderrepresented', 'label': { 'primary': 'Underrepresented Minority (COE)', }, 'type': { 'db': 'boolean', 'ux': 'boolean', }, }, ], ] available_categories = [] def is_available(d): available = 'ADMIN' in scope or next( (dept_code for dept_code in d['availableTo'] if dept_code in scope), False) if available and 'options' in d: # If it is available then populate menu options options = d.pop('options') d['options'] = options() if callable(options) else options return available for category in categories: available_categories.append( list(filter(lambda d: is_available(d), category))) # Remove unavailable (ie, empty) categories return list(filter(lambda g: len(g), available_categories))