Exemple #1
0
    def test_create_and_delete_cohort(self):
        """Cohort_filter record to Flask-Login for recognized UID."""
        owner = AuthorizedUser.find_by_uid(asc_advisor_uid).uid
        # Check validity of UID
        assert owner

        # Create cohort
        group_codes = ['MFB-DB', 'MFB-DL', 'MFB-MLB', 'MFB-OLB']
        cohort = CohortFilter.create(
            uid=owner,
            name='Football, Defense',
            filter_criteria={
                'groupCodes': group_codes,
            },
        )
        cohort_id = cohort['id']
        assert CohortFilter.find_by_id(cohort_id)['owner']['uid'] == owner
        assert cohort['totalStudentCount'] == len(
            CohortFilter.get_sids(cohort_id))

        # Delete cohort and verify
        previous_owner_count = cohort_count(owner)
        CohortFilter.delete(cohort_id)
        std_commit(allow_test_environment=True)
        assert cohort_count(owner) == previous_owner_count - 1
Exemple #2
0
def _construct_phantom_cohort(filters, **kwargs):
    # A "phantom" cohort is an unsaved search.
    cohort = CohortFilter(
        name=f'phantom_cohort_{datetime.now().timestamp()}',
        filter_criteria=_translate_filters_to_cohort_criteria(filters),
    )
    return cohort.to_api_json(**kwargs)
Exemple #3
0
    def test_distinct_sids(self, client, fake_auth):
        """Get distinct SIDs across cohorts and curated groups."""
        user_id = AuthorizedUser.get_id_per_uid(coe_advisor_uid)
        cohort_ids = []
        sids = set()
        for cohort in CohortFilter.get_cohorts(user_id):
            cohort_id = cohort['id']
            cohort_ids.append(cohort_id)
            sids.update(set(CohortFilter.get_sids(cohort_id)))

        assert len(sids) > 1
        curated_group = CuratedGroup.create(
            user_id, 'Like a lemon to a lime, a lime to a lemon')
        curated_group_ids = [curated_group.id]
        # We use SIDs from cohorts (above). Therefore, we expect no increase in 'batch_distinct_student_count'.
        for sid in sids:
            CuratedGroup.add_student(curated_group.id, sid)
        # A specific student (SID) that is in neither cohorts nor curated groups.
        some_other_sid = '5678901234'
        assert some_other_sid not in sids
        # Log in as authorized user
        fake_auth.login(coe_advisor_uid)
        data = self._api_distinct_sids(
            client,
            cohort_ids=cohort_ids,
            curated_group_ids=curated_group_ids,
            sids=[some_other_sid],
        )
        assert sids.union({some_other_sid}) == set(data['sids'])
Exemple #4
0
    def test_cohort_update_filter_criteria(self, client, asc_advisor_session):
        label = 'Swimming, Men\'s'
        original_student_count = 4
        cohort = CohortFilter.create(
            uid=asc_advisor_uid,
            label=label,
            group_codes=['MSW', 'MSW-DV', 'MSW-SW'],
            student_count=original_student_count,
        )
        assert original_student_count > 0
        updated_filter_criteria = {
            'groupCodes': ['MSW-DV', 'MSW-SW'],
        }
        data = {
            'id': cohort.id,
            'filterCriteria': updated_filter_criteria,
            'studentCount': original_student_count - 1,
        }
        response = client.post('/api/filtered_cohort/update', data=json.dumps(data), content_type='application/json')
        assert 200 == response.status_code

        updated_cohort = CohortFilter.find_by_id(int(response.json['id']))
        assert updated_cohort.label == label
        assert updated_cohort.student_count == original_student_count - 1

        def remove_empties(filter_criteria):
            return {k: v for k, v in filter_criteria.items() if v is not None}

        expected = remove_empties(cohort.filter_criteria)
        actual = remove_empties(updated_cohort.filter_criteria)
        assert expected == actual
