def update_course(course_id): course = DegreeProgressCourse.find_by_id(course_id) if course: params = request.get_json() accent_color = normalize_accent_color(get_param(params, 'accentColor')) grade = get_param(params, 'grade') grade = course.grade if grade is None else grade name = get_param(params, 'name') name = course.display_name if name is None else name if name is None: raise BadRequestError('name is required.') note = get_param(params, 'note') # Courses are 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 units = get_param(params, 'units') or None course = DegreeProgressCourse.update( accent_color=accent_color, course_id=course_id, grade=grade, name=name, note=note, unit_requirement_ids=unit_requirement_ids, units=units, ) # 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 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())
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())
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())
def create_course(): params = request.get_json() accent_color = normalize_accent_color(get_param(params, 'accentColor')) degree_check_id = get_param(params, 'degreeCheckId') grade = get_param(params, 'grade') name = get_param(params, 'name') note = get_param(params, 'note') sid = get_param(params, 'sid') value = get_param(request.get_json(), 'unitRequirementIds') unit_requirement_ids = list(filter(None, value.split(','))) if isinstance( value, str) else value units = get_param(params, 'units') if 0 in map(lambda v: len(str(v).strip()) if v else 0, (name, sid)): raise BadRequestError('Missing one or more required parameters') course = DegreeProgressCourse.create( accent_color=accent_color, degree_check_id=degree_check_id, display_name=name, grade=grade, manually_created_at=datetime.now(), manually_created_by=current_user.get_id(), note=note, section_id=None, sid=sid, term_id=None, unit_requirement_ids=unit_requirement_ids or (), units=units, ) # 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())
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
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 create_category(): params = request.get_json() category_type = get_param(params, 'categoryType') course_units_lower = get_param(params, 'unitsLower') course_units_upper = get_param(params, 'unitsUpper') description = get_param(params, 'description') name = get_param(params, 'name') parent_category_id = get_param(params, 'parentCategoryId') position = get_param(params, 'position') template_id = get_param(params, 'templateId') # Categories are 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 if not category_type or not name or not _is_valid_position( position) or not template_id: raise BadRequestError( "Insufficient data: categoryType, name, position and templateId are required.'" ) if category_type in ['Category', 'Campus Requirements' ] and parent_category_id: raise BadRequestError(f'{category_type} cannot have a parent.') if category_type in ('Course Requirement', 'Subcategory') and not parent_category_id: raise BadRequestError( f"The parentCategoryId param is required when categoryType equals '{category_type}'." ) if parent_category_id: parent = _get_degree_category(parent_category_id) parent_type = parent.category_type if parent_type == 'Course Requirement' or ( parent_type == 'Subcategory' and category_type == 'Category'): raise BadRequestError( f'Type {category_type} not allowed, based on parent type: {parent_type}.' ) if position != parent.position: raise BadRequestError( f'Category position ({position}) must match its parent ({parent.position}).' ) category = _create_category( 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, ) # Update updated_at date of top-level record DegreeProgressTemplate.refresh_updated_at(template_id, current_user.get_id()) return tolerant_jsonify(category.to_api_json())
def delete_unit_requirement(unit_requirement_id): unit_requirement = DegreeProgressUnitRequirement.find_by_id(unit_requirement_id) if unit_requirement: DegreeProgressUnitRequirement.delete(unit_requirement_id) # Update updated_at date of top-level record DegreeProgressTemplate.refresh_updated_at(unit_requirement.template_id, current_user.get_id()) return tolerant_jsonify({'message': f'Unit requirement {unit_requirement_id} deleted'}), 200 else: raise ResourceNotFoundError(f'No unit_requirement found with id={unit_requirement_id}.')
def update_degree_note(degree_check_id): params = request.get_json() body = get_param(params, 'body') note = DegreeProgressNote.upsert( body=body, template_id=degree_check_id, updated_by=current_user.get_id(), ) # Update updated_at date of top-level record DegreeProgressTemplate.refresh_updated_at(degree_check_id, current_user.get_id()) return tolerant_jsonify(note.to_api_json())
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)
def update_unit_requirement(unit_requirement_id): params = request.get_json() name = params.get('name') min_units = params.get('minUnits') if not name or not min_units: raise BadRequestError('Unit requirement \'name\' and \'minUnits\' must be provided.') unit_requirement = DegreeProgressUnitRequirement.update( id_=unit_requirement_id, min_units=min_units, name=name, updated_by=current_user.get_id(), ) # Update updated_at date of top-level record DegreeProgressTemplate.refresh_updated_at(unit_requirement.template_id, current_user.get_id()) return tolerant_jsonify(unit_requirement.to_api_json())
def test_edit_degree_note(self, client, fake_auth, mock_note): """Authorized user can edit a degree note.""" template_id = mock_note.template_id original_updated_at = DegreeProgressTemplate.find_by_id( template_id).updated_at fake_auth.login(coe_advisor_read_write_uid) body = 'Stróż pchnął kość w quiz gędźb vel fax myjń.' api_json = self._api_update_degree_note(client, body=body, template_id=template_id) assert api_json['templateId'] assert api_json['body'] == body # Verify update of updated_at assert DegreeProgressTemplate.find_by_id( template_id).updated_at != original_updated_at
def test_delete(self, client, fake_auth, mock_template): """COE advisor can delete degree category.""" fake_auth.login(coe_advisor_read_write_uid) original_updated_at = mock_template.updated_at category = _api_create_category( category_type='Category', client=client, name='Blister in the sun', position=3, template_id=mock_template.id, ) subcategory = _api_create_category( category_type='Subcategory', client=client, name='Gone Daddy Gone', parent_category_id=category['id'], position=3, template_id=mock_template.id, ) course = _api_create_category( category_type='Course Requirement', client=client, name='Blister in the sun', parent_category_id=subcategory['id'], position=3, template_id=mock_template.id, ) category_id = category['id'] assert client.delete(f'/api/degree/category/{category_id}').status_code == 200 # Verify that all were deleted. for object_id in (category_id, subcategory['id'], course['id']): assert client.get(f'/api/degree/category/{object_id}').status_code == 404 # Verify update of updated_at std_commit(allow_test_environment=True) assert DegreeProgressTemplate.find_by_id(mock_template.id).updated_at != original_updated_at
def mock_degree_check(): return DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name=f'Degree check of {coe_student_sid}', student_sid='11667051', )
def test_update_template(self, client, fake_auth): """Authorized user can edit a template.""" user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(user.uid) template = DegreeProgressTemplate.create( advisor_dept_codes=get_dept_codes(user), created_by=user.id, degree_name='Boogie Down Productions', ) for index in (1, 2, 3): DegreeProgressUnitRequirement.create( created_by=user.id, min_units=index, name=f'Unit Requirement #{index}', template_id=template.id, ) updated_name = 'KRS One' api_json = self._api_clone_template(client=client, name=updated_name, template_id=template.id) assert api_json['id'] != template.id assert api_json['name'] == updated_name assert len(api_json['unitRequirements']) == 3
def test_create_nonunique_unit_requirements(self, db): """Requires unit requirements in a single template to have unique names.""" coe_advisor_id = AuthorizedUser.get_id_per_uid(coe_advisor_uid) template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=coe_advisor_id, degree_name='Celtic Studies BA 2021', ) name = 'Celtic Requirements' unit_requirement = DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=70, name=name, template_id=template.id, ) assert unit_requirement with pytest.raises(IntegrityError): DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=53, name=name, template_id=template.id, ) db.session.rollback()
def get_degree_checks(uid): sid = get_sid_by_uid(uid) if sid: return tolerant_jsonify( DegreeProgressTemplate.find_by_sid(student_sid=sid)) else: raise ResourceNotFoundError('Student not found')
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 update_degree_template(template_id): name = request.get_json().get('name') template_id = int(template_id) validate_template_upsert(name=name, template_id=template_id) template = DegreeProgressTemplate.update(name=name, template_id=template_id) return tolerant_jsonify(template.to_api_json())
def test_create_unit_requirement(self): coe_advisor_id = AuthorizedUser.get_id_per_uid(coe_advisor_uid) template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=coe_advisor_id, degree_name='Celtic Studies BA 2021', ) min_units = 45 name = 'Celtic Requirements' unit_requirement = DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=min_units, name=name, template_id=template.id, ) assert unit_requirement assert unit_requirement.__repr__( ) == f"""<DegreeProgressUnitRequirement id={unit_requirement.id}, name={name}, min_units={min_units}, template_id={template.id}, created_at={unit_requirement.created_at}, created_by={coe_advisor_id}, updated_at={unit_requirement.updated_at}, updated_by={coe_advisor_id}>""" assert template.unit_requirements assert len(template.unit_requirements) == 1 assert unit_requirement.template
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 mock_template(): user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) marker = datetime.now().timestamp() return DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock template, made for a mock category ({marker})', )
def test_delete(self, client, fake_auth): """COE advisor can delete template.""" fake_auth.login(coe_advisor_read_write_uid) user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) assert user.degree_progress_permission == 'read_write' template = DegreeProgressTemplate.create(['COENG'], user.id, f'Classical Civilizations, by {user.id}') assert client.delete(f'/api/degree/{template.id}').status_code == 200 assert client.get(f'/api/degree/{template.id}').status_code == 404
def clone_degree_template(template_id, name=None, sid=None): template = DegreeProgressTemplate.find_by_id(template_id) if template_id and not template: raise ResourceNotFoundError( f'No template found with id={template_id}.') if name: validate_template_upsert(name=name, template_id=template_id) created_by = current_user.get_id() return clone(template, created_by, name=name, sid=sid)
def mock_degree_check(): user_id = AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid) parent_template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user_id, degree_name='Zoology BS 2021', ) degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user_id, degree_name='Zoology BS 2021', parent_template_id=parent_template.id, student_sid=coe_student_sid, ) std_commit(allow_test_environment=True) yield degree_check # Avoid polluting other tests DegreeProgressTemplate.delete(degree_check.id) std_commit(allow_test_environment=True)
def create_degree(): params = request.get_json() name = get_param(params, 'name', None) validate_template_upsert(name=name) degree = DegreeProgressTemplate.create( advisor_dept_codes=dept_codes_where_advising(current_user), created_by=current_user.get_id(), degree_name=name, ) return tolerant_jsonify(degree.to_api_json())
def mock_degree_checks(): user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) marker = datetime.now().timestamp() degree_checks = [] for index in (1, 2, 3): parent_template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock template ({marker}_{index})', ) degree_checks.append( DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock degree check ({marker}_{index})', student_sid=coe_student_sid, parent_template_id=parent_template.id, ), ) std_commit(allow_test_environment=True) return degree_checks
def validate_template_upsert(name, template_id=None): if not name: raise BadRequestError('\'name\' is required.') # Name must be unique across non-deleted templates template = DegreeProgressTemplate.find_by_name(name=name, case_insensitive=True) if template and (template_id is None or template_id != template.id): raise BadRequestError( f'A degree named <strong>{name}</strong> already exists. Please choose a different name.' ) return template
def mock_unit_requirement(): template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name='Ursinology 2025', ) return DegreeProgressUnitRequirement.create( created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), min_units=16, name='Aureum Ursi', template_id=template.id, )