Esempio n. 1
0
def delete_degree_category(category_id):
    category = _get_degree_category(category_id)
    DegreeProgressCategory.delete(category.id)
    # Update updated_at date of top-level record
    DegreeProgressTemplate.refresh_updated_at(category.template_id,
                                              current_user.get_id())
    return tolerant_jsonify({'message':
                             f'Template {category_id} deleted'}), 200
Esempio n. 2
0
def update_category(category_id):
    params = request.get_json()
    units_lower = get_param(params, 'unitsLower')
    units_upper = get_param(params, 'unitsUpper')
    description = get_param(params, 'description')
    name = get_param(params, 'name')
    parent_category_id = get_param(params, 'parentCategoryId')
    # Courses can be mapped to degree_progress_unit_requirements
    value = get_param(request.get_json(), 'unitRequirementIds')
    unit_requirement_ids = list(filter(None, value.split(','))) if isinstance(
        value, str) else value

    category = DegreeProgressCategory.update(
        category_id=category_id,
        course_units_lower=units_lower,
        course_units_upper=units_upper,
        description=description,
        parent_category_id=parent_category_id,
        name=name,
        unit_requirement_ids=unit_requirement_ids,
    )
    # Update updated_at date of top-level record
    DegreeProgressTemplate.refresh_updated_at(category.template_id,
                                              current_user.get_id())
    return tolerant_jsonify(category.to_api_json())
Esempio n. 3
0
def toggle_campus_requirement(category_id):
    params = request.get_json()
    is_satisfied = get_param(params, 'isSatisfied')
    if is_satisfied is None:
        raise BadRequestError('Parameter \'isSatisfied\' is required')
    category = _get_degree_category(category_id)
    if category.category_type not in [
            'Campus Requirement, Satisfied', 'Campus Requirement, Unsatisfied'
    ]:
        raise BadRequestError('Category must be a \'Campus Requirement\' type')
    if ((category.category_type == 'Campus Requirement, Satisfied'
         and is_satisfied is True)
            or (category.category_type == 'Campus Requirement, Unsatisfied'
                and is_satisfied is False)):
        app.logger.info(
            f'Request ignored: set is_satisfied={is_satisfied} on {category.category_type}'
        )
    else:
        category = DegreeProgressCategory.set_campus_requirement_satisfied(
            category_id=category_id,
            is_satisfied=is_satisfied,
        )
        # Update updated_at date of top-level record
        DegreeProgressTemplate.refresh_updated_at(category.template_id,
                                                  current_user.get_id())
    return tolerant_jsonify(category.to_api_json())
Esempio n. 4
0
def recommend_category(category_id):
    params = request.get_json()
    is_recommended = get_param(params, 'isRecommended')
    if is_recommended is None:
        raise BadRequestError('Parameter \'isRecommended\' is required')

    accent_color = normalize_accent_color(get_param(params, 'accentColor'))
    grade = get_param(params, 'grade')
    note = get_param(params, 'note')
    units_lower = get_param(params, 'unitsLower')
    units_upper = get_param(params, 'unitsUpper')

    category = DegreeProgressCategory.recommend(
        accent_color=accent_color,
        category_id=category_id,
        course_units_lower=units_lower,
        course_units_upper=units_upper,
        grade=grade,
        is_recommended=is_recommended,
        note=(note or '').strip() or None,
    )
    # Update updated_at date of top-level record
    DegreeProgressTemplate.refresh_updated_at(category.template_id,
                                              current_user.get_id())
    return tolerant_jsonify(category.to_api_json())