Exemple #5
0
    def test_filter_criteria(self):
        gpa_ranges = [
            'numrange(0, 2, \'[)\')',
            'numrange(2, 2.5, \'[)\')',
        ]
        group_codes = ['MFB-DB', 'MFB-DL']
        levels = ['Junior']
        majors = ['Environmental Economics & Policy', 'Gender and Women\'s Studies']
        unit_ranges = [
            'numrange(0, 5, \'[]\')',
            'numrange(30, NULL, \'[)\')',
        ]
        cohort = CohortFilter.create(
            uid='1022796',
            label='All criteria, all the time',
            gpa_ranges=gpa_ranges,
            group_codes=group_codes,
            in_intensive_cohort=None,
            levels=levels,
            majors=majors,
            unit_ranges=unit_ranges,
        )
        cohort = CohortFilter.find_by_id(cohort.id)
        expected = {
            'gpaRanges': gpa_ranges,
            'groupCodes': group_codes,
            'inIntensiveCohort': None,
            'levels': levels,
            'majors': majors,
            'unitRanges': unit_ranges,
        }

        def sort_and_format(filter_criteria):
            return json.dumps(filter_criteria, sort_keys=True, indent=2)
        assert sort_and_format(expected) == sort_and_format(cohort.filter_criteria)
Exemple #6
0
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)
Exemple #7
0
 def test_cohort_update(self):
     cohort = CohortFilter.create(label='Football teams',
                                  team_codes=['FBM', 'FBW'],
                                  uid='2040')
     foosball_label = 'Foosball teams'
     cohort = CohortFilter.update(cohort['id'], foosball_label)
     assert cohort['label'] == foosball_label
Exemple #8
0
 def test_undefined_filter_criteria(self):
     with pytest.raises(InternalServerError):
         CohortFilter.create(
             uid=asc_advisor_uid,
             label='Cohort with undefined filter criteria',
             genders=[],
             in_intensive_cohort=None,
         )
def dismiss_alert(alert_id):
    user_id = current_user.get_id()
    Alert.dismiss(alert_id, user_id)
    CohortFilter.refresh_alert_counts_for_owner(user_id)
    return tolerant_jsonify({
        'message':
        f'Alert {alert_id} dismissed by UID {current_user.get_uid()}'
    }), 200
Exemple #10
0
 def test_delete_cohort(self, client, coe_advisor_session):
     """Deletes existing custom cohort while enforcing rules of ownership."""
     label = 'Water polo teams'
     cohort = CohortFilter.create(uid=coe_advisor_uid, label=label, group_codes=['WWP', 'MWP'])
     # Verify deletion
     response = client.delete(f'/api/filtered_cohort/delete/{cohort.id}')
     assert response.status_code == 200
     cohorts = CohortFilter.all_owned_by(coe_advisor_uid)
     assert not next((c for c in cohorts if c.id == cohort.id), None)
Exemple #11
0
 def delete(cls, curated_group_id):
     curated_group = cls.query.filter_by(id=curated_group_id).first()
     if curated_group:
         db.session.delete(curated_group)
         std_commit()
         # Delete all cohorts that reference the deleted group
         for cohort_filter_id in curated_group.get_referencing_cohort_ids():
             CohortFilter.delete(cohort_filter_id)
             std_commit()
Exemple #12
0
 def test_undefined_filter_criteria(self):
     with pytest.raises(InternalServerError):
         CohortFilter.create(
             uid=asc_advisor_uid,
             name='Cohort with undefined filter criteria',
             filter_criteria={
                 'genders': [],
                 'inIntensiveCohort': None,
             },
         )
