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.')
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)
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)
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
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())
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)