Esempio n. 5
0
def assign_course(course_id):
    params = request.get_json()
    course = DegreeProgressCourse.find_by_id(course_id)
    if course:
        # Get existing category assignment. If it's a placeholder then delete it at end of transaction.
        previous_category = DegreeProgressCategory.find_by_id(
            course.category_id) if course.category_id else None
        category_id = get_param(params, 'categoryId')
        category = DegreeProgressCategory.find_by_id(
            category_id) if category_id else None
        if category:
            if category.template_id != course.degree_check_id:
                raise BadRequestError(
                    'The category and course do not belong to the same degree_check instance.'
                )
            children = DegreeProgressCategory.find_by_parent_category_id(
                parent_category_id=category_id)
            if next((c for c in children if c.category_type == 'Subcategory'),
                    None):
                raise BadRequestError(
                    'A course cannot be assigned to a category with a subcategory.'
                )
            course = DegreeProgressCourse.assign(category_id=category_id,
                                                 course_id=course.id)

        else:
            # When user un-assigns a course we delete all copies of that course,.
            for copy_of_course in DegreeProgressCourse.get_courses(
                    degree_check_id=course.degree_check_id,
                    manually_created_at=course.manually_created_at,
                    manually_created_by=course.manually_created_by,
                    section_id=course.section_id,
                    sid=course.sid,
                    term_id=course.term_id,
            ):
                if copy_of_course.id != course.id:
                    DegreeProgressCourseUnitRequirement.delete(
                        copy_of_course.id)
                    category = DegreeProgressCategory.find_by_id(
                        copy_of_course.category_id)
                    if category and 'Placeholder' in category.category_type:
                        DegreeProgressCategory.delete(category.id)
                    DegreeProgressCourse.delete(copy_of_course)
            ignore = to_bool_or_none(get_param(params, 'ignore'))
            course = DegreeProgressCourse.unassign(course_id=course.id,
                                                   ignore=ignore)

        # If previous assignment was a "placeholder" category then delete it.
        if previous_category and 'Placeholder' in previous_category.category_type:
            DegreeProgressCategory.delete(previous_category.id)

        # Update updated_at date of top-level record
        DegreeProgressTemplate.refresh_updated_at(course.degree_check_id,
                                                  current_user.get_id())
        return tolerant_jsonify(course.to_api_json())
    else:
        raise ResourceNotFoundError('Course not found.')
Esempio n. 6
0
 def _create_category(category_type='Category',
                      parent_category_id=None):
     category = DegreeProgressCategory.create(
         category_type=category_type,
         name=
         f'{category_type} for {sid} ({datetime.now().timestamp()})',
         parent_category_id=parent_category_id,
         position=1,
         template_id=degree_check_id,
     )
     return category
Esempio n. 7
0
    def test_illegal_assign(self, client, fake_auth):
        """A course cannot be assigned to a category with a subcategory."""
        advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(advisor.uid)
        sid = '11667051'
        # Set up
        degree_check = DegreeProgressTemplate.create(
            advisor_dept_codes=['COENG'],
            created_by=advisor.id,
            degree_name=f'Degree for {sid}',
            student_sid=sid,
        )
        category = DegreeProgressCategory.create(
            category_type='Category',
            name=f'Category for {sid}',
            position=1,
            template_id=degree_check.id,
        )
        # Subcategory
        subcategory = DegreeProgressCategory.create(
            category_type='Subcategory',
            name=f'Subcategory for {sid}',
            parent_category_id=category.id,
            position=category.position,
            template_id=degree_check.id,
        )
        std_commit(allow_test_environment=True)

        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        course_id = api_json['courses']['unassigned'][-1]['id']
        # Expect failure
        _api_assign_course(category_id=category.id,
                           client=client,
                           course_id=course_id,
                           expected_status_code=400)
        # Expect success
        _api_assign_course(category_id=subcategory.id,
                           client=client,
                           course_id=course_id)