Exemple #13
0
 def test_filter_criteria(self):
     gpa_ranges = [
         {
             'min': 0,
             'max': 1.999
         },
         {
             'min': 2,
             'max': 2.499
         },
     ]
     group_codes = ['MFB-DB', 'MFB-DL']
     intended_majors = ['Public Health BA']
     levels = ['Junior']
     majors = [
         'Environmental Economics & Policy', 'Gender and Women\'s Studies'
     ]
     minors = ['Physics UG']
     unit_ranges = [
         'numrange(0, 5, \'[]\')',
         'numrange(30, NULL, \'[)\')',
     ]
     cohort = CohortFilter.create(
         uid='1022796',
         name='All criteria, all the time',
         filter_criteria={
             'gpaRanges': gpa_ranges,
             'groupCodes': group_codes,
             'inIntensiveCohort': None,
             'intendedMajors': intended_majors,
             'levels': levels,
             'majors': majors,
             'minors': minors,
             'unitRanges': unit_ranges,
         },
     )
     cohort_id = cohort['id']
     cohort = CohortFilter.find_by_id(cohort_id)
     expected = {
         'gpaRanges': gpa_ranges,
         'groupCodes': group_codes,
         'inIntensiveCohort': None,
         'intendedMajors': intended_majors,
         'levels': levels,
         'majors': majors,
         'minors': minors,
         'unitRanges': unit_ranges,
     }
     for key, value in expected.items():
         assert cohort['criteria'][key] == expected[key]
     assert cohort['totalStudentCount'] == len(
         CohortFilter.get_sids(cohort_id))
    def test_delete_cohort(self, authenticated_session, client):
        """deletes existing custom cohort while enforcing rules of ownership"""
        label = 'Water polo teams'
        cohort = CohortFilter.create(label=label, team_codes=['WPW', 'WPM'], uid=test_uid)

        assert cohort and 'id' in cohort
        id_of_created_cohort = cohort['id']

        # Verify deletion
        response = client.delete('/api/cohort/delete/{}'.format(id_of_created_cohort))
        assert response.status_code == 200
        cohorts = CohortFilter.all_owned_by(test_uid)
        assert not next((c for c in cohorts if c['id'] == id_of_created_cohort), None)
Exemple #15
0
def delete_cohort(cohort_id):
    if cohort_id.isdigit():
        cohort_id = int(cohort_id)
        if CohortFilter.is_cohort_owned_by(cohort_id, current_user.get_id()):
            CohortFilter.delete(cohort_id)
            return tolerant_jsonify(
                {'message': f'Cohort deleted (id={cohort_id})'}), 200
        else:
            raise BadRequestError(
                f'User {current_user.get_uid()} does not own cohort with id={cohort_id}'
            )
    else:
        raise ForbiddenRequestError(
            f'Programmatic deletion of canned cohorts is not allowed (id={cohort_id})'
        )
Exemple #16
0
def update_cohort():
    params = request.get_json()
    uid = current_user.get_id()
    label = params['label']
    if not label:
        raise BadRequestError('Requested cohort label is empty or invalid')

    cohort = get_cohort_owned_by(params['id'], uid)
    if not cohort:
        raise BadRequestError(
            'Cohort does not exist or is not owned by {}'.format(uid))

    CohortFilter.update(cohort_id=cohort['id'], label=label)
    return jsonify({'message':
                    'Cohort updated (label: {})'.format(label)}), 200
Exemple #17
0
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}')
Exemple #18
0
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))
Exemple #19
0
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)
Exemple #20
0
def _filters_to_filter_criteria(filters, order_by=None):
    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'
        )
    return CohortFilter.translate_filters_to_cohort_criteria(filters)
Exemple #21
0
def create_cohort():
    params = request.get_json()
    domain = get_param(params, 'domain', 'default')
    if is_unauthorized_domain(domain):
        raise ForbiddenRequestError(
            f'You are unauthorized to query the \'{domain}\' domain')
    name = get_param(params, 'name', None)
    filters = get_param(params, 'filters', None)
    order_by = params.get('orderBy')
    # Authorization check
    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'
        )
    filter_criteria = _translate_filters_to_cohort_criteria(filters, domain)
    if not name or not filter_criteria:
        raise BadRequestError(
            'Cohort creation requires \'name\' and \'filters\'')
    cohort = CohortFilter.create(
        uid=current_user.get_uid(),
        name=name,
        filter_criteria=filter_criteria,
        domain=domain,
        order_by=order_by,
        include_alerts_for_user_id=current_user.get_id(),
    )
    _decorate_cohort(cohort)
    return tolerant_jsonify(cohort)
