def _dev_auth_login(uid, password): if app.config['DEVELOPER_AUTH_ENABLED']: logger = app.logger if password != app.config['DEVELOPER_AUTH_PASSWORD']: logger.error('Dev-auth: Wrong password') return tolerant_jsonify({'message': 'Invalid credentials'}, 401) user_id = AuthorizedUser.get_id_per_uid(uid) user = user_id and app.login_manager.user_callback(user_id=user_id, flush_cached=True) if user is None: logger.error( f'Dev-auth: User with UID {uid} is not registered in BOA.') return tolerant_jsonify( { 'message': f'Sorry, user with UID {uid} is not registered to use BOA.' }, 403) if not user.is_active: logger.error( f'Dev-auth: UID {uid} is registered with BOA but not active.') return tolerant_jsonify( { 'message': f'Sorry, user with UID {uid} is not authorized to use BOA.' }, 403) logger.info(f'Dev-auth used to log in as UID {uid}') login_user(user, force=True, remember=True) return tolerant_jsonify(current_user.to_api_json()) else: raise ResourceNotFoundError('Unknown path')
def get_service_announcement(): if current_user.is_authenticated: announcement = _get_service_announcement() return tolerant_jsonify(announcement if current_user.is_admin or announcement['isPublished'] else None) else: return tolerant_jsonify(None)
def create_notes(): benchmark = get_benchmarker('create_notes') params = request.form sids = _get_sids_for_note_creation() benchmark(f'SID count: {len(sids)}') body = params.get('body', None) is_private = to_bool_or_none(params.get('isPrivate', False)) subject = params.get('subject', None) topics = get_note_topics_from_http_post() if not sids or not subject: benchmark('end (BadRequest)') raise BadRequestError( 'Note creation requires \'subject\' and \'sids\'') dept_codes = dept_codes_where_advising(current_user) if current_user.is_admin or not len(dept_codes): benchmark('end (Forbidden)') raise ForbiddenRequestError( 'Sorry, only advisors can create advising notes') if is_private and not current_user.can_access_private_notes: benchmark('end (Forbidden)') raise ForbiddenRequestError( 'Sorry, you are not authorized to manage note privacy.') attachments = get_note_attachments_from_http_post(tolerate_none=True) benchmark(f'Attachment count: {len(attachments)}') body = process_input_from_rich_text_editor(body) template_attachment_ids = get_template_attachment_ids_from_http_post() if len(sids) == 1: note = Note.create( **_get_author_profile(), attachments=attachments, body=body, is_private=is_private, sid=sids[0], subject=subject, template_attachment_ids=template_attachment_ids, topics=topics, ) response = tolerant_jsonify( _boa_note_to_compatible_json(note, note_read=True)) else: response = tolerant_jsonify( Note.create_batch( **_get_author_profile(), attachments=attachments, author_id=current_user.to_api_json()['id'], body=body, is_private=is_private, sids=sids, subject=subject, template_attachment_ids=template_attachment_ids, topics=topics, ), ) benchmark('end') return response
def get_cohort(cohort_id): benchmark = get_benchmarker(f'cohort {cohort_id} get_cohort') benchmark('begin') filter_keys = list(request.args.keys()) order_by = get_param(request.args, 'orderBy', None) if is_unauthorized_search(filter_keys, order_by): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) include_students = to_bool(get_param(request.args, 'includeStudents')) include_students = True if include_students is None else include_students offset = get_param(request.args, 'offset', 0) limit = get_param(request.args, 'limit', 50) benchmark('begin cohort filter query') cohort = CohortFilter.find_by_id( int(cohort_id), order_by=order_by, offset=int(offset), limit=int(limit), include_alerts_for_user_id=current_user.get_id(), include_profiles=True, include_students=include_students, ) if cohort and _can_current_user_view_cohort(cohort): _decorate_cohort(cohort) benchmark('end') return tolerant_jsonify(cohort) else: raise ResourceNotFoundError( f'No cohort found with identifier: {cohort_id}')
def get_degree_template(template_id): template = fetch_degree_template(template_id).to_api_json(include_courses=True) parent_template_id = template['parentTemplateId'] parent_template = DegreeProgressTemplate.find_by_id(parent_template_id) if parent_template_id else None if parent_template: template['parentTemplateUpdatedAt'] = _isoformat(parent_template.updated_at) return tolerant_jsonify(template)
def create_note_template(): params = request.form title = params.get('title', None) subject = params.get('subject', None) body = params.get('body', None) topics = get_note_topics_from_http_post() if not title or not subject: raise BadRequestError( 'Note creation requires \'subject\' and \'title\'') user_dept_codes = dept_codes_where_advising(current_user) if current_user.is_admin or not len(user_dept_codes): raise ForbiddenRequestError( 'Sorry, only advisors can create advising note templates') attachments = get_note_attachments_from_http_post(tolerate_none=True) note_template = NoteTemplate.create( attachments=attachments, body=process_input_from_rich_text_editor(body), creator_id=current_user.get_id(), is_private=to_bool_or_none(params.get('isPrivate', False)), subject=subject, title=title, topics=topics, ) return tolerant_jsonify(note_template.to_api_json())
def _update_drop_in_status(uid, dept_code, active): dept_code = dept_code.upper() if uid == 'me': uid = current_user.get_uid() else: authorized_to_toggle = current_user.is_admin or dept_code in [ d['code'] for d in current_user.departments if d.get('isScheduler') ] if not authorized_to_toggle: raise errors.ForbiddenRequestError( f'Unauthorized to toggle drop-in status for department {dept_code}' ) drop_in_status = None user = AuthorizedUser.find_by_uid(uid) if user: drop_in_status = next( (d for d in user.drop_in_departments if d.dept_code == dept_code), None) if drop_in_status: drop_in_status.update_availability(active) UserSession.flush_cache_for_id(user.id) return tolerant_jsonify(drop_in_status.to_api_json()) else: raise errors.ResourceNotFoundError( f'No drop-in advisor status found: (uid={uid}, dept_code={dept_code})' )
def app_config(): current_term_name = app.config['CANVAS_CURRENT_ENROLLMENT_TERM'] current_term_id = sis_term_id_for_name(current_term_name) return tolerant_jsonify({ 'boacEnv': app.config['BOAC_ENV'], 'currentEnrollmentTerm': current_term_name, 'currentEnrollmentTermId': int(current_term_id), 'disableMatrixViewThreshold': app.config['DISABLE_MATRIX_VIEW_THRESHOLD'], 'devAuthEnabled': app.config['DEVELOPER_AUTH_ENABLED'], 'ebEnvironment': app.config['EB_ENVIRONMENT'] if 'EB_ENVIRONMENT' in app.config else None, 'featureFlagAdvisorAppointments': app.config['FEATURE_FLAG_ADVISOR_APPOINTMENTS'], 'googleAnalyticsId': app.config['GOOGLE_ANALYTICS_ID'], 'isDemoModeAvailable': app.config['DEMO_MODE_AVAILABLE'], 'maxAttachmentsPerNote': app.config['NOTES_ATTACHMENTS_MAX_PER_NOTE'], 'pingFrequency': app.config['PING_FREQUENCY'], 'supportEmailAddress': app.config['BOAC_SUPPORT_EMAIL'], 'timezone': app.config['TIMEZONE'], })
def update_cohort(): params = request.get_json() cohort_id = int(params.get('id')) name = params.get('name') filters = params.get('filters') # Validation if not name and not filters: raise BadRequestError('Invalid request') if not CohortFilter.is_cohort_owned_by(cohort_id, current_user.get_id()): raise ForbiddenRequestError(f'Invalid or unauthorized request') filter_keys = list(map(lambda f: f['key'], filters)) if is_unauthorized_search(filter_keys): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) filter_criteria = _translate_filters_to_cohort_criteria(filters) updated = CohortFilter.update( cohort_id=cohort_id, name=name, filter_criteria=filter_criteria, include_students=False, include_alerts_for_user_id=current_user.get_id(), ) _decorate_cohort(updated) return tolerant_jsonify(updated)
def get_appointment(appointment_id): appointment = Appointment.find_by_id(appointment_id) if not appointment: raise ResourceNotFoundError('Unknown path') api_json = appointment.to_api_json(current_user.get_id()) _put_student_profile_per_appointment([api_json]) return tolerant_jsonify(api_json)
def add_appointment_scheduler_to_dept(dept_code): _verify_membership_and_appointments_enabled(current_user, dept_code) params = request.get_json() or {} scheduler_uid = params.get('uid', None) if not scheduler_uid: raise errors.BadRequestError('Scheduler UID missing') calnet_user = calnet.get_calnet_user_for_uid(app, scheduler_uid, skip_expired_users=True) if not calnet_user or not calnet_user.get('csid'): raise errors.BadRequestError('Invalid scheduler UID') user = AuthorizedUser.create_or_restore( scheduler_uid, created_by=current_user.get_uid(), is_admin=False, is_blocked=False, can_access_canvas_data=False, ) Scheduler.create_or_update_membership( dept_code, user.id, drop_in=True, same_day=True, ) _create_department_memberships(user, [{ 'code': dept_code, 'role': 'scheduler', 'automateMembership': False }]) UserSession.flush_cache_for_id(user.id) return tolerant_jsonify( _get_appointment_scheduler_list(current_user, dept_code))
def update_service_announcement(): params = request.get_json() text = process_input_from_rich_text_editor(params.get('text', '')) if not text and _is_service_announcement_published(): raise BadRequestError('If the service announcement is published then API requires \'text\'') ToolSetting.upsert('SERVICE_ANNOUNCEMENT_TEXT', text) return tolerant_jsonify(_get_service_announcement())
def create_appointment(): params = request.get_json() dept_code = params.get('deptCode', None) sid = params.get('sid', None) advisor_uid = params.get('advisorUid', None) appointment_type = params.get('appointmentType', None) topics = params.get('topics', None) if not dept_code or not sid or not appointment_type or not len(topics): raise BadRequestError( 'Appointment creation: required parameters were not provided') dept_code = dept_code.upper() if dept_code not in BERKELEY_DEPT_CODE_TO_NAME: raise ResourceNotFoundError( f'Unrecognized department code: {dept_code}') if dept_code not in _dept_codes_with_scheduler_privilege(): raise ForbiddenRequestError( f'You are unauthorized to manage {dept_code} appointments.') appointment = Appointment.create( advisor_uid=advisor_uid, appointment_type=appointment_type, created_by=current_user.get_id(), dept_code=dept_code, details=params.get('details', None), student_sid=sid, topics=topics, ) AppointmentRead.find_or_create(current_user.get_id(), appointment.id) api_json = appointment.to_api_json(current_user.get_id()) _put_student_profile_per_appointment([api_json]) return tolerant_jsonify(api_json)
def app_config(): return tolerant_jsonify({ 'academicStandingDescriptions': ACADEMIC_STANDING_DESCRIPTIONS, 'apptDeskRefreshInterval': app.config['APPT_DESK_REFRESH_INTERVAL'], 'boacEnv': app.config['BOAC_ENV'], 'currentEnrollmentTerm': current_term_name(), 'currentEnrollmentTermId': int(current_term_id()), 'degreeCategoryTypeOptions': list( filter( lambda t: 'Placeholder' not in t and 'Campus' not in t, degree_progress_category_type.enums, ), ) + ['Campus Requirements'], 'degreeProgressColorCodes': ACCENT_COLOR_CODES, 'disableMatrixViewThreshold': app.config['DISABLE_MATRIX_VIEW_THRESHOLD'], 'devAuthEnabled': app.config['DEVELOPER_AUTH_ENABLED'], 'ebEnvironment': app.config['EB_ENVIRONMENT'] if 'EB_ENVIRONMENT' in app.config else None, 'featureFlagAdmittedStudents': app.config['FEATURE_FLAG_ADMITTED_STUDENTS'], 'featureFlagDegreeCheck': app.config['FEATURE_FLAG_DEGREE_CHECK'], 'fixedWarningOnAllPages': app.config['FIXED_WARNING_ON_ALL_PAGES'], 'googleAnalyticsId': app.config['GOOGLE_ANALYTICS_ID'], 'isDemoModeAvailable': app.config['DEMO_MODE_AVAILABLE'], 'maxAttachmentsPerNote': app.config['NOTES_ATTACHMENTS_MAX_PER_NOTE'], 'pingFrequency': app.config['PING_FREQUENCY'], 'supportEmailAddress': app.config['BOAC_SUPPORT_EMAIL'], 'timezone': app.config['TIMEZONE'], })
def create_curated_cohort(): params = request.get_json() name = params.get('name', None) if not name: raise BadRequestError('Cohort creation requires \'name\'') curated_cohort = CuratedCohort.create(current_user.id, name) return tolerant_jsonify(curated_cohort.to_api_json())
def students_with_alerts(cohort_id): benchmark = get_benchmarker(f'cohort {cohort_id} students_with_alerts') benchmark('begin') offset = get_param(request.args, 'offset', 0) limit = get_param(request.args, 'limit', 50) cohort = CohortFilter.find_by_id( cohort_id, include_alerts_for_user_id=current_user.get_id(), include_students=False, alert_offset=offset, alert_limit=limit, ) benchmark('fetched cohort') if cohort and _can_current_user_view_cohort(cohort): _decorate_cohort(cohort) students = cohort.get('alerts', []) alert_sids = [s['sid'] for s in students] alert_profiles = get_summary_student_profiles(alert_sids) benchmark('fetched student profiles') alert_profiles_by_sid = {p['sid']: p for p in alert_profiles} for student in students: student.update(alert_profiles_by_sid[student['sid']]) # The enrolled units count is the one piece of term data we want to preserve. if student.get('term'): student['term'] = { 'enrolledUnits': student['term'].get('enrolledUnits') } else: raise ResourceNotFoundError( f'No cohort found with identifier: {cohort_id}') benchmark('end') return tolerant_jsonify(students)
def user_search(): snippet = request.get_json().get('snippet', '').strip() if snippet: search_by_uid = re.match(r'\d+', snippet) if search_by_uid: users = AuthorizedUser.users_with_uid_like(snippet, include_deleted=True) else: users = AuthorizedUser.get_all_active_users(include_deleted=True) users = list( calnet.get_calnet_users_for_uids(app, [u.uid for u in users]).values()) if not search_by_uid: any_ = r'.*' pattern = re.compile(any_ + any_.join(snippet.split()) + any_, re.IGNORECASE) users = list( filter(lambda u: u.get('name') and pattern.match(u['name']), users)) else: users = [] def _label(user): name = user['name'] return f"{name} ({user['uid']})" if name else user['uid'] return tolerant_jsonify([{ 'label': _label(u), 'uid': u['uid'] } for u in users])
def appointment_check_in(appointment_id): appointment = Appointment.find_by_id(appointment_id) if not appointment: raise ResourceNotFoundError('Unknown path') if appointment.dept_code in _dept_codes_with_scheduler_privilege(): params = request.get_json() advisor_uid = params.get('advisorUid', None) if not advisor_uid: raise BadRequestError( 'Appointment check-in requires \'advisor_uid\'') appointment = Appointment.check_in( appointment_id=appointment_id, checked_in_by=current_user.get_id(), advisor_dept_codes=params.get('advisorDeptCodes', None), advisor_name=params.get('advisorName', None), advisor_role=params.get('advisorRole', None), advisor_uid=advisor_uid, ) api_json = appointment.to_api_json(current_user.get_id()) _put_student_profile_per_appointment([api_json]) return tolerant_jsonify(api_json) else: raise ForbiddenRequestError( f'You are unauthorized to manage {appointment.dept_code} appointments.' )
def create_or_update_user_profile(): params = request.get_json() profile = params.get('profile', None) memberships = params.get('memberships', None) delete_action = to_bool_or_none(util.get(params, 'deleteAction')) if not profile or not profile.get('uid') or memberships is None: raise errors.BadRequestError('Required parameters are missing') authorized_user = _update_or_create_authorized_user(memberships, profile, include_deleted=True) _delete_existing_memberships(authorized_user) _create_department_memberships(authorized_user, memberships) if delete_action is True and not authorized_user.deleted_at: AuthorizedUser.delete(authorized_user.uid) elif delete_action is False and authorized_user.deleted_at: AuthorizedUser.un_delete(authorized_user.uid) user_id = authorized_user.id UserSession.flush_cache_for_id(user_id) updated_user = AuthorizedUser.find_by_id(user_id, include_deleted=True) users_json = authorized_users_api_feed([updated_user]) return tolerant_jsonify(users_json and users_json[0])
def search_students(): params = request.get_json() if is_unauthorized_search(params): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) search_phrase = util.get(params, 'searchPhrase', '').strip() if not len(search_phrase): raise BadRequestError('Invalid or empty search input') results = search_for_students( include_profiles=True, search_phrase=search_phrase.replace(',', ' '), is_active_asc=_convert_asc_inactive_arg( util.get(params, 'isInactiveAsc')), order_by=util.get(params, 'orderBy', None), offset=util.get(params, 'offset', 0), limit=util.get(params, 'limit', 50), ) alert_counts = Alert.current_alert_counts_for_viewer(current_user.id) students = results['students'] add_alert_counts(alert_counts, students) return tolerant_jsonify({ 'students': students, 'totalStudentCount': results['totalStudentCount'], })
def _update_drop_in_availability(uid, dept_code, new_availability): dept_code = dept_code.upper() if uid != current_user.get_uid(): authorized_to_toggle = current_user.is_admin or dept_code in [ d['code'] for d in current_user.departments if d.get('role') == 'scheduler' ] if not authorized_to_toggle: raise errors.ForbiddenRequestError( f'Unauthorized to toggle drop-in availability for department {dept_code}' ) drop_in_membership = None user = AuthorizedUser.find_by_uid(uid) if user: drop_in_membership = next( (d for d in user.drop_in_departments if d.dept_code == dept_code), None) if drop_in_membership: if drop_in_membership.is_available is True and new_availability is False: Appointment.unreserve_all_for_advisor(uid, current_user.get_id()) drop_in_membership.update_availability(new_availability) UserSession.flush_cache_for_id(user.id) return tolerant_jsonify(drop_in_membership.to_api_json()) else: raise errors.ResourceNotFoundError( f'No drop-in advisor membership found: (uid={uid}, dept_code={dept_code})' )
def user_status(): return tolerant_jsonify({ 'isActive': current_user.is_active, 'isAnonymous': current_user.is_anonymous, 'isAuthenticated': current_user.is_authenticated, 'uid': current_user.get_id(), })
def my_profile(): uid = current_user.get_id() profile = calnet.get_calnet_user_for_uid(app, uid) if current_user.is_active: authorized_user_id = current_user.id curated_cohorts = CuratedCohort.get_curated_cohorts_by_owner_id(authorized_user_id) curated_cohorts = [c.to_api_json(sids_only=True) for c in curated_cohorts] departments = {} for m in current_user.department_memberships: departments.update({ m.university_dept.dept_code: { 'isAdvisor': m.is_advisor, 'isDirector': m.is_director, }, }) my_cohorts = CohortFilter.all_owned_by(uid) profile.update({ 'myFilteredCohorts': [c.to_api_json(include_students=False) for c in my_cohorts], 'myCuratedCohorts': curated_cohorts, 'isAdmin': current_user.is_admin, 'departments': departments, }) else: profile.update({ 'myFilteredCohorts': None, 'myCuratedCohorts': None, 'isAdmin': False, 'departments': None, }) return tolerant_jsonify(profile)
def get_cohort_per_filters(): benchmark = get_benchmarker('cohort get_students_per_filters') benchmark('begin') params = request.get_json() filters = get_param(params, 'filters', []) if not filters: raise BadRequestError('API requires \'filters\'') include_students = to_bool(get_param(params, 'includeStudents')) include_students = True if include_students is None else include_students order_by = get_param(params, 'orderBy', None) offset = get_param(params, 'offset', 0) limit = get_param(params, 'limit', 50) filter_keys = list(map(lambda f: f['key'], filters)) if is_unauthorized_search(filter_keys, order_by): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) benchmark('begin phantom cohort query') cohort = _construct_phantom_cohort( filters=filters, order_by=order_by, offset=int(offset), limit=int(limit), include_alerts_for_user_id=current_user.get_id(), include_profiles=True, include_students=include_students, ) _decorate_cohort(cohort) benchmark('end') return tolerant_jsonify(cohort)
def get_students_with_alerts(curated_group_id): offset = get_param(request.args, 'offset', 0) limit = get_param(request.args, 'limit', 50) benchmark = get_benchmarker( f'curated group {curated_group_id} students_with_alerts') benchmark('begin') curated_group = CuratedGroup.find_by_id(curated_group_id) if not curated_group: raise ResourceNotFoundError( f'Sorry, no curated group found with id {curated_group_id}.') if not _can_current_user_view_curated_group(curated_group): raise ForbiddenRequestError( f'Current user, {current_user.get_uid()}, cannot view curated group {curated_group.id}' ) students = Alert.include_alert_counts_for_students( benchmark=benchmark, viewer_user_id=current_user.get_id(), group={'sids': CuratedGroup.get_all_sids(curated_group_id)}, count_only=True, offset=offset, limit=limit, ) alert_count_per_sid = {} for s in list(filter(lambda s: s.get('alertCount') > 0, students)): sid = s.get('sid') alert_count_per_sid[sid] = s.get('alertCount') sids = list(alert_count_per_sid.keys()) benchmark('begin profile query') students_with_alerts = get_student_profile_summaries(sids=sids) benchmark('end profile query') for student in students_with_alerts: student['alertCount'] = alert_count_per_sid[student['sid']] benchmark('end') return tolerant_jsonify(students_with_alerts)
def all_user_profiles(): # This feature is not available in production if app.config['DEVELOPER_AUTH_ENABLED']: users = AuthorizedUser.query.all() return tolerant_jsonify(authorized_users_api_feed(users)) else: raise errors.ResourceNotFoundError('Unknown path')
def update_note_template(): params = request.form note_template_id = params.get('id', None) subject = params.get('subject', None) if not subject: raise BadRequestError('Requires \'subject\'') body = params.get('body', None) topics = get_note_topics_from_http_post() delete_ids_ = params.get('deleteAttachmentIds') or [] delete_ids_ = delete_ids_ if isinstance( delete_ids_, list) else str(delete_ids_).split(',') delete_attachment_ids = [int(id_) for id_ in delete_ids_] note_template = NoteTemplate.find_by_id(note_template_id=note_template_id) if not note_template: raise ResourceNotFoundError('Template not found') if note_template.creator_id != current_user.get_id(): raise ForbiddenRequestError('Template not available.') note_template = NoteTemplate.update( attachments=get_note_attachments_from_http_post(tolerate_none=True), body=process_input_from_rich_text_editor(body), delete_attachment_ids=delete_attachment_ids, is_private=to_bool_or_none(params.get('isPrivate', False)), note_template_id=note_template_id, subject=subject, topics=topics, ) return tolerant_jsonify(note_template.to_api_json())
def create_cohort(): params = request.get_json() if is_unauthorized_search(params): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) label = util.get(params, 'label', None) if not label: raise BadRequestError('Cohort creation requires \'label\'') cohort = CohortFilter.create( advisor_ldap_uids=util.get(params, 'advisorLdapUids'), coe_prep_statuses=util.get(params, 'coePrepStatuses'), ethnicities=util.get(params, 'ethnicities'), genders=util.get(params, 'genders'), gpa_ranges=util.get(params, 'gpaRanges'), group_codes=util.get(params, 'groupCodes'), in_intensive_cohort=util.to_bool_or_none( params.get('inIntensiveCohort')), is_inactive_asc=util.to_bool_or_none(params.get('isInactiveAsc')), label=label, last_name_range=util.get(params, 'lastNameRange'), levels=util.get(params, 'levels'), majors=util.get(params, 'majors'), uid=current_user.get_id(), underrepresented=util.get(params, 'underrepresented'), unit_ranges=util.get(params, 'unitRanges'), ) return tolerant_jsonify(decorate_cohort(cohort))
def get_note_template(note_template_id): note_template = NoteTemplate.find_by_id(note_template_id=note_template_id) if not note_template: raise ResourceNotFoundError('Template not found') if note_template.creator_id != current_user.get_id(): raise ForbiddenRequestError('Template not available') return tolerant_jsonify(note_template.to_api_json())
def get_cohort(cohort_id): if is_unauthorized_search(request.args): raise ForbiddenRequestError( 'You are unauthorized to access student data managed by other departments' ) include_students = util.to_bool_or_none( util.get(request.args, 'includeStudents')) include_students = True if include_students is None else include_students order_by = util.get(request.args, 'orderBy', None) offset = util.get(request.args, 'offset', 0) limit = util.get(request.args, 'limit', 50) cohort = CohortFilter.find_by_id(int(cohort_id)) if cohort and can_view_cohort(current_user, cohort): cohort = decorate_cohort( cohort, order_by=order_by, offset=int(offset), limit=int(limit), include_alerts_for_uid=current_user.uid, include_profiles=True, include_students=include_students, ) return tolerant_jsonify(cohort) else: raise ResourceNotFoundError( f'No cohort found with identifier: {cohort_id}')