Esempio n. 8
0
def _create_category(
    category_type,
    course_units_lower,
    course_units_upper,
    description,
    name,
    parent_category_id,
    position,
    template_id,
    unit_requirement_ids,
):
    campus_requirements = []
    if category_type == 'Campus Requirements':
        category_type = 'Category'
        name = 'Campus Requirements'
        campus_requirements = [
            'Entry Level Writing', 'American History', 'American Institutions',
            'American Cultures'
        ]
    category = DegreeProgressCategory.create(
        category_type=category_type,
        course_units_lower=course_units_lower,
        course_units_upper=course_units_upper,
        description=description,
        name=name,
        parent_category_id=parent_category_id,
        position=position,
        template_id=template_id,
        unit_requirement_ids=unit_requirement_ids,
    )
    for name in campus_requirements:
        DegreeProgressCategory.create(
            category_type='Campus Requirement, Unsatisfied',
            name=name,
            parent_category_id=category.id,
            position=position,
            template_id=template_id,
        )
    return category
def clone(template, created_by, name=None, sid=None):
    clone = DegreeProgressTemplate.create(
        advisor_dept_codes=dept_codes_where_advising(current_user),
        created_by=created_by,
        degree_name=name or template.degree_name,
        parent_template_id=template.id if sid else None,
        student_sid=sid,
    )
    unit_requirements_by_source_id = {}
    for unit_requirement in template.unit_requirements:
        source_id = unit_requirement.id
        unit_requirements_by_source_id[
            source_id] = DegreeProgressUnitRequirement.create(
                created_by=created_by,
                min_units=unit_requirement.min_units,
                name=unit_requirement.name,
                template_id=clone.id,
            )

    def _create_category(category_, parent_id):
        unit_requirement_ids = []
        for u in category_['unitRequirements']:
            source_id_ = u['id']
            cross_reference = unit_requirements_by_source_id[source_id_]
            unit_requirement_ids.append(cross_reference.id)
        return DegreeProgressCategory.create(
            category_type=category_['categoryType'],
            name=category_['name'],
            position=category_['position'],
            template_id=clone.id,
            course_units_lower=category_['unitsLower'],
            course_units_upper=category_['unitsUpper'],
            description=category_['description'],
            parent_category_id=parent_id,
            unit_requirement_ids=unit_requirement_ids,
        )

    for category in DegreeProgressCategory.get_categories(
            template_id=template.id):
        c = _create_category(category_=category, parent_id=None)
        for course in category['courseRequirements']:
            _create_category(category_=course, parent_id=c.id)
        for subcategory in category['subcategories']:
            s = _create_category(category_=subcategory, parent_id=c.id)
            for course in subcategory['courseRequirements']:
                _create_category(category_=course, parent_id=s.id)

    # TODO: Unit requirements?
    return DegreeProgressTemplate.find_by_id(clone.id)