Exemple #22
0
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)
Exemple #23
0
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 = CohortFilter.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)
Exemple #24
0
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}')
Exemple #25
0
 def test_unauthorized_cohort_update(self, client, coe_advisor_session):
     cohort = CohortFilter.create(uid=asc_advisor_uid, label='Swimming, Men\'s', group_codes=['MSW', 'MSW-DV', 'MSW-SW'])
     data = {
         'id': cohort.id,
         'label': 'Hack the label!',
     }
     response = client.post('/api/filtered_cohort/update', data=json.dumps(data), content_type='application/json')
     assert 403 == response.status_code
Exemple #26
0
def delete_cohort(cohort_id):
    if cohort_id.isdigit():
        cohort_id = int(cohort_id)
        uid = current_user.get_id()
        cohort = get_cohort_owned_by(cohort_id, uid)
        if cohort:
            CohortFilter.delete(cohort_id)
            return jsonify(
                {'message': 'Cohort deleted (id={})'.format(cohort_id)}), 200
        else:
            raise BadRequestError(
                'User {uid} does not own cohort_filter with id={id}'.format(
                    uid=uid, id=cohort_id))
    else:
        raise ForbiddenRequestError(
            'Programmatic deletion of teams is not supported (id={})'.format(
                cohort_id))
Exemple #27
0
    def test_create_and_delete_cohort(self):
        """Cohort_filter record to Flask-Login for recognized UID."""
        owner = AuthorizedUser.find_by_uid(asc_advisor_uid).uid
        shared_with = AuthorizedUser.find_by_uid(coe_advisor_uid).uid
        # Check validity of UIDs
        assert owner
        assert shared_with

        # Create and share cohort
        group_codes = ['MFB-DB', 'MFB-DL', 'MFB-MLB', 'MFB-OLB']
        cohort = CohortFilter.create(
            uid=owner,
            name='Football, Defense',
            filter_criteria={
                'groupCodes': group_codes,
            },
        )
        cohort_id = cohort['id']
        CohortFilter.share(cohort_id, shared_with)
        owners = CohortFilter.find_by_id(cohort_id)['owners']
        assert len(owners) == 2
        assert owner, shared_with in [user['uid'] for user in owners]
        assert cohort['totalStudentCount'] == len(
            CohortFilter.get_sids(cohort_id))

        # Delete cohort and verify
        previous_owner_count = cohort_count(owner)
        previous_shared_count = cohort_count(shared_with)
        CohortFilter.delete(cohort_id)
        assert cohort_count(owner) == previous_owner_count - 1
        assert cohort_count(shared_with) == previous_shared_count - 1
Exemple #28
0
def delete_cohort(cohort_id):
    if cohort_id.isdigit():
        cohort_id = int(cohort_id)
        uid = current_user.get_id()
        cohort = next(
            (c for c in CohortFilter.all_owned_by(uid) if c.id == cohort_id),
            None)
        if cohort:
            CohortFilter.delete(cohort_id)
            return tolerant_jsonify(
                {'message': f'Cohort deleted (id={cohort_id})'}), 200
        else:
            raise BadRequestError(
                f'User {uid} does not own cohort_filter with id={cohort_id}')
    else:
        raise ForbiddenRequestError(
            f'Programmatic deletion of canned cohorts is not allowed (id={cohort_id})'
        )
Exemple #29
0
def all_cohorts():
    cohorts = {}
    for cohort in CohortFilter.all():
        for uid in cohort['owners']:
            if uid not in cohorts:
                cohorts[uid] = []
            cohorts[uid].append(cohort)

    return jsonify(cohorts)
    def test_delete_cohort_wrong_user(self, client, fake_auth):
        """custom cohort deletion is only available to owners"""
        cohort = CohortFilter.create(label='Badminton teams', team_codes=['MBK', 'WBK'], uid=test_uid)
        assert cohort and 'id' in cohort

        # This user does not own the custom cohort above
        fake_auth.login('2040')
        response = client.delete('/api/cohort/delete/{}'.format(cohort['id']))
        assert response.status_code == 400
        assert '2040 does not own' in str(response.data)