Esempio n. 10
0
 def test_assign_and_unassign_course(self, client, fake_auth):
     """User can assign and unassign a course."""
     advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
     fake_auth.login(advisor.uid)
     sid = '11667051'
     # Set up
     degree_check = DegreeProgressTemplate.create(
         advisor_dept_codes=['COENG'],
         created_by=advisor.id,
         degree_name=f'Degree check for {sid}',
         student_sid=sid,
     )
     original_updated_at = degree_check.updated_at
     category = DegreeProgressCategory.create(
         category_type='Category',
         name=f'Category for {sid}',
         position=1,
         template_id=degree_check.id,
     )
     std_commit(allow_test_environment=True)
     # Assign
     api_json = _api_get_degree(client, degree_check_id=degree_check.id)
     course_id = api_json['courses']['unassigned'][-1]['id']
     course = _api_assign_course(category_id=category.id,
                                 client=client,
                                 course_id=course_id)
     # Verify assignment
     api_json = _api_get_degree(client, degree_check_id=degree_check.id)
     assert course['categoryId'] == category.id
     assert course_id in [c['id'] for c in api_json['courses']['assigned']]
     assert course_id not in [
         c['id'] for c in api_json['courses']['unassigned']
     ]
     # Unassign
     _api_assign_course(category_id=None,
                        client=client,
                        course_id=course_id)
     api_json = _api_get_degree(client, degree_check_id=degree_check.id)
     assert course_id not in [
         c['id'] for c in api_json['courses']['assigned']
     ]
     assert course_id in [
         c['id'] for c in api_json['courses']['unassigned']
     ]
     # Verify update of updated_at
     assert DegreeProgressTemplate.find_by_id(
         degree_check.id).updated_at != original_updated_at
 def test_recommend_category(self, client, fake_auth, mock_template):
     """Authorized user can edit a category."""
     user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
     fake_auth.login(user.uid)
     category = DegreeProgressCategory.create(
         category_type='Category',
         name='Recommend me to your friends.',
         position=1,
         template_id=mock_template.id,
     )
     category_id = category.id
     note = 'Four out of five dentists surveyed recommend sugarless gum for their patients who chew gum.'
     self._api_recommend_category(
         category_id=category_id,
         client=client,
         grade='B',
         is_recommended=True,
         note=note,
         units_lower=3,
         units_upper=4,
     )
     # Verify the update
     api_json = _api_get_template(client=client,
                                  template_id=mock_template.id)
     category = api_json['categories'][0]
     assert category['id'] == category_id
     assert category['grade'] == 'B'
     assert category['isRecommended'] is True
     assert category['note'] == note
     assert category['unitsLower'] == 3
     assert category['unitsUpper'] == 4
     # Set to False
     self._api_recommend_category(
         category_id=category_id,
         client=client,
         is_recommended=False,
         note='',
     )
     api_json = _api_get_template(client=client,
                                  template_id=mock_template.id)
     category = api_json['categories'][0]
     assert category['id'] == category_id
     assert category['grade'] is None
     assert category['isRecommended'] is False
     assert category['note'] is None
     assert category['unitsLower'] is None
     assert category['unitsUpper'] is None
 def _create_category(category_, parent_id):
     unit_requirement_ids = []
     for u in category_['unitRequirements']:
         source_id_ = u['id']
         cross_reference = unit_requirements_by_source_id[source_id_]
         unit_requirement_ids.append(cross_reference.id)
     return DegreeProgressCategory.create(
         category_type=category_['categoryType'],
         name=category_['name'],
         position=category_['position'],
         template_id=clone.id,
         course_units_lower=category_['unitsLower'],
         course_units_upper=category_['unitsUpper'],
         description=category_['description'],
         parent_category_id=parent_id,
         unit_requirement_ids=unit_requirement_ids,
     )
 def test_create_campus_requirements(self, client, fake_auth, mock_template, app):
     """Campus Requirements is created as a category with four children."""
     fake_auth.login(coe_advisor_read_write_uid)
     api_json = _api_create_category(
         client,
         category_type='Campus Requirements',
         name='hacked name',
         position=2,
         template_id=mock_template.id,
     )
     assert api_json['id']
     assert api_json['categoryType'] == 'Category'
     assert api_json['name'] == 'Campus Requirements'
     assert api_json['parentCategoryId'] is None
     assert api_json['position'] == 2
     assert api_json['templateId'] == mock_template.id
     children = DegreeProgressCategory.find_by_parent_category_id(api_json['id'])
     assert len(children) == 4
Esempio n. 14
0
    def test_unassigned_courses(self, client, fake_auth):
        """Authorized user can un-assign a course."""
        advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(advisor.uid)
        # Set up
        sid = '11667051'
        degree_check = DegreeProgressTemplate.create(
            advisor_dept_codes=['COENG'],
            created_by=advisor.id,
            degree_name=f'Degree check for {sid}',
            student_sid=sid,
        )
        category = DegreeProgressCategory.create(
            category_type='Category',
            name=f'Category for {sid}',
            position=1,
            template_id=degree_check.id,
        )
        std_commit(allow_test_environment=True)
        # Fetch
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        assigned_courses = api_json['courses']['assigned']
        unassigned_courses = api_json['courses']['unassigned']
        assert len(unassigned_courses)
        assigned_course_count = len(assigned_courses)
        unassigned_course_count = len(unassigned_courses)
        # Assign
        unassigned_course = unassigned_courses[-1]
        _api_assign_course(client=client,
                           category_id=category.id,
                           course_id=unassigned_course['id'])
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        # Verify
        assigned_courses = api_json['courses']['assigned']
        assert len(assigned_courses) == assigned_course_count + 1
        assert next((c for c in unassigned_courses
                     if c['sectionId'] == unassigned_course['sectionId']),
                    None)

        unassigned_courses = api_json['courses']['unassigned']
        assert len(unassigned_courses) == unassigned_course_count - 1
        assert next((c for c in unassigned_courses
                     if c['sectionId'] == unassigned_course['sectionId']),
                    None) is None
    def test_satisfy_requirement(self, client, fake_auth, mock_template):
        """Authorized user can toggle a Campus Requirement satisfied/unsatisfied."""
        user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(user.uid)
        parent_category = _api_create_category(
            client,
            category_type='Campus Requirements',
            name='Campus Requirements',
            position=3,
            template_id=mock_template.id,
        )
        # Requirements are initially unsatisfied
        requirements = DegreeProgressCategory.find_by_parent_category_id(
            parent_category['id'])
        for requirement in requirements:
            assert requirement.category_type == 'Campus Requirement, Unsatisfied'
        requirement_id = requirements[0].id
        self._api_toggle_satisfied(
            category_id=requirement_id,
            client=client,
            is_satisfied=True,
        )
        api_json = _api_get_template(client=client,
                                     template_id=mock_template.id)
        category = api_json['categories'][0]
        assert category['id'] == parent_category['id']
        assert category['courseRequirements'][0]['id'] == requirement_id
        assert category['courseRequirements'][0][
            'categoryType'] == 'Campus Requirement, Satisfied'

        self._api_toggle_satisfied(
            category_id=requirements[0].id,
            client=client,
            is_satisfied=False,
        )
        api_json = _api_get_template(client=client,
                                     template_id=mock_template.id)
        category = api_json['categories'][0]
        assert category['id'] == parent_category['id']
        assert category['courseRequirements'][0]['id'] == requirement_id
        assert category['courseRequirements'][0][
            'categoryType'] == 'Campus Requirement, Unsatisfied'
Esempio n. 16
0
 def to_api_json(self, include_courses=False):
     unit_requirements = [u.to_api_json() for u in self.unit_requirements]
     api_json = {
         'id': self.id,
         'advisorDeptCodes': self.advisor_dept_codes,
         'categories': DegreeProgressCategory.get_categories(template_id=self.id),
         'createdAt': _isoformat(self.created_at),
         'createdBy': self.created_by,
         'name': self.degree_name,
         'note': self.note.to_api_json() if self.note else None,
         'parentTemplateId': self.parent_template_id,
         'sid': self.student_sid,
         'unitRequirements': sorted(unit_requirements, key=lambda r: r['name']),
         'updatedAt': _isoformat(self.updated_at),
         'updatedBy': self.updated_by,
     }
     if self.student_sid and include_courses:
         assigned_courses, ignored_courses, unassigned_courses = self._get_partitioned_courses_json()
         api_json['courses'] = {
             'assigned': sorted(assigned_courses, key=lambda c: c['name']),
             'ignored': sorted(ignored_courses, key=lambda c: c['name']),
             'unassigned': sorted(unassigned_courses, key=lambda c: c['name']),
         }
     return api_json
Esempio n. 17
0
def copy_course():
    params = request.get_json()
    category_id = to_int_or_none(get_param(params, 'categoryId'))
    category = category_id and DegreeProgressCategory.find_by_id(category_id)
    if not category:
        raise ResourceNotFoundError('Category not found.')
    course_id = to_int_or_none(get_param(params, 'courseId'))
    course = DegreeProgressCourse.find_by_id(course_id)
    if not course:
        raise ResourceNotFoundError('Course not found.')

    sid = course.sid
    section_id = course.section_id
    term_id = course.term_id
    degree_check_id = category.template_id
    courses = DegreeProgressCourse.get_courses(
        degree_check_id=degree_check_id,
        manually_created_at=course.manually_created_at,
        manually_created_by=course.manually_created_by,
        section_id=section_id,
        sid=sid,
        term_id=term_id,
    )
    if not len(courses):
        raise ResourceNotFoundError('Course not found.')

    elif len(courses) == 1 and not courses[0].category_id:
        raise BadRequestError(
            f'Course {courses[0].id} is unassigned. Use the /assign API instead.'
        )

    else:
        if not _can_accept_course_requirements(category):
            raise BadRequestError(
                f'A \'Course Requirement\' cannot be added to a {category.category_type}.'
            )

        # Verify that course is not already in the requested category/subcategory.
        for c in DegreeProgressCourse.find_by_category_id(category.id):
            if c.section_id == section_id and c.sid == sid and c.term_id:
                raise BadRequestError(
                    f'Course already belongs to category {category.name}.')
        for child in DegreeProgressCategory.find_by_parent_category_id(
                category_id):
            if child.id == category_id:
                raise BadRequestError(
                    f'Course already belongs to category {category.name}.')

        # Create a new course instance and a new 'Course Requirement'.
        course = courses[0]
        course = DegreeProgressCourse.create(
            accent_color=course.accent_color,
            degree_check_id=degree_check_id,
            display_name=course.display_name,
            grade=course.grade,
            manually_created_at=course.manually_created_at,
            manually_created_by=course.manually_created_by,
            section_id=section_id,
            sid=sid,
            term_id=term_id,
            unit_requirement_ids=[
                u.unit_requirement_id for u in course.unit_requirements
            ],
            units=course.units,
        )
        course_requirement = DegreeProgressCategory.create(
            category_type='Placeholder: Course Copy',
            name=course.display_name,
            position=category.position,
            template_id=degree_check_id,
            course_units_lower=course.units,
            course_units_upper=course.units,
            parent_category_id=category.id,
            unit_requirement_ids=[
                u.unit_requirement_id for u in category.unit_requirements
            ],
        )
        DegreeProgressCourse.assign(category_id=course_requirement.id,
                                    course_id=course.id)
        return tolerant_jsonify(
            DegreeProgressCourse.find_by_id(course.id).to_api_json())
Esempio n. 18
0
    def test_assign_and_unassign_course(self, app, client, fake_auth):
        """User can assign and unassign a course."""
        advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(advisor.uid)
        sid = '11667051'
        # Set up
        degree_check = DegreeProgressTemplate.create(
            advisor_dept_codes=['COENG'],
            created_by=advisor.id,
            degree_name=f'Degree check for {sid}',
            student_sid=sid,
        )
        original_updated_at = degree_check.updated_at
        category = DegreeProgressCategory.create(
            category_type='Category',
            name=f'Category for {sid}',
            position=1,
            template_id=degree_check.id,
        )
        std_commit(allow_test_environment=True)
        # Assign
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        course_id = api_json['courses']['unassigned'][-1]['id']
        course = _api_assign_course(category_id=category.id,
                                    client=client,
                                    course_id=course_id)
        original_grade = course['grade']
        assert original_grade == 'P'

        # Verify assignment
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        assert course['categoryId'] == category.id
        assert course_id in [c['id'] for c in api_json['courses']['assigned']]
        assert course_id not in [
            c['id'] for c in api_json['courses']['unassigned']
        ]

        # Modify grade in data lake and verify that assigned course receives the update
        modified_grade = 'C+'
        _change_grade_in_data_loch(
            app=app,
            grade=modified_grade,
            section_id=course['sectionId'],
            sid=sid,
            term_id=course['termId'],
        )
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        assigned_course = api_json['courses']['assigned'][0]
        assert assigned_course['id'] == course_id
        assert assigned_course['grade'] == modified_grade
        _change_grade_in_data_loch(
            app=app,
            grade=original_grade,
            section_id=course['sectionId'],
            sid=sid,
            term_id=course['termId'],
        )

        # Unassign
        _api_assign_course(category_id=None,
                           client=client,
                           course_id=course_id)
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        assert course_id not in [
            c['id'] for c in api_json['courses']['assigned']
        ]
        unassigned_course = api_json['courses']['unassigned'][-1]
        assert unassigned_course['id'] == course_id
        assert unassigned_course['grade'] == original_grade
        # Verify update of updated_at
        assert DegreeProgressTemplate.find_by_id(
            degree_check.id).updated_at != original_updated_at
Esempio n. 19
0
    def test_delete(self, client, fake_auth):
        """Advisor can delete course."""
        advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(advisor.uid)
        sid = '11667051'

        degree_check = DegreeProgressTemplate.create(
            advisor_dept_codes=['COENG'],
            created_by=advisor.id,
            degree_name=f'Degree for {sid}',
            student_sid=sid,
        )
        api_json = _api_get_degree(client, degree_check_id=degree_check.id)
        course_id = api_json['courses']['unassigned'][-1]['id']
        categories = []
        for index in (1, 2, 3):
            categories.append(
                self._api_create_category(
                    category_type='Category',
                    client=client,
                    name=f'Category {index}',
                    position=index,
                    template_id=degree_check.id,
                ), )
        # Category #2 gets a child 'Course Requirement'
        course_requirement = self._api_create_category(
            category_type='Course Requirement',
            client=client,
            name='Course Requirement',
            parent_category_id=categories[1]['id'],
            position=categories[1]['position'],
            template_id=degree_check.id,
        )
        # Assign course to Category #1
        course = _api_assign_course(
            category_id=categories[0]['id'],
            client=client,
            course_id=course_id,
        )
        # Copy course to Category #2 and then assign it to the 'Course Requirement'
        course_id = course['id']
        course_copy_1 = _api_copy_course(
            category_id=categories[1]['id'],
            client=client,
            course_id=course_id,
        )
        placeholder_category_id = course_copy_1['categoryId']
        course_copy_1 = _api_assign_course(
            category_id=course_requirement['id'],
            client=client,
            course_id=course_copy_1['id'],
        )
        # Placeholder category is auto-deleted during re-assignment
        assert not DegreeProgressCategory.find_by_id(placeholder_category_id)
        # Delete the course_copy_1 and expect the underlying 'Course Requirement' to survive
        assert client.delete(
            f"/api/degree/course/{course_copy_1['id']}").status_code == 200

        # Copy course to Category #3 and then delete. Expect removal of course and its 'Placeholder' category.
        course_copy_2 = _api_copy_course(
            category_id=categories[2]['id'],
            client=client,
            course_id=course_id,
        )
        placeholder_category_id = course_copy_2['categoryId']
        assert 'Placeholder' in DegreeProgressCategory.find_by_id(
            placeholder_category_id).category_type
        assert client.delete(
            f"/api/degree/course/{course_copy_2['id']}").status_code == 200
        assert not DegreeProgressCategory.find_by_id(placeholder_category_id)
Esempio n. 20
0
    def test_copy_course(self, client, fake_auth):
        """User can copy course and add it to a category."""
        advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid)
        fake_auth.login(advisor.uid)
        sid = '11667051'
        degree_check = DegreeProgressTemplate.create(
            advisor_dept_codes=['COENG'],
            created_by=advisor.id,
            degree_name=f'Degree for {sid}',
            student_sid=sid,
        )
        std_commit(allow_test_environment=True)
        degree_check_id = degree_check.id

        # Set up
        def _create_category(category_type='Category',
                             parent_category_id=None):
            category = DegreeProgressCategory.create(
                category_type=category_type,
                name=
                f'{category_type} for {sid} ({datetime.now().timestamp()})',
                parent_category_id=parent_category_id,
                position=1,
                template_id=degree_check_id,
            )
            return category

        category_1 = _create_category()
        category_2 = _create_category()
        std_commit(allow_test_environment=True)

        # Get sample course from list of unassigned courses
        api_json = _api_get_degree(client=client,
                                   degree_check_id=degree_check_id)
        course = api_json['courses']['unassigned'][-1]
        course_id = course['id']
        section_id = course['sectionId']
        copied_course_ids = []

        def _copy_course(category_id, expected_status_code=200):
            course_copy = _api_copy_course(
                category_id=category_id,
                client=client,
                course_id=course_id,
                expected_status_code=expected_status_code,
            )
            if expected_status_code == 200:
                copied_course_ids.append(course_copy['id'])
            return course_copy

        # Verify: user cannot copy an unassigned course.
        _copy_course(category_id=category_1.id, expected_status_code=400)
        # Verify: user cannot copy course to a category which already has the course.
        _api_assign_course(category_id=category_1.id,
                           client=client,
                           course_id=course_id)
        by_id = DegreeProgressCourse.find_by_id(course_id)
        assert by_id.category_id == category_1.id
        _copy_course(category_id=category_1.id, expected_status_code=400)

        subcategory = _create_category(category_type='Subcategory',
                                       parent_category_id=category_1.id)
        # Verify: user cannot copy course to a category which has subcategories.
        _copy_course(category_id=category_1.id, expected_status_code=400)

        # Verify we can create a copy of course in subcategory, thereby provisioning a new 'Course Requirement'
        child_count = len(
            DegreeProgressCategory.find_by_parent_category_id(subcategory.id))
        copy_of_course = _copy_course(category_id=subcategory.id)
        children = DegreeProgressCategory.find_by_parent_category_id(
            subcategory.id)
        assert len(children) == child_count + 1
        assert children[0].category_type == 'Placeholder: Course Copy'
        assert copy_of_course['categoryId'] == children[0].id

        # Assign the copied course to an actual Course Requirement and verify that "placeholder" category is deleted.
        course_requirement = _create_category(
            category_type='Course Requirement',
            parent_category_id=subcategory.id)
        placeholder_category_id = copy_of_course['categoryId']
        _api_assign_course(category_id=course_requirement.id,
                           client=client,
                           course_id=copy_of_course['id'])
        assert DegreeProgressCategory.find_by_id(
            placeholder_category_id) is None

        # Finally, we create a copy for a separate category and expect success.
        copy_of_course = _copy_course(category_id=category_2.id)
        assert copy_of_course['id'] != course_id
        assert copy_of_course['sectionId'] == section_id
        # Verify 'isCopy' property per course
        degree_json = _api_get_degree(client=client,
                                      degree_check_id=degree_check_id)
        assigned_courses = degree_json['courses']['assigned']
        unassigned_courses = degree_json['courses']['unassigned']
        assert len(assigned_courses)
        assert len(unassigned_courses)
        # Expect no "copies" in the Unassigned set of courses.
        assert True not in [c['isCopy'] for c in unassigned_courses]
        for assigned_course in assigned_courses:
            course_id = assigned_course['id']
            assert assigned_course['isCopy'] == (course_id
                                                 in copied_course_ids)
Esempio n. 21
0
 def _delete_course(c):
     category = DegreeProgressCategory.find_by_id(c.category_id)
     DegreeProgressCourseUnitRequirement.delete(c.id)
     DegreeProgressCourse.delete(c)
     if category and 'Placeholder' in category.category_type:
         DegreeProgressCategory.delete(category_id=category.id)
Esempio n. 22
0
def _get_category_children(category_id, category_type):
    children = DegreeProgressCategory.find_by_parent_category_id(category_id)
    return list(filter(lambda c: c.category_type == category_type, children))
Esempio n. 23
0
def _get_degree_category(category_id):
    category = DegreeProgressCategory.find_by_id(category_id)
    if not category:
        raise ResourceNotFoundError(f'No category found with id={category_id}.')
    return category