diff --git a/.vscode/settings.json b/.vscode/settings.json index 86b58d041..619f65463 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "python.testing.unittestArgs": ["-v", "-s", "./App", "-p", "test_*.py"], "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true + "python.testing.unittestEnabled": true, + "python.analysis.typeCheckingMode": "basic", + "python.analysis.autoImportCompletions": true } diff --git a/App/controllers/__init__.py b/App/controllers/__init__.py index fc0f1af34..4d7978872 100644 --- a/App/controllers/__init__.py +++ b/App/controllers/__init__.py @@ -9,4 +9,5 @@ from .studentCourseHistory import * from .coursePlanCourses import * from .coursesOfferedPerSem import * +from .courseOfferings import * from .coursePlan import * \ No newline at end of file diff --git a/App/controllers/courseOfferings.py b/App/controllers/courseOfferings.py new file mode 100644 index 000000000..2613d1f1d --- /dev/null +++ b/App/controllers/courseOfferings.py @@ -0,0 +1,89 @@ +from App.database import db +from App.models import CourseOfferings + +def createCourseOffering(courseCode, academic_year, semester): + courseCode=courseCode.replace(" ","").upper() #ensure consistent course code format + offering = CourseOfferings.query.filter_by(course_code=courseCode, academic_year=academic_year, semester=semester).first() + if offering: + print("Course offering exists already") + return None + try: + if CourseOfferings.checkAcademicYearFormat(academic_year): + if semester == 1 or semester == 2 or semester == 3: + if CourseOfferings.getCourse(courseCode): + offeredCourse = CourseOfferings(academic_year, semester, courseCode) + if offeredCourse: + db.session.add(offeredCourse) + db.session.commit() + print("Course offering created successfully") + return offeredCourse + else: + print("The new course offering could not be created") + else: + print(f"Invalid course code") + else: + print(f"The semester is invalid. There are 3 semesters: 1, 2, 3") + else: + print(f"Academic year format incorrect. Should be yyyy/yyyy e.g. 2022/2023") + except Exception as e: + db.session.rollback() + print(f"An error occured when trying to add a course to the course offerings: {e}") + +def getAllCourseOfferings(): + return CourseOfferings.query.all() + +def getCourseOfferingsByYear(year): + if CourseOfferings.checkAcademicYearFormat(year): + offerings = CourseOfferings.query.filter_by(academic_year = year).all() + if offerings: + return offerings + else: + print("There are no offerings for the selected year") + else: + print("Academic year format incorrect. Should be yyyy/yyyy e.g. 2022/2023") + +def getCourseOfferingsBySemester(sem): + if sem == 1 or sem == 2 or sem == 3: + offerings = CourseOfferings.query.filter_by(semester = sem).all() + if offerings: + return offerings + else: + print("There are no offerings for the selected semester") + else: + print(f"The semester is invalid. There are 3 semesters: 1, 2, 3") + +def getCourseOfferingsByYearAndSemester(year, sem): + if CourseOfferings.checkAcademicYearFormat(year): + if sem == 1 or sem == 2 or sem == 3: + offerings = CourseOfferings.query.filter_by(semester = sem, academic_year = year).all() + if offerings: + return offerings + else: + print("There are no offerings for the selected year and semester") + else: + print(f"The semester is invalid. There are 3 semesters: 1, 2, 3") + else: + print("Academic year format incorrect. Should be yyyy/yyyy e.g. 2022/2023") + +def deleteCourseOffering(courseCode, academic_year, semester): + courseCode=courseCode.replace(" ","").upper() #ensure consistent course code format + try: + offering = CourseOfferings.query.filter_by(course_code=courseCode, academic_year=academic_year, semester=semester).first() + if offering: + db.session.delete(offering) + db.session.commit() + print("Course offering deleted successfully") + return True + else: + print("The course offering you are trying to delete does not exist") + except Exception as e: + db.session.rollback() + print(f"An error occured when trying to delete the course from the course offerings: {e}") + return False + +def isCourseOffering(courseCode, academic_year, semester): + offering = CourseOfferings.query.filter_by(course_code=courseCode, academic_year=academic_year, semester=semester).first() + if offering: + return True + else: + return False diff --git a/App/controllers/coursePlan.py b/App/controllers/coursePlan.py index 50ffab72a..767b39d51 100644 --- a/App/controllers/coursePlan.py +++ b/App/controllers/coursePlan.py @@ -1,4 +1,4 @@ -from App.models import CoursePlan +from App.models import CoursePlan, CoursePlanCourses from App.database import db from App.controllers import ( get_program_by_id, @@ -8,271 +8,301 @@ getCompletedCourses, createPlanCourse, deleteCourseFromCoursePlan, - get_allCore, - get_allFoun, - get_allElectives, - getCompletedCourseCodes, + getProgramCoursesByType, + getPassedCourseCodes, convertToList, get_all_OfferedCodes, isCourseOffered, programCourses_SortedbyRating, programCourses_SortedbyHighestCredits, get_all_courses_by_planid, - + isCourseOffering, + getCompletedCourseCodes, + get_student_by_id, + getCourseOfferingsByYearAndSemester, + get_all_programCourses ) -def create_CoursePlan(id): - plan = CoursePlan(id) - db.session.add(plan) - db.session.commit() - return plan +def create_CoursePlan(id, year, sem): + try: + plan = CoursePlan.query.filter_by(studentId=id, academic_year=year, semester=sem).first() + if plan: + print("Course plan exists already") + return plan + else: + if CoursePlan.checkAcademicYearFormat(year): + if sem == 1 or sem == 2 or sem == 3: + plan = CoursePlan(id, year, sem) + if plan: + db.session.add(plan) + db.session.commit() + print("New course plan created successfully") + return plan + else: + print("The course plan could not be created") + else: + print(f"The semester is invalid. There are 3 semesters: 1, 2, 3") + else: + print(f"Academic year format incorrect. Should be yyyy/yyyy e.g. 2022/2023") + except Exception as e: + db.session.rollback() + print(f"An error occured when trying to create the course plan: {e}") + +def getCoursePlan(studentid, year, sem): + return CoursePlan.query.filter_by(studentId=studentid, academic_year=year, semester=sem).first() + +def possessPrereqs(studentId, courseCode): + preqs = getPrereqCodes(courseCode) + # for i in preqs: + # print(f'PREREQ: {preqs}') + + completed = getPassedCourseCodes(studentId) -def getCoursePlan(studentid): - return CoursePlan.query.filter_by(studentId=studentid).first() + # for x in completed: + # print(f'COMPLETED: {x}') -def possessPrereqs(Student, course): - preqs = getPrereqCodes(course.courseName) - completed = getCompletedCourseCodes(Student.id) for course in preqs: + # print(course) if course not in completed: return False return True -def addCourseToPlan(Student, courseCode): + +def getPlanCourses(student_id, year, sem): + plan = getCoursePlan(student_id, year, sem) + return get_all_courses_by_planid(plan.planId) + +def numCoursesInPlan(plan_id): + courses = get_all_courses_by_planid(plan_id) + return len(courses) + +def addCourseToPlan(studentId, courseCode, year, sem): course = get_course_by_courseCode(courseCode) if course: offered = isCourseOffered(courseCode) if offered: - haveAllpreqs = possessPrereqs(Student, course) - if haveAllpreqs: - plan = getCoursePlan(Student.id) - if plan: - createPlanCourse(plan.planId, courseCode) - print("Course successfully added to course plan") - return plan + offering = isCourseOffering(courseCode, year, sem) + if offering: + haveAllpreqs = possessPrereqs(studentId, course.courseCode) + if haveAllpreqs: + plan = getCoursePlan(studentId, year, sem) + if plan: + createPlanCourse(plan.planId, courseCode) + print("Course successfully added to course plan") + return plan + else: + plan = create_CoursePlan(studentId, year, sem) + createPlanCourse(plan.planId, courseCode) + print("Plan successfully created and Course was successfully added to course plan") + return plan else: - plan = create_CoursePlan(Student.id) - createPlanCourse(plan.planId, courseCode) - print("Plan successfully created and Course was successfully added to course plan") - return plan + print("Missing prerequisites") + else: + print("Course not being offered for requested semester") else: print("Course is not offered") else: print("Course does not exist") - -def removeCourse(Student, courseCode): - plan=getCoursePlan(Student.id) +def removeCourseFromPlan(studentid, year, sem, courseCode): + plan=getCoursePlan(studentid, year, sem) if plan: deleteCourseFromCoursePlan(plan.planId, courseCode) -def getRemainingCourses(completed, required): - # Check if either 'completed' or 'required' is None - if completed is None or required is None: - return [] # Return an empty list or handle it in a way that makes sense for your application - - completedCodes = [] - for c in completed: - completedCodes.append(c.code) - - remainingCodes = [] - for r in required: - remainingCodes.append(r.code) - - - notCompleted = remainingCodes.copy() - for a in completedCodes: - if a in notCompleted: - notCompleted.remove(a) - - return notCompleted - - -def getRemainingCore(Student): - programme=get_program_by_id(Student.program_id) +def getRemainingCoursesByType(student_id, type): + student=get_student_by_id(student_id) + program=get_program_by_id(student.program_id) remaining = [] - if programme: - reqCore=get_allCore(programme.name) - completed = getCompletedCourses(Student.id) - remaining=getRemainingCourses(completed,reqCore) - - return remaining + if program: + required=getProgramCoursesByType(program.name, type) + completed = getPassedCourseCodes(student_id) + if completed == []: + return required -def getRemainingFoun(Student): - programme = get_program_by_id(Student.program_id) - remaining =[] + remaining = required.copy() + for course in completed: + if course in required: + remaining.remove(course) - if programme: - reqFoun = get_allFoun(programme.name) - completed = getCompletedCourses(Student.id) - remaining=getRemainingCourses(completed,reqFoun) - return remaining - -def getRemainingElec(Student): - programme = get_program_by_id(Student.program_id) # Get the student's program - remaining = [] - - if programme: - reqElec = get_allElectives(programme.name) # Use the instance method to get elective courses - completed = getCompletedCourses(Student.id) - remaining = getRemainingCourses(completed, reqElec) - - return remaining - - -def remElecCredits(Student): - programme = get_program_by_id(Student.program_id) # Get the student's program - completedcourses = getCompletedCourseCodes(Student.id) - requiredCreds = 0 - - if programme: - requiredCreds = programme.elective_credits # Access the elective_credits attribute - elective_courses = get_allElectives(programme.name) # Use the instance method to get elective courses - electCodes = convertToList(elective_courses) - if electCodes: - for code in electCodes: - if code in completedcourses: - c = get_course_by_courseCode(code) # Get course - if c: - requiredCreds = requiredCreds - c.credits # Subtract credits +def getRemainingCreditsByCourseType(student_id, type): + remaining = getRemainingCoursesByType(student_id, type) + student=get_student_by_id(student_id) + program=get_program_by_id(student.program_id) + requiredCreds=0 + if type == 1: + requiredCreds=program.core_credits + elif type == 2: + requiredCreds=program.elective_credits + elif type == 3: + requiredCreds=program.foun_credits + + for code in remaining: + course = get_course_by_courseCode(code) + if course: + requiredCreds = requiredCreds - course.credits + return requiredCreds - -def findAvailable(courseList): - listing=get_all_OfferedCodes() +''' + @getAllAvailableCourseOptions + filters each course being offered in the specified semester by if: + - the student passed it already + - the student's program has it + - the student has all the prerequisites to take it + and compiles a list of courses the student can take +''' +def getAllAvailableCourseOptions(student_id, year, sem): + offerings = getCourseOfferingsByYearAndSemester(year, sem) + offeringCourseCodes = [] + if offerings: + for offering in offerings: + offeringCourseCodes.append(offering.course_code) + + student = get_student_by_id(student_id) + program = get_program_by_id(student.program_id) + programCourses = get_all_programCourses(program.name) + programCourseCodes = [] + for programCourse in programCourses: + programCourseCodes.append(programCourse.code) + + passed = getPassedCourseCodes(student_id) + available=[] - for code in courseList: - if code in listing: - available.append(code) + for code in offeringCourseCodes: + if code not in passed: + if code in programCourseCodes: + if possessPrereqs(student.id, code): + available.append(code) return available #returns an array of course codes +#same function as above but different parameters +def getCourseOptions(student_id, offeringCourseCodes, passed, programCourseCodes): + available=[] -def checkPrereq(Student, recommnded): - validCourses=[] - for course in recommnded: - c = get_course_by_courseCode(course) - satisfied = possessPrereqs(Student, c) - if satisfied: - validCourses.append(c.courseCode) - - return validCourses - -def getTopfive(list): - return list[:5] - -def prioritizeElectives(Student): - #get available electives - electives=findAvailable(getRemainingElec(Student)) - credits=remElecCredits(Student) - courses=[] + for code in offeringCourseCodes: + if code not in passed: + if code in programCourseCodes: + if possessPrereqs(student_id, code): + available.append(code) + + return available +# previous code - seems unnecessary +# def getTopfive(list): +# return list[:5] + +# def prioritizeElectives(Student): +# #get available electives +# electives=findAvailable(getRemainingElec(Student)) +# credits=remElecCredits(Student) +# courses=[] - #select courses to satisfy the programme's credit requirements - for c in electives: - if credits>0: - courses.append(c) - credits = credits - get_credits(c) +# #select courses to satisfy the programme's credit requirements +# for c in electives: +# if credits>0: +# courses.append(c) +# credits = credits - get_credits(c) - #merge available, required core and foundation courses - courses = courses + findAvailable(getRemainingCore(Student)) + findAvailable(getRemainingFoun(Student)) +# #merge available, required core and foundation courses +# courses = courses + findAvailable(getRemainingCore(Student)) + findAvailable(getRemainingFoun(Student)) - courses = checkPrereq(Student,courses) - return getTopfive(courses) +# courses = checkPrereq(Student,courses) +# return getTopfive(courses) -def removeCoursesFromList(list1,list2): - newlist = list2.copy() - for a in list1: - if a in newlist: - newlist.remove(a) - return newlist +# def removeCoursesFromList(list1,list2): +# newlist = list2.copy() +# for a in list1: +# if a in newlist: +# newlist.remove(a) +# return newlist -def easyCourses(Student): - program = get_program_by_id(Student.program_id) - completed = getCompletedCourseCodes(Student.id) - codesSortedbyRating = programCourses_SortedbyRating(Student.program_id) +# def easyCourses(Student): +# program = get_program_by_id(Student.program_id) +# completed = getCompletedCourseCodes(Student.id) +# codesSortedbyRating = programCourses_SortedbyRating(Student.program_id) - coursesToDo = removeCoursesFromList(completed, codesSortedbyRating) +# coursesToDo = removeCoursesFromList(completed, codesSortedbyRating) - elecCredits = remElecCredits(Student) +# elecCredits = remElecCredits(Student) - if elecCredits == 0: - allElectives = convertToList(get_allElectives(program.name)) - coursesToDo = removeCoursesFromList(allElectives, coursesToDo) +# if elecCredits == 0: +# allElectives = convertToList(get_allElectives(program.name)) +# coursesToDo = removeCoursesFromList(allElectives, coursesToDo) - coursesToDo = findAvailable(coursesToDo) +# coursesToDo = findAvailable(coursesToDo) - ableToDo = checkPrereq(Student, coursesToDo) - # for a in ableToDo: - # print(a) +# ableToDo = checkPrereq(Student, coursesToDo) +# # for a in ableToDo: +# # print(a) - return getTopfive(ableToDo) +# return getTopfive(ableToDo) -def fastestGraduation(Student): - program = get_program_by_id(Student.program_id) - sortedCourses = programCourses_SortedbyHighestCredits(Student.program_id) - completed = getCompletedCourseCodes(Student.id) +# def fastestGraduation(Student): +# program = get_program_by_id(Student.program_id) +# sortedCourses = programCourses_SortedbyHighestCredits(Student.program_id) +# completed = getCompletedCourseCodes(Student.id) - coursesToDo = removeCoursesFromList(completed, sortedCourses) +# coursesToDo = removeCoursesFromList(completed, sortedCourses) - elecCredits = remElecCredits(Student) +# elecCredits = remElecCredits(Student) - if elecCredits == 0: - allElectives = convertToList(get_allElectives(program.name)) - coursesToDo = removeCoursesFromList(allElectives, coursesToDo) +# if elecCredits == 0: +# allElectives = convertToList(get_allElectives(program.name)) +# coursesToDo = removeCoursesFromList(allElectives, coursesToDo) - coursesToDo = findAvailable(coursesToDo) - ableToDo = checkPrereq(Student, coursesToDo) +# coursesToDo = findAvailable(coursesToDo) +# ableToDo = checkPrereq(Student, coursesToDo) - return getTopfive(ableToDo) +# return getTopfive(ableToDo) -def commandCall(Student, command): - courses = [] +# def commandCall(Student, command): +# courses = [] - if command == "electives": - courses = prioritizeElectives(Student) +# if command == "electives": +# courses = prioritizeElectives(Student) - elif command == "easy": - courses = easyCourses(Student) +# elif command == "easy": +# courses = easyCourses(Student) - elif command == "fastest": - courses = fastestGraduation(Student) +# elif command == "fastest": +# courses = fastestGraduation(Student) - else: - print("Invalid command") +# else: +# print("Invalid command") - return courses +# return courses -def generator(Student, command): - courses = [] +# def generator(Student, command): +# courses = [] - plan = getCoursePlan(Student.id) +# plan = getCoursePlan(Student.id) - if plan is None: - plan = plan = create_CoursePlan(Student.id) +# if plan is None: +# plan = plan = create_CoursePlan(Student.id) - courses = commandCall(Student, command) +# courses = commandCall(Student, command) - existingPlanCourses = get_all_courses_by_planid(plan.planId) +# existingPlanCourses = get_all_courses_by_planid(plan.planId) - planCourses = [] - for q in existingPlanCourses: - planCourses.append(q.code) +# planCourses = [] +# for q in existingPlanCourses: +# planCourses.append(q.code) - for c in courses: - if c not in planCourses: - createPlanCourse(plan.planId, c) +# for c in courses: +# if c not in planCourses: +# createPlanCourse(plan.planId, c) - return courses \ No newline at end of file +# return courses \ No newline at end of file diff --git a/App/controllers/coursePlanCourses.py b/App/controllers/coursePlanCourses.py index b3c5bc8b1..9b362192e 100644 --- a/App/controllers/coursePlanCourses.py +++ b/App/controllers/coursePlanCourses.py @@ -2,24 +2,31 @@ from App.database import db def createPlanCourse(planid, code): - course = CoursePlanCourses(planid, code) - db.session.add(course) - db.session.commit() + exists = CoursePlanCourses.query.filter_by(planId=planid, code=code).first() + if exists: + print("Course plan course exists already") + return None + try: + course = CoursePlanCourses(planid, code) + db.session.add(course) + db.session.commit() + except Exception as e: + db.session.rollback() + print(f"An error occured when trying to add a course to the course plan: {e}") + def getCourseFromCoursePlan(planid, coursecode): - return CoursePlanCourses.query.filter_by( - planId = planid, - code = coursecode - ).first() + return CoursePlanCourses.query.filter_by(planId = planid, code = coursecode).first() def get_all_courses_by_planid(id): return CoursePlanCourses.query.filter_by(planId=id).all() def deleteCourseFromCoursePlan(planid, coursecode): course = getCourseFromCoursePlan(planid, coursecode) - if course: + try: db.session.delete(course) db.session.commit() print("Course succesfully removed from course plan") - else: + except Exception as e: + db.session.rollback() print("Course is not in Course Plan") diff --git a/App/controllers/courses.py b/App/controllers/courses.py index 3d285f0f1..a2961f93f 100644 --- a/App/controllers/courses.py +++ b/App/controllers/courses.py @@ -1,30 +1,46 @@ -from App.models import Course, Prerequisites -from App.controllers.prerequistes import (create_prereq, get_all_prerequisites) +from App.models import Course +from App.controllers.prerequistes import (create_prereq, get_prerequisites) from App.database import db import json, csv -def createPrerequistes(prereqs, courseName): - for prereq_code in prereqs: - prereq_course = Course.query.filter_by(courseCode=prereq_code).first() +def createPrerequisites(courseCode, preReqCodes): + course = Course.query.filter_by(courseCode = courseCode).first() + + if course: + for prereq in preReqCodes: + prerequisite = Course.query.filter_by(courseCode = prereq).first() + + if prerequisite: + success = create_prereq(course, prereq) + else: + return("Course " + prereq + " not found. Create a new course before adding it as a prerequisite.") - if prereq_course: - create_prereq(prereq_code,courseName) + if (success == False): + return ("One or more prerequisites already exist for this course.") + else: + return ("Unable to add prerequisite. Course not found.") + + return ("Successfully added Prerequisite!") + + + +def create_course(code, name, credits, rating, semester, level, offered, prereqs): -def create_course(code, name, rating, credits, prereqs): already = get_course_by_courseCode(code) if already is None: - course = Course(code, name, rating, credits) - - if prereqs: - createPrerequistes(prereqs, name) - + course = Course(code, name, credits, rating, semester, level, offered) db.session.add(course) db.session.commit() + + if (prereqs[0] != ""): + createPrerequisites(code, prereqs) + return course else: return None + def createCoursesfromFile(file_path): try: with open(file_path, 'r') as file: @@ -34,10 +50,13 @@ def createCoursesfromFile(file_path): courseName = row["courseName"] credits = int(row["numCredits"]) rating = int(row["rating"]) + semester = int(row["semster"]) + level = int(row["level"]) + offered = bool(row["offered"]) prerequisites_codes = row["preReqs"].split(',') + # create_course(courseCode, courseName, rating, credits, prerequisites_codes) + create_course(courseCode, courseName, credits, rating, semester, level, offered, prerequisites_codes) - create_course(courseCode, courseName, rating, credits, prerequisites_codes) - except FileNotFoundError: print("File not found.") @@ -63,10 +82,23 @@ def courses_Sorted_byRating_Objects(): return Course.query.order_by(Course.rating.asc()).all() +def isCourseOffered(courseCode): + course = Course.query.filter_by(courseCode=courseCode).first() + return course.offered + +def get_all_OfferedCodes(): + offered = list_all_courses() + offeredcodes=[] + + for c in offered: + offeredcodes.append(c.courseCode) + + return offeredcodes + def get_prerequisites(code): - course = get_course_by_courseCode(code) - prereqs = get_all_prerequisites(course.courseName) - return prereqs + # course = get_course_by_courseCode(code) + # prereqs = get_all_prerequisites(course.courseName) + return get_prerequisites(code) def get_credits(code): course = get_course_by_courseCode(code) @@ -76,5 +108,7 @@ def get_ratings(code): course = get_course_by_courseCode(code) return course.rating if course else 0 +def list_all_courses(): + return (Course.query.all()) diff --git a/App/controllers/coursesOfferedPerSem.py b/App/controllers/coursesOfferedPerSem.py index d82985c6e..5ab61a731 100644 --- a/App/controllers/coursesOfferedPerSem.py +++ b/App/controllers/coursesOfferedPerSem.py @@ -1,39 +1,39 @@ -from App.models import CoursesOfferedPerSem -from App.controllers import get_course_by_courseCode -from App.database import db +# from App.models import CoursesOfferedPerSem +# from App.controllers import get_course_by_courseCode +# from App.database import db -def addSemesterCourses(courseCode): - course = get_course_by_courseCode(courseCode) - if course: - semCourses = CoursesOfferedPerSem(courseCode) - db.session.add(semCourses) - db.session.commit() - return semCourses - else: - print("Course not found") +# def addSemesterCourses(courseCode): +# course = get_course_by_courseCode(courseCode) +# if course: +# semCourses = CoursesOfferedPerSem(courseCode) +# db.session.add(semCourses) +# db.session.commit() +# return semCourses +# else: +# print("Course not found") -def delete_all_records(): - try: - db.session.query(CoursesOfferedPerSem).delete() - db.session.commit() - print("All records deleted successfully.") +# def delete_all_records(): +# try: +# db.session.query(CoursesOfferedPerSem).delete() +# db.session.commit() +# print("All records deleted successfully.") - except Exception as e: - db.session.rollback() - print(f"An error occurred: {str(e)}") +# except Exception as e: +# db.session.rollback() +# print(f"An error occurred: {str(e)}") -def isCourseOffered(courseCode): - course = CoursesOfferedPerSem.query.filter_by(code=courseCode).first() - return True if course else False +# def isCourseOffered(courseCode): +# course = CoursesOfferedPerSem.query.filter_by(code=courseCode).first() +# return True if course else False -def get_all_courses(): - return CoursesOfferedPerSem.query.all() +# def get_all_courses(): +# return CoursesOfferedPerSem.query.all() -def get_all_OfferedCodes(): - offered = get_all_courses() - offeredcodes=[] +# def get_all_OfferedCodes(): +# offered = get_all_courses() +# offeredcodes=[] - for c in offered: - offeredcodes.append(c.code) +# for c in offered: +# offeredcodes.append(c.code) - return offeredcodes +# return offeredcodes diff --git a/App/controllers/prerequistes.py b/App/controllers/prerequistes.py index 671db33c8..5661ff2c0 100644 --- a/App/controllers/prerequistes.py +++ b/App/controllers/prerequistes.py @@ -1,19 +1,57 @@ from App.models import Prerequisites from App.database import db -def create_prereq(prereqCode, courseName): - prereq = Prerequisites(prereqCode, courseName) - db.session.add(prereq) - db.session.commit() +# def create_prereq(prereqCode, courseName): +# prereq = Prerequisites(prereqCode, courseName) +# db.session.add(prereq) +# db.session.commit() -def get_all_prerequisites(courseName): - return Prerequisites.query.filter(Prerequisites.courseName == courseName).all() +def create_prereq(course, prereqCode): + exists = Prerequisites.query.filter_by(course_code= course.courseCode, prereq_code= prereqCode).first() -def getPrereqCodes(courseName): - prereqs = get_all_prerequisites(courseName) + if exists: + return False + + + new_prereq = Prerequisites(course_code=course.courseCode, prereq_code= prereqCode) + course.prerequisites.append(new_prereq) + + try: + db.session.add(new_prereq) + db.session.commit() + except Exception as e: + db.session.rollback() + print("There was an error...") + print(e) + +# check if a course is a prerequisite +def is_prerequisite(prereq_code): + prereq = Prerequisites.query.filter_by(prereq_code = prereq_code).first() + if prereq: + return True + else: + return False + +#using a prerequisite, find courses that can be done as a result of having it +def get_prereq_course_options(prereq_code): + course_options = Prerequisites.query.filter_by(prereq_code = prereq_code).all() + for c in course_options: + codes = getPrereqCodes(c.course_code) + if len(codes) > 1: + course_options.remove(c) + return course_options + +def get_all_prerequisites(courseCode): + return Prerequisites.query.filter_by(course_code = courseCode).all() + +def get_prerequisites(courseCode): + return Prerequisites.query.filter_by(course_code = courseCode).all() + +def getPrereqCodes(courseCode): + prereqs = get_prerequisites(courseCode) codes = [] for p in prereqs: - codes.append(p.prereq_courseCode) + codes.append(p.prereq_code) return codes \ No newline at end of file diff --git a/App/controllers/program.py b/App/controllers/program.py index 4f2cdc29f..cf072aa0a 100644 --- a/App/controllers/program.py +++ b/App/controllers/program.py @@ -2,13 +2,20 @@ from App.database import db def create_program(name, core, elective, foun): - newProgram = Program(name, core, elective, foun) - db.session.add(newProgram) - print("Program successfully created") - db.session.commit() - return newProgram - - + program = Program.query.filter_by(name=name, core_credits=core, elective_credits=elective, foun_credits=foun).first() + if program is not None: + print("Program exits already") + return None + else: + try: + newProgram = Program(name, core, elective, foun) + db.session.add(newProgram) + print("Program successfully created") + db.session.commit() + return newProgram + except Exception as e: + db.session.rollback() + print(f'Error occured when creating program: {e}') def get_program_by_name(programName): return Program.query.filter_by(name=programName).first() @@ -53,12 +60,16 @@ def get_foun_courses(programName): return courses if program else [] def get_all_courses(programName): - core_courses = get_core_courses(programName) - elective_courses = get_elective_courses(programName) - foun_courses = get_foun_courses(programName) + # core_courses = get_core_courses(programName) + # elective_courses = get_elective_courses(programName) + # foun_courses = get_foun_courses(programName) - all = core_courses + elective_courses + foun_courses - return all + # all = core_courses + elective_courses + foun_courses + # return all + program = get_program_by_name(programName) + return program.courses +def get_all_programs(): + return Program.query.all() diff --git a/App/controllers/programCourses.py b/App/controllers/programCourses.py index 955554d37..f63249f16 100644 --- a/App/controllers/programCourses.py +++ b/App/controllers/programCourses.py @@ -3,55 +3,88 @@ from App.database import db def create_programCourse(programName, code, num): - program = get_program_by_name(programName) - if program: - course = get_course_by_courseCode(code) - if course: - proCourse = ProgramCourses(program.id, code, num) - db.session.add(proCourse) - db.session.commit() - return proCourse + try: + program = get_program_by_name(programName) + if program is not None: + course = get_course_by_courseCode(code) + if course: + proCourse = ProgramCourses.query.filter_by(program_id=program.id, code=code, courseType=num).first() + if proCourse: + print(f'Course already added to program') + else: + proCourse = ProgramCourses(program.id, code, num) + db.session.add(proCourse) + db.session.commit() + return proCourse + else: + print(f'Invalid course code') else: - return "Invalid course code" - else: - return "Invalid program name" + print(f'Invalid program name') + except Exception as e: + db.session.rollback() + print(f'Error occured when trying to add course to program: {e}') def get_all_programCourses(programName): program = get_program_by_name(programName) return ProgramCourses.query.filter(ProgramCourses.program_id == program.id).all() -def get_allCore(programName): - program = get_program_by_name(programName) - core = ProgramCourses.query.filter_by( - program_id=program.id, - courseType=1 - ).all() - return core if core else [] +def get_program_course_by_code(code): + return ProgramCourses.query.filter_by(code = code).first() -def get_allElectives(programName): - program = get_program_by_name(programName) - core = ProgramCourses.query.filter_by( - program_id=program.id, - courseType=2 - ).all() - return core if core else [] -def get_allFoun(programName): +# def get_all_programCourses(program_id): +# program = get_program_by_id(program_id) +# return ProgramCourses.query.filter(ProgramCourses.program_id == program_id).all() + +# new function to get core, elective or foun courses +def getProgramCoursesByType(programName, type): program = get_program_by_name(programName) - core = ProgramCourses.query.filter_by( - program_id=program.id, - courseType=3 - ).all() - return core if core else [] + programCourses = ProgramCourses.query.filter_by(program_id=program.id, courseType=type).all() + # courseCodes = [] + # for programCourse in programCourses: + # courseCodes.append(programCourse.code) + return programCourses + +# repetitive +# def get_allCore(programName): +# program = get_program_by_name(programName) +# core = ProgramCourses.query.filter_by(program_id=program.id, courseType=1).all() +# return core if core else [] + +# def get_allElectives(programName): +# program = get_program_by_name(programName) +# core = ProgramCourses.query.filter_by(program_id=program.id, courseType=2).all() +# return core if core else [] + +# def get_allFoun(programName): +# program = get_program_by_name(programName) +# core = ProgramCourses.query.filter_by(program_id=program.id, courseType=3).all() +# return core if core else [] -def convertToList(list): - codes = [] +def convertToList(programCourses): + courseCodes = [] - for a in list: - codes.append(a.code) + for programCourse in programCourses: + courseCodes.append(programCourse.code) - return codes + return courseCodes +# new function to get courses with a specific rating +def getProgramCoursesByRating(programName, rating): + courses = [] + program = get_program_by_name(programName) + programCourses = ProgramCourses.query.filter_by(program_id=program.id).all() + for programCourse in programCourses: + course = get_course_by_courseCode(programCourse.code) + if course.rating == rating: + courses.append(course) + if courses == []: + print("No courses found in the given program with the given rating") + return courses if courses else [] + + + +# from previous code - not tested def programCourses_SortedbyRating(programid): program = get_program_by_id(programid) programCourses = get_all_programCourses(program.name) @@ -66,6 +99,7 @@ def programCourses_SortedbyRating(programid): return sorted_courses_list +# from previous code - seems unnecessary def programCourses_SortedbyHighestCredits(programid): program = get_program_by_id(programid) programCourses = get_all_programCourses(program.name) @@ -80,3 +114,5 @@ def programCourses_SortedbyHighestCredits(programid): highTolow.append(course.courseCode) return highTolow + + diff --git a/App/controllers/staff.py b/App/controllers/staff.py index c90b3eec1..c806256df 100644 --- a/App/controllers/staff.py +++ b/App/controllers/staff.py @@ -1,7 +1,6 @@ from App.models import Program, Course, Staff from App.database import db - def create_staff(password, staff_id, name): new_staff = Staff(password, staff_id, name) db.session.add(new_staff) diff --git a/App/controllers/student.py b/App/controllers/student.py index e3248e4b3..d681603df 100644 --- a/App/controllers/student.py +++ b/App/controllers/student.py @@ -3,15 +3,24 @@ from App.database import db def create_student(student_id, password, name, programname): - program = get_program_by_name(programname) - if program: - new_student = Student(student_id, password, name, program.id) - db.session.add(new_student) - db.session.commit() - return new_student - print("Student successfully created") - else: - print("Program doesn't exist") + try: + program = get_program_by_name(programname) + if program: + student = Student.query.filter_by(id=student_id, name=name, program_id=program.id).first() + if student is not None: + print("Student exists already") + return None + else: + new_student = Student(student_id, password, name, program.id) + db.session.add(new_student) + db.session.commit() + print("Student successfully created") + return new_student + else: + print("Program doesn't exist") + except Exception as e: + db.session.rollback() + print(f'Error creating student: {e}') def get_student_by_id(ID): return Student.query.filter_by(id=ID).first() diff --git a/App/controllers/studentCourseHistory.py b/App/controllers/studentCourseHistory.py index cb157ee69..0ef795c6a 100644 --- a/App/controllers/studentCourseHistory.py +++ b/App/controllers/studentCourseHistory.py @@ -2,28 +2,71 @@ from App.controllers import (get_student_by_id, get_course_by_courseCode) from App.database import db -def addCoursetoHistory(studentid, code): - student = get_student_by_id(studentid) - if student: - course = get_course_by_courseCode(code) - if course: - completed = StudentCourseHistory(studentid, code) - db.session.add(completed) - db.session.commit() + +def addCoursetoHistory(studentid, code, grade): + try: + exists = StudentCourseHistory.query.filter_by(studentID=studentid, code=code, grade=grade).first() + if exists is not None: + print("Course added to history already") + return None else: - print("Course doesn't exist") - else: - print("Student doesn't exist") - + student = get_student_by_id(studentid) + if student: + course = get_course_by_courseCode(code) + if course: + completed = StudentCourseHistory(studentid, code, grade) + db.session.add(completed) + db.session.commit() + else: + print("Course doesn't exist") + else: + print("Student doesn't exist") + except Exception as e: + db.session.rollback() + print(f'Error adding course to student history: {e}') +''' +def addCoursetoHistory(student_id, course_code, grade): + + try: + student = get_student_by_id(student_id) + course = get_course_by_courseCode(course_code) + + if not student: + raise ValueError("Invalid student ID") + + if not course: + raise ValueError("Invalid course code") + + enrollment = StudentCourseHistory( + student_id = student_id, + course_code = course_code, + grade = grade + ) + + db.session.add(enrollment) + db.session.commit() + except ValueError as e: + print(e) + + return enrollment +''' def getCompletedCourses(id): return StudentCourseHistory.query.filter_by(studentID=id).all() -def getCompletedCourseCodes(id): +def getPassedCourseCodes(id): completed = getCompletedCourses(id) + passed = [] + for course in completed: + if course.grade != "F1" and course.grade != "F2" and course.grade != "F3": + passed.append(course.code) + return passed + +def getCompletedCourseCodes(id): + completed_courses = getCompletedCourses(id) codes = [] - for c in completed: - codes.append(c.code) + for course in completed_courses: + codes.append(course.code) - return codes + return codes diff --git a/App/models/CoursePlanner.py b/App/models/CoursePlanner.py new file mode 100644 index 000000000..3b147611d --- /dev/null +++ b/App/models/CoursePlanner.py @@ -0,0 +1,15 @@ +from typing import List +from App.database import db +from App.models import CoursePlannerStrategy, CoursePlan, student + +# Context +class CoursePlanner: + def __init__(self, strategy: CoursePlannerStrategy): + self._strategy = strategy + + def set_strategy(self, strategy: CoursePlannerStrategy): + self._strategy = strategy + + + def plan_courses(self, data: List[str]): + return self._strategy.planCourses(data) \ No newline at end of file diff --git a/App/models/CoursePlannerStrategy.py b/App/models/CoursePlannerStrategy.py new file mode 100644 index 000000000..9d112f61c --- /dev/null +++ b/App/models/CoursePlannerStrategy.py @@ -0,0 +1,11 @@ +from App.database import db +from App.models import CoursePlan +from typing import List +from abc import ABC, abstractmethod + +# Strategy Interface +class CoursePlannerStrategy(ABC): + @abstractmethod + def planCourses(self, data: List[str]): + pass + diff --git a/App/models/EasyCoursePlanner.py b/App/models/EasyCoursePlanner.py new file mode 100644 index 000000000..1246a3352 --- /dev/null +++ b/App/models/EasyCoursePlanner.py @@ -0,0 +1,167 @@ +from App.database import db +from App.models import CoursePlan, CoursePlannerStrategy, student +from typing import List +from App.controllers import ( + getAllAvailableCourseOptions, + get_student_by_id, + get_all_programCourses, + getCompletedCourses, + getProgramCoursesByRating, + getProgramCoursesByType, + get_course_by_courseCode, + get_program_course_by_code, + create_CoursePlan, + getCourseOfferingsByYearAndSemester, + addCourseToPlan, + getPlanCourses, + numCoursesInPlan + #add as needed +) + +# Concrete Strategy: Easy +class EasyCoursePlanner(CoursePlannerStrategy): + def planCourses(self, data: List[str]): + # implement logic + student_id = data[0] + student = get_student_by_id(student_id) + program = student.associated_program + # program_courses = getProgramCoursesByRating(program.name, 5) + program_courses = get_all_programCourses(program.name) + core_courses = getProgramCoursesByType(program.name, 1) + elec_courses = getProgramCoursesByType(program.name, 2) + foun_courses = getProgramCoursesByType(program.name, 3) + courseHistory = getCompletedCourses(student.id) + completed_core_courses = [] + incomplete_core_courses = [] + complete_electives = [] + complete_foun_courses = [] + + core_credits = 0 + elec_credits = 0 + foun_credits = 0 + + for pastCourse in courseHistory: + for core in core_courses: + if (core.code == pastCourse.code): + completed_core_courses.append(core) + core_credits += get_course_by_courseCode(core.code).credits + for elec in elec_courses: + if (elec.code == pastCourse.code): + complete_electives.append(elec) + elec_credits += get_course_by_courseCode(elec.code).credits + for foun in foun_courses: + if (foun.code == pastCourse.code): + complete_foun_courses.append(foun) + foun_credits += get_course_by_courseCode(foun.code).credits + + for core in core_courses: + if (core not in completed_core_courses): + incomplete_core_courses.append(core) + + + print(f'Program Core Credits: {program.core_credits}') + print(f'Program Elec Credits: {program.elective_credits}') + print(f'Program Foun Credits: {program.foun_credits}') + + print(f'Student Core Credits: {core_credits}') + print(f'Student Elec Credits: {elec_credits}') + print(f'Student Foun Credits: {foun_credits}') + + + remaining_core_courses = int((program.core_credits - core_credits)/3) + remaining_elec_courses = int((program.elective_credits - elec_credits)/3) + remaining_foun_courses = int((program.foun_credits- foun_credits)/3) + + # print(int(remaining_core_courses)) + # print(int(remaining_elec_courses)) + # print(int(remaining_foun_courses)) + + + #assuming the easiest courses get a higher rating, we take courses with ratings of 5 for the easy course plan + # if courses with a rating of 5, the next 'easiest' rating will be selected, and so on + easy_core_courses = [] + easy_elec_courses = [] + easy_foun_courses = [] + + for i in range(5, 0, -1): + easy_program_courses = getProgramCoursesByRating(program.name, i) + + for course in easy_program_courses: + easy= get_program_course_by_code(course.courseCode) + + if (easy.courseType == 1): + easy_core_courses.append(course) + if (easy.courseType == 2): + easy_elec_courses.append(course) + # print(course.courseCode) + if (easy.courseType == 3): + easy_foun_courses.append(course) + + if (len(easy_core_courses)>= remaining_core_courses) and (len(easy_elec_courses)>=remaining_elec_courses) and (len(easy_foun_courses)>= remaining_foun_courses): + break + + + counter = 0 + + # while(True): + + coursePlanList = [] + sem = 1 + year = "2023/2024" + + + + coursePlanList.append(create_CoursePlan(student.id, year, sem)) + offering = getCourseOfferingsByYearAndSemester(year,sem) + offered_courses = [] + + if offering: + for crs in offering: + offered_courses.append(get_course_by_courseCode(crs.course_code)) + + plan = coursePlanList[0] + + for i in range(remaining_core_courses, 0, -1): + if(numCoursesInPlan(plan.planId) >=5) or (core_credits >= program.core_credits): + break + popped = easy_core_courses.pop(0) + if(popped in offered_courses): + print("Adding core course") + core_credits += popped.credits + addCourseToPlan(student_id, popped.courseCode, year, sem) + + + + for i in range(remaining_elec_courses, 0, -1): + if(numCoursesInPlan(plan.planId) >=5): + break + popped = easy_elec_courses.pop(0) + + if(popped in offered_courses): + elec_credits += popped.credits + addCourseToPlan(student_id, popped.courseCode, year, sem) + + + + for i in range(remaining_foun_courses, 0, -1): + if(numCoursesInPlan(plan.planId) >=5): + break + popped = easy_foun_courses.pop(0) + + if(popped in offered_courses): + foun_credits = popped.credits + addCourseToPlan(student_id, popped.courseCode, year, sem) + + + print("\n\n") + print(f'Student Core Credits: {core_credits}') + print(f'Student Elec Credits: {elec_credits}') + print(f'Student Foun Credits: {foun_credits}') + + + plancourses = getPlanCourses(student_id, "2023/2024", sem) + + for i in plancourses: + print(f'{get_course_by_courseCode(i.code).courseCode} {get_course_by_courseCode(i.code).credits}') + + return plancourses \ No newline at end of file diff --git a/App/models/ElectiveCoursePlanner.py b/App/models/ElectiveCoursePlanner.py new file mode 100644 index 000000000..fa0d5dd90 --- /dev/null +++ b/App/models/ElectiveCoursePlanner.py @@ -0,0 +1,13 @@ +from App.database import db +from App.models import CoursePlan, CoursePlannerStrategy +from typing import List +from App.controllers import ( + getAllAvailableCourseOptions + #add as needed +) + +# Concrete Strategy: Elective +class ElectiveCoursePlanner(CoursePlannerStrategy): + def planCourses(self, data: List[str]): + # implement logic + pass \ No newline at end of file diff --git a/App/models/FastCoursePlanner.py b/App/models/FastCoursePlanner.py new file mode 100644 index 000000000..b34b41b0e --- /dev/null +++ b/App/models/FastCoursePlanner.py @@ -0,0 +1,221 @@ +from App.controllers.coursePlan import getPlanCourses +from App.database import db +from App.models import CoursePlan, CoursePlannerStrategy, Course +from typing import List +from App.controllers import ( + get_student_by_id, + get_program_by_id, + getPassedCourseCodes, + getProgramCoursesByType, + create_CoursePlan, + getAllAvailableCourseOptions, + is_prerequisite, + get_course_by_courseCode, + addCourseToPlan, + numCoursesInPlan, + get_program_course_by_code, + getCourseOfferingsByYearAndSemester, + get_all_programCourses, + possessPrereqs, + getCourseOptions + #add as needed +) +from App.models.courses import Course + +# Concrete Strategy: Fast +class FastCoursePlanner(CoursePlannerStrategy): + def planCourses(self, data: List[str]): + student_id = data[0] + current_year = data[1] + semester = data[2] + split_year = current_year.split("/") + next_year = split_year[1] + "/" + str(int(split_year[1])+1) + student = get_student_by_id(student_id) + program = get_program_by_id(student.program_id) + program_core_courses = getProgramCoursesByType(program.name, 1) + program_elec_courses = getProgramCoursesByType(program.name, 2) + program_foun_courses = getProgramCoursesByType(program.name, 3) + passed_courses = getPassedCourseCodes(student_id) + added = [] + course_plan = create_CoursePlan(student_id, current_year, semester) + options = getAllAvailableCourseOptions(student_id, current_year, semester) + if options: + altered = False + for course_code in options: + course = get_course_by_courseCode(course_code) + if numCoursesInPlan(course_plan.planId) < 5: + if course.level == 1 : + addCourseToPlan(student_id, course_code, current_year, semester) + added.append(course_code) + altered = True + # print(f'{course_code} {current_year} 1') + if altered == True: + for a in added: + options.remove(a) + altered = False + for course_code in options: + if numCoursesInPlan(course_plan.planId) < 5: + if is_prerequisite(course_code): + addCourseToPlan(student_id, course_code, current_year, semester) + added.append(course_code) + altered = True + # print(f'{course_code} {current_year} 1') + if altered == True: + for a in added: + options.remove(a) + for course_code in options: + if numCoursesInPlan(course_plan.planId) < 5: + if is_prerequisite(course_code): + addCourseToPlan(student_id, course_code, current_year, semester) + added.append(course_code) + # print(f'{course_code} {current_year} 1') + + codes=[] + plan_courses = getPlanCourses(student_id, current_year, semester) + if plan_courses: + print("Courses in plan:") + for pc in plan_courses: + codes.append(pc.code) + print(pc.code) + return codes + # programCourses = get_all_programCourses(program.name) + # programCourseCodes = [] + # for programCourse in programCourses: + # programCourseCodes.append(programCourse.code) + + # s1_offerings = getCourseOfferingsByYearAndSemester(current_year, semester) + # s1_offeringCourseCodes = [] + # if s1_offerings: + # for offering in s1_offerings: + # s1_offeringCourseCodes.append(offering.course_code) + + # s2_offerings = getCourseOfferingsByYearAndSemester(current_year, semester) + # s2_offeringCourseCodes = [] + # if s2_offerings: + # for offering in s2_offerings: + # s2_offeringCourseCodes.append(offering.course_code) + + + # program_course_plan = [] + # level_1_courses = [] + # level_2_courses = [] + # level_3_courses = [] + + + # options = getCourseOptions(student_id, passed_courses, s1_offeringCourseCodes, programCourseCodes) + # if options: + # for o in options: + # print(o) + # y1s1_course_plan = create_CoursePlan(student_id, current_year, 1) + # for course_code in options: + # course = get_course_by_courseCode(course_code) + # if course.level == 1 : + # if numCoursesInPlan(y1s1_course_plan.planId) <= 5: + # addCourseToPlan(student_id, course.courseCode, current_year, 1) + # passed_courses.append(course_code) + # print(f'{course.courseCode} {current_year} 1') + # else: + # program_course_plan.append(y1s1_course_plan) + # print("y1s1 full") + # options = getCourseOptions(student_id, passed_courses, s1_offeringCourseCodes, programCourseCodes) + + # if len(program_course_plan) == 1: + # split_year = current_year.split("/") + # current_year = split_year[1] + "/" + str(int(split_year[1])+1) + # y2s1_course_plan = create_CoursePlan(student_id, current_year, 1) + # for course_code in options: + # course = get_course_by_courseCode(course_code) + # if course.level == 1 : + # if numCoursesInPlan(y2s1_course_plan.planId) <= 5: + # addCourseToPlan(student_id, course.courseCode, current_year, 1) + # passed_courses.append(course_code) + # print(f'{course.courseCode} {current_year} 1') + # else: + # program_course_plan.append(y2s1_course_plan) + # print("y2s1 full") + # else: + # for course_code in options: + # course = get_course_by_courseCode(course_code) + # if course.level == 1 : + # if numCoursesInPlan(y1s1_course_plan.planId) <= 5: + # addCourseToPlan(student_id, course.courseCode, current_year, 1) + # passed_courses.append(course_code) + # print(f'{course.courseCode} {current_year} 1') + # else: + # program_course_plan.append(y1s1_course_plan) + # print("y1s1 full") + + + + # for course_code in options: + # course = get_course_by_courseCode(course_code) + # if course.level == 1: + # print(course_code) + # level_1_courses.append(course) + # elif course.level == 2: + # level_2_courses.append(course) + # elif course.level == 3: + # level_3_courses.append(course) + + # if len(level_1_courses) != 0: + # + # for course in level_1_courses: + + # level_1_courses.remove(course) + # print(f'{course.courseCode} {current_year} {semester}') + # else: + # program_course_plan.append(y1s1_course_plan) + + # if len(level_1_courses) != 0: + # y1s1_course_plan = create_CoursePlan(student_id, current_year, semester) + # # for course in level_1_courses: + + + # if len(level_1_courses) != 0: + # + # for course in level_1_courses: + # if numCoursesInPlan(y2s1_course_plan.planId) <= 5: + # addCourseToPlan(student_id, course.courseCode, current_year, semester) + # level_1_courses.remove(course) + # print(f'{course.courseCode} {current_year} {semester}') + + # if len(level_1_courses) <= 5: + # for course in level_1_courses: + # course = get_course_by_courseCode(course.courseCode) + # if course.level == 1: + # addCourseToPlan(student_id, course.courseCode, current_year, semester) + # level_1_courses.remove(course) + # print(f'{course.courseCode} {current_year} {semester}') + + # # if() + + + # for course_code in options: + + # addCourseToPlan(student_id, course_code, current_year, semester) + # options.remove(course_code) + # elif is_prerequisite(course_code): + # addCourseToPlan(student_id, course_code, current_year, semester) + # options.remove(course_code) + # print(f'{course_code} {current_year} {semester}') + + # if num_courses > 5 and num_courses <= 10: + # split_year = current_year.split("/") + # current_year = split_year[1] + "/" + str(int(split_year[1])+1) + # addCourseToPlan(student_id, course_code, current_year, semester) + # options.remove(course_code) + # num_courses += 1 + # print(f'{course_code} {current_year} {semester}') + # elif num_courses > 5 and num_courses <= 10: + + # num_courses += 1 + # print(f'{course_code} {current_year} {semester}') + # for course_code + + + + + + + + \ No newline at end of file diff --git a/App/models/__init__.py b/App/models/__init__.py index c8b645206..f99ec3157 100644 --- a/App/models/__init__.py +++ b/App/models/__init__.py @@ -1,11 +1,18 @@ from .user import * +from .prerequisites import * from .courses import * from .program import * from .staff import * from .student import * -from .coursesOfferedPerSem import * +# from .coursesOfferedPerSem import * from .programCourses import * -from .prerequisites import * + from .studentCourseHistory import * from .coursePlanCourses import * from .coursePlan import * +from .courseOfferings import * +from .CoursePlanner import * +from .CoursePlannerStrategy import * +from .EasyCoursePlanner import * +from .ElectiveCoursePlanner import * +from .FastCoursePlanner import * diff --git a/App/models/courseOfferings.py b/App/models/courseOfferings.py new file mode 100644 index 000000000..64fe7d7e6 --- /dev/null +++ b/App/models/courseOfferings.py @@ -0,0 +1,34 @@ +from App.database import db +from .courses import Course + +class CourseOfferings(db.Model): + id = db.Column(db.Integer, primary_key = True) + academic_year = db.Column(db.String, nullable=False) + semester = db.Column(db.Integer, nullable=False) + course_code = db.Column(db.String, nullable=False) + + def __init__(self, academic_year, semester, course_code): + self.academic_year = academic_year + self.semester = semester + self.course_code = course_code + + def get_json(self): + return{ + 'id': self.id, + 'year': self.academic_year, + 'semester': self.semester, + 'course': self.course_code + } + + def getCourse(course_code): + return Course.query.filter_by(courseCode=course_code).first() + + def checkAcademicYearFormat(academic_year): + s = academic_year.split("/") + if len(s) != 2: + return False + elif int(s[0]) != int(s[1])-1: + return False + elif int(s[0]) < 2000: + return False + return True diff --git a/App/models/coursePlan.py b/App/models/coursePlan.py index e3cfaa1e5..f3d84d15a 100644 --- a/App/models/coursePlan.py +++ b/App/models/coursePlan.py @@ -1,19 +1,38 @@ from App.database import db class CoursePlan(db.Model): + __tablename__ = 'courseplan' + + planId=db.Column(db.Integer, primary_key=True) - studentId=db.Column(db.Integer, db.ForeignKey('student.id'), nullable=False) - + academic_year = db.Column(db.String, nullable=False) + semester = db.Column(db.String(1), nullable=False) + studentId=db.Column(db.String(10), db.ForeignKey('student.id'), nullable=False) + student = db.relationship('Student', backref=db.backref('course_plans', uselist=True)) # courses = db.relationship('CoursePlanCourses', backref = 'coursePlan', lazy=True) - - def __init__(self, studentid, ): + + def __init__(self, studentid, year, sem): self.studentId = studentid + self.academic_year = year + self.semester = sem def get_json(self): return{ 'planId': self.planId, + 'year': self.academic_year, + 'semester': self.semester, 'studentId': self.studentId, - } \ No newline at end of file + } + + def checkAcademicYearFormat(academic_year): + s = academic_year.split("/") + if len(s) != 2: + return False + elif int(s[0]) != int(s[1])-1: + return False + elif int(s[0]) < 2000: + return False + return True \ No newline at end of file diff --git a/App/models/coursePlanCourses.py b/App/models/coursePlanCourses.py index e4f982a9f..9304ecaf8 100644 --- a/App/models/coursePlanCourses.py +++ b/App/models/coursePlanCourses.py @@ -1,13 +1,15 @@ -from App.database import db +from App.database import db +from App.models import Course class CoursePlanCourses(db.Model): id = db.Column(db.Integer, primary_key=True) - planId = db.Column(db.ForeignKey('course_plan.planId')) - code = db.Column(db.ForeignKey('course.courseCode')) + planId = db.Column(db.ForeignKey('courseplan.planId')) + code = db.Column(db.ForeignKey('courses.courseCode')) + - # associated_coursePlan = db.relationship('CoursePlan', back_populates='students', overlaps="coursePlan") - # associated_course = db.relationship('Course', back_populates='planIds', overlaps="courses") + # courseplan = db.relationship('CoursePlan', back_populates='courses', overlaps="courseplan") + def __init__(self, plan, courseCode): self.planId = plan @@ -19,4 +21,7 @@ def get_json(self): 'Course Plan ID': self.planId, 'Course': self.code } + + def getCourse(course_code): + return Course.query.filter_by(courseCode=course_code).first() diff --git a/App/models/courses.py b/App/models/courses.py index c198814c0..279a3c3b9 100644 --- a/App/models/courses.py +++ b/App/models/courses.py @@ -1,31 +1,46 @@ +# from App.models import Prerequisites from App.database import db -from App.models import prerequisites +from App.models import Prerequisites + import json class Course(db.Model): + __tablename__ = 'courses' + courseCode = db.Column(db.String(8), primary_key=True) courseName = db.Column(db.String(25)) credits = db.Column(db.Integer) rating = db.Column(db.Integer) + semester = db.Column(db.Integer) + level = db.Column(db.Integer) #the degree year that the course is typically taken + offered = db.Column(db.Boolean) #whether or not the course is currently offered - offered = db.relationship('CoursesOfferedPerSem', backref ='courses', lazy=True) students = db.relationship('StudentCourseHistory', backref='courses', lazy=True) programs = db.relationship('ProgramCourses', backref='courses', lazy=True) - prerequisites = db.relationship('Prerequisites', backref='courses', lazy = True) + coursePlan = db.relationship('CoursePlanCourses', backref='courses', lazy=True) + prerequisites = db.relationship('Prerequisites', foreign_keys=[Prerequisites.course_code], lazy = True) + - # planIds = db.relationship('CoursePlanCourses', backref='courses', lazy=True) - def __init__(self, code, name, rating, credits): + def __init__(self, code, name, credits, rating, semester, level, offered): self.courseCode = code self.courseName = name - self.rating = rating self.credits = credits + self.rating = rating + self.semester = semester + self.level = level + self.offered = offered + def get_json(self): return{ 'Course Code:': self.courseCode, 'Course Name: ': self.courseName, + 'Credits: ': self.credits, + 'Semester: ': self.semester, + 'Level: ': self.level, 'Course Rating: ': self.rating, 'No. of Credits: ': self.credits, + } \ No newline at end of file diff --git a/App/models/coursesOfferedPerSem.py b/App/models/coursesOfferedPerSem.py deleted file mode 100644 index b3dcb1f5d..000000000 --- a/App/models/coursesOfferedPerSem.py +++ /dev/null @@ -1,16 +0,0 @@ -from App.database import db - -class CoursesOfferedPerSem(db.Model): - id = db.Column(db.Integer, primary_key=True) - code = db.Column(db.ForeignKey('course.courseCode')) - - associated_course = db.relationship('Course', back_populates='offered', overlaps="courses") - - def __init__(self, courseCode): - self.code = courseCode - - def get_json(self): - return{ - 'ID:': self.id, - 'Course Code:': self.code - } diff --git a/App/models/department.py b/App/models/department.py new file mode 100644 index 000000000..736f7d7c7 --- /dev/null +++ b/App/models/department.py @@ -0,0 +1,10 @@ +from App.database import db + +class Department(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), nullable=False, unique=True) + + __tablename__ = 'departments' # Explicitly set the table name + + def __repr__(self): + return f"Department('{self.name}')" diff --git a/App/models/prerequisites.py b/App/models/prerequisites.py index 4858c747a..ccb2f9888 100644 --- a/App/models/prerequisites.py +++ b/App/models/prerequisites.py @@ -1,21 +1,20 @@ from App.database import db + + class Prerequisites(db.Model): id = db.Column(db.Integer, primary_key=True) - prereq_courseCode = db.Column(db.ForeignKey('course.courseCode')) - courseName = db.Column(db.String(25)) + course_code = db.Column(db.String(8), db.ForeignKey('courses.courseCode')) + prereq_code = db.Column(db.String(8), db.ForeignKey('courses.courseCode')) + - associated_course = db.relationship('Course', back_populates='prerequisites', overlaps="courses") - - - def __init__(self, prereqCode, nameofCourse): - self.prereq_courseCode = prereqCode - self.courseName = nameofCourse + def __init__(self, course_code, prereq_code): + self.course_code = course_code + self.prereq_code = prereq_code def get_json(self): return{ 'prereq_id': self.id, - 'prerequisite_courseCode': self.prereq_courseCode, - 'prerequisite_course':self.courseName - } \ No newline at end of file + 'prerequisite_courseCode': self.prereq_code, + } \ No newline at end of file diff --git a/App/models/program.py b/App/models/program.py index 322983bbe..fc5f651cc 100644 --- a/App/models/program.py +++ b/App/models/program.py @@ -1,7 +1,7 @@ from App.database import db class Program(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50)) + name = db.Column(db.String(50), nullable=False, unique=True) core_credits = db.Column(db.Integer) elective_credits = db.Column(db.Integer) foun_credits = db.Column(db.Integer) diff --git a/App/models/programCourses.py b/App/models/programCourses.py index c74cf1137..5855cd709 100644 --- a/App/models/programCourses.py +++ b/App/models/programCourses.py @@ -1,9 +1,19 @@ from App.database import db + + + class ProgramCourses(db.Model): + ''' + Course Typing: + 1: core + 2: elective + 3: foun + ''' + __tablename__ ='program_courses' id = db.Column(db.Integer, primary_key=True) program_id = db.Column(db.ForeignKey('program.id')) - code = db.Column(db.ForeignKey('course.courseCode')) + code = db.Column(db.ForeignKey('courses.courseCode')) courseType = db.Column(db.Integer) associated_program = db.relationship('Program', back_populates='courses', overlaps="program") diff --git a/App/models/staff.py b/App/models/staff.py index bb6013857..1bedcbd1d 100644 --- a/App/models/staff.py +++ b/App/models/staff.py @@ -6,6 +6,8 @@ class Staff(User): id = db.Column(db.String(10), db.ForeignKey('user.id'), primary_key=True) name = db.Column(db.String(50)) + # courses = db.relationship('StudentCourseHistory', backref='student', lazy=True) + def __init__(self, password, staff_id, name): super().__init__(staff_id, password) diff --git a/App/models/student.py b/App/models/student.py index a0445ecb1..991ea68e6 100644 --- a/App/models/student.py +++ b/App/models/student.py @@ -8,7 +8,10 @@ class Student(User): associated_program = db.relationship('Program', back_populates='students', overlaps="program") courses = db.relationship('StudentCourseHistory', backref='student', lazy=True) + + + def __init__(self, username, password, name, program_id): super().__init__(username, password) self.id = username @@ -17,8 +20,8 @@ def __init__(self, username, password, name, program_id): def get_json(self): return{'student_id': self.id, - 'name': self.name, + 'name': self.name, 'program' : self.program_id - + } diff --git a/App/models/studentCourseHistory.py b/App/models/studentCourseHistory.py index b5d9e8528..bcef66d1f 100644 --- a/App/models/studentCourseHistory.py +++ b/App/models/studentCourseHistory.py @@ -4,17 +4,20 @@ class StudentCourseHistory(db.Model): __tablename__ = 'studentCourses' id = db.Column(db.Integer, primary_key=True) studentID = db.Column(db.ForeignKey('student.id')) - code = db.Column(db.ForeignKey('course.courseCode')) + code = db.Column(db.String(8), db.ForeignKey('courses.courseCode')) + grade = db.Column(db.String(10)) associated_course = db.relationship('Course', back_populates='students', overlaps="courses") associated_student = db.relationship('Student', back_populates='courses', overlaps="student") - def __init__(self, id, courseCode): - self.studentID = id - self.code = courseCode + def __init__(self, student_id, course_code, grade): + self.studentID = student_id + self.code = course_code + self.grade = grade def get_json(self): return{ 'Program ID': self.id, #is this suppose to be id or program_id alone - 'Course Code': self.code + 'Course Code': self.code, + 'Grade': self.grade } \ No newline at end of file diff --git a/App/views/__init__.py b/App/views/__init__.py index 756127bb3..18eeaa987 100644 --- a/App/views/__init__.py +++ b/App/views/__init__.py @@ -5,6 +5,7 @@ from .auth import auth_views from .staff import staff_views from .student import student_views +from .courses import course_views -views = [user_views, index_views, auth_views, staff_views, student_views] +views = [user_views, index_views, auth_views, staff_views, student_views, course_views] # blueprints must be added to this list \ No newline at end of file diff --git a/App/views/courses.py b/App/views/courses.py new file mode 100644 index 000000000..94dbc7350 --- /dev/null +++ b/App/views/courses.py @@ -0,0 +1,42 @@ +from flask import Blueprint, render_template, jsonify, request, send_from_directory, flash, redirect, url_for +from flask_jwt_extended import jwt_required, current_user as jwt_current_user +from flask_login import current_user, login_required +from App.models import Program, ProgramCourses + +from.index import index_views + +from App.controllers import ( + list_all_courses, + create_course, + createPrerequisites +) + +course_views = Blueprint('course_views', __name__, template_folder='../templates') + + +@course_views.route('/course', methods = ["GET"]) +def list_courses_json(): + course_list = list_all_courses() + course_json = ([course.get_json() for course in course_list]) + + return jsonify(course_json) + +# @course_views.route('/course', methods = ['POST']) +# def new_course(): +# data = request.json + +# course = create_course(data['code'], +# data['name'], +# data['credits'], +# data['rating'], +# data['semester'], +# data['level'], +# data['offered'], +# data['prereqs']) +# return "Successfully Created course!" + +@course_views.route('/course/prereq', methods = ['POST']) +def add_prereq(): + data = request.json + + return createPrerequisites(data['courseCode'], data['prereqCode']) \ No newline at end of file diff --git a/App/views/staff.py b/App/views/staff.py index 91b134725..bbbbf6d05 100644 --- a/App/views/staff.py +++ b/App/views/staff.py @@ -3,6 +3,9 @@ from flask_login import current_user, login_required from App.models import Program, ProgramCourses + + + from.index import index_views from App.controllers import ( @@ -13,10 +16,16 @@ get_all_users, get_all_users_json, jwt_required, - addSemesterCourses, get_all_OfferedCodes, get_all_programCourses, - verify_staff + verify_staff, + createCourseOffering, + deleteCourseOffering, + getAllCourseOfferings, + getCourseOfferingsByYearAndSemester, + get_program_by_name, + list_all_courses, + create_course ) staff_views = Blueprint('staff_views', __name__, template_folder='../templates') @@ -28,8 +37,37 @@ def getOfferedCourses(): if not verify_staff(username): #verify that the user is staff return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 - listing=get_all_OfferedCodes() - return jsonify({'message':'Success', 'offered_courses':listing}), 200 + # listing=get_all_OfferedCodes() + # listing = getAllCourseOfferings() + # offered_json = ([course.get_json() for course in listing]) + # return jsonify(offered_json), 200 + course_list = list_all_courses() + course_json = ([course.get_json() for course in course_list]) + + return jsonify(course_json) + + + + + +@staff_views.route('/staff/offeredCourses/yearsem', methods=['GET']) +@login_required +def getOfferedCoursesYearSem(): + data = request.json + year = data['year'] + sem = data['sem'] + + username=current_user.username + if not verify_staff(username): #verify that the user is staff + return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 + + # listing=get_all_OfferedCodes() + listing = getCourseOfferingsByYearAndSemester(year, sem) + offered_json = ([course.get_json() for course in listing]) + return jsonify(offered_json), 200 + + + @staff_views.route('/staff/program', methods=['POST']) @login_required @@ -44,12 +82,13 @@ def addProgram(): if not verify_staff(username): #verify that the user is staff return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 - #get all programs and check to see if it already exists - programs=Program.query.all() - programNames=[] - for p in programs: - programNames.append(p.name) - if name in programNames: + # get all programs and check to see if it already exists + programs=get_program_by_name(name) + # programNames=[] + # for p in programs: + # programNames.append(p.name) + # if name in programNames: + if programs: return jsonify({'message': 'Program already exists'}), 400 if not isinstance(core, int): @@ -106,25 +145,93 @@ def addProgramRequirements(): return jsonify({'message': response.get_json()}), 200 +# @staff_views.route('/staff/addOfferedCourse', methods=['POST']) +# @login_required +# def addCourse(): +# data=request.json +# courseCode=data['code'] + +# username=current_user.username +# if not verify_staff(username): #verify that the user is staff +# return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 + +# offeredCourses=get_all_OfferedCodes() +# courseCode=courseCode.replace(" ","").upper() #ensure consistent course code format + +# #check if course code is already in the list of offered courses +# if courseCode in offeredCourses: +# return jsonify({'message': f"{courseCode} already exists in the list of offered courses"}), 400 + +# course = addSemesterCourses(courseCode) +# if course: +# return jsonify(course.get_json()), 200 +# else: +# return jsonify({'message': "Course addition unsucessful"}), 400 + + + + + @staff_views.route('/staff/addOfferedCourse', methods=['POST']) @login_required -def addCourse(): +def addCourseOffering(): data=request.json courseCode=data['code'] + year = data['year'] + sem = data['sem'] username=current_user.username if not verify_staff(username): #verify that the user is staff return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 - offeredCourses=get_all_OfferedCodes() + offering = createCourseOffering(courseCode, year, sem) + if offering is not None: + return jsonify(offering.get_json()), 200 + else: + return jsonify({'message': "Course offering creation unsucessful"}), 400 + + + + + +@staff_views.route('/staff/removeCourseOffering', methods=['DELETE']) +@login_required +def removeCourseOffering(): + data=request.json + courseCode=data['code'] + year = data['year'] + sem = data['sem'] courseCode=courseCode.replace(" ","").upper() #ensure consistent course code format - #check if course code is already in the list of offered courses - if courseCode in offeredCourses: - return jsonify({'message': f"{courseCode} already exists in the list of offered courses"}), 400 + username=current_user.username + if not verify_staff(username): #verify that the user is staff + return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 - course = addSemesterCourses(courseCode) - if course: - return jsonify(course.get_json()), 200 + deleted = deleteCourseOffering(courseCode, year, sem) + if deleted: + return jsonify({'message:': "Course offering deletion succesful"}), 200 else: - return jsonify({'message': "Course addition unsucessful"}), 400 \ No newline at end of file + return jsonify({'message': "Course offering deletion unsucessful"}), 404 + + + + +@staff_views.route('/staff/createcourse', methods=['POST']) +@login_required +def new_course(): + data = request.json + + username=current_user.username + if not verify_staff(username): #verify that the user is staff + return jsonify({'message': 'You are unauthorized to perform this action. Please login with Staff credentials.'}), 401 + + + course = create_course(data['code'], + data['name'], + data['credits'], + data['rating'], + data['semester'], + data['level'], + data['offered'], + data['prereqs']) + return jsonify("Successfully Created course!") \ No newline at end of file diff --git a/App/views/student.py b/App/views/student.py index 927928cc5..67001733c 100644 --- a/App/views/student.py +++ b/App/views/student.py @@ -3,6 +3,13 @@ from flask_login import current_user, login_required from.index import index_views + +from App.models.EasyCoursePlanner import EasyCoursePlanner +from App.models.FastCoursePlanner import FastCoursePlanner +from App.models.ElectiveCoursePlanner import ElectiveCoursePlanner +from App.models.CoursePlanner import CoursePlanner +from App.models.programCourses import ProgramCourses + from App.controllers import ( create_user, jwt_authenticate, @@ -15,9 +22,11 @@ get_course_by_courseCode, addCoursetoHistory, getCompletedCourseCodes, - generator, + # generator, - addCourseToPlan, - verify_student + verify_student, + getCompletedCourses + ) student_views = Blueprint('student_views', __name__, template_folder='../templates') @@ -51,6 +60,7 @@ def create_student_route(): def add_course_to_student_route(): student_id = request.json['student_id'] course_code = request.json['course_code'] + grade = request.json['grade'] username=current_user.username if not verify_student(username): #verify that the user is logged in @@ -73,40 +83,99 @@ def add_course_to_student_route(): if course_code in completed_courses: return jsonify({'Error': 'Course already completed'}), 400 - addCoursetoHistory(student_id, course_code) + # addCoursetoHistory(student_id, course_code) + addCoursetoHistory(student_id, course_code, grade) return jsonify({'Success!': f"Course {course_code} added to student {student_id}'s course history"}), 200 + +@student_views.route('/student/get_history', methods=['GET']) +@login_required +def get_student_course_history(): + username=current_user.username + if not verify_student(username): #verify that the user is logged in + return jsonify({'message': 'You are unauthorized to perform this action. Please login with Student credentials.'}), 401 + + student_id = request.json['student_id'] + completed = getCompletedCourses(student_id) + completed_json = ([course.get_json() for course in completed]) + # course_json = ([course.get_json() for course in course_list]) + + return jsonify(completed_json) + + + + + + + ##Add course plan @student_views.route('/student/create_student_plan', methods=['POST']) @login_required def create_student_plan_route(): student_id = request.json['student_id'] - command = request.json['command'] + strategy = request.json['command'] + year = request.json['year'] username=current_user.username if not verify_student(username): #verify that the student is logged in return jsonify({'message': 'You are unauthorized to perform this action. Please login with Student credentials.'}), 401 - student = get_student_by_id(student_id) +# def generate_plan(student_id, strategy, year): + if strategy.lower() == "easy": + strategy = EasyCoursePlanner() + params = [student_id] + elif strategy.lower() == "elective": + electives = input("Please enter the electives you wish to pursue (e.g. COMP3606, COMP3607): ") + strategy = ElectiveCoursePlanner() + params = [student_id, year, electives] + elif strategy.lower() == "fast": + strategy = FastCoursePlanner() + params = [student_id, year] + else: + print("Invalid planning strategy. Please choose 'easy', 'fast' or 'elective'.") + return + + context = CoursePlanner(strategy) + result = context.plan_courses(params) + + course_json = ([course.get_json() for course in result]) - if not student: - return jsonify({'Error': 'Student not found'}), 400 - valid_command = ["electives", "easy", "fastest"] - if command in valid_command: - courses = generator(student, command) - return jsonify({'Success!': f"{command} plan added to student {student_id} ", "courses" : courses}), 200 - course = get_course_by_courseCode(command) - if course: - addCourseToPlan(student, command) - return jsonify({'Success!': f"Course {command} added to student {student_id} plan"}), 200 + + + + + + + + + + + + - return jsonify("Invalid command. Please enter 'electives', 'easy', 'fastest', or a valid course code."), 400 + # student = get_student_by_id(student_id) + # if not student: + # return jsonify({'Error': 'Student not found'}), 400 + + # valid_command = ["electives", "easy", "fastest"] + # if command in valid_command: + # courses = generator(student, command) + # return jsonify({'Success!': f"{command} plan added to student {student_id} ", "courses" : courses}), 200 + # course = get_course_by_courseCode(command) + # if course: + # addCourseToPlan(student, command) + # return jsonify({'Success!': f"Course {command} added to student {student_id} plan"}), 200 + # return jsonify("Invalid command. Please enter 'electives', 'easy', 'fastest', or a valid course code."), 400 + + + + diff --git a/readme.md b/readme.md index 6797b4469..9b0266ea5 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/uwidcit/flaskmvc) +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Ara-Ara-Arabiansss/CodeCraftersProject-Refactored) Deploy to Render diff --git a/testData/courseData.csv b/testData/courseData.csv index 9ef96328e..0036da018 100644 --- a/testData/courseData.csv +++ b/testData/courseData.csv @@ -1,36 +1,36 @@ -courseCode,courseName,numCredits,rating,preReqs -COMP1600,Introduction to Computing Concepts,3,4, -COMP1601,Programming 1,3,1, -COMP1602,Programming 2,3,4, -COMP1603,Programming 3,3,3, -COMP1604,Mathematics for Computing,3,2, -INFO1600, Introduction to Information Technology Concepts,3,1, -INFO1601,Introduction to WWW Programming,3,1, -MATH1115,Fundamental Mathematics for the General Sciences 1,3,1, -COMP2611,Data Structures,3,5,COMP1602 -COMP2601,Computer Architecture,3,5,COMP1600 -MATH2250,Industrial Statistics,3,4, -COMP3605,Introduction to Data Analytics,3,3,"COMP2611,MATH2250" -COMP3610,Big Data Analytics,3,5,COMP3605 -COMP2602,Computer Networks,3,1,COMP1600 -COMP2603,Object-Oriented Programming 1,3,1,COMP1603 -COMP2604,Operating Systems,3,5,COMP1600 -COMP2605,Enterprise Database Systems,3,2,COMP1602 -COMP2606,Software Engineering 1,3,3,COMP1602 -INFO2602,Web Programming and Technologies 1,3,3,INFO1601 -INFO2604,Information Systems Security,3,3,COMP1602 -COMP3601,Design and Analysis of Algorithms,3,5,COMP2611 -COMP3602,Theory of Computing,3,5,COMP1604 -COMP3603,Human-Computer Interaction,3,1,COMP2606 -INFO3604,Project,3,5,"INFO2600,COMP2606" -FOUN1101,Caribbean Civilization,3,1, -FOUN1105,Scientific and Technical Writing,3,1, -FOUN1301,"Law, Governance, Economy and Society",3,1, -COMP3606,Wireless and Mobile Computing,3,3,"COMP2602,INFO2601" -COMP3607,Object-Oriented Programming II,3,2,COMP2603 -COMP3608,Intelligent Systems,3,4,"COMP2611,MATH2250" -COMP3609,Game Programming,3,1,"COMP2603,COMP2606" -COMP3611,Modelling and Simulation,3,5,MATH2250 -COMP3612,Special Topics in Computer,3,5,"COMP2611,COMP2603" -COMP3613,Software Engineering 2,3,5,COMP2606 -INFO2606, Internship, 6, 1, +courseCode,courseName,numCredits,rating,semster,level,offered,preReqs +COMP1600,Introduction to Computing Concepts,3,4,1,1,TRUE, +COMP1601,Programming 1,3,1,1,1,TRUE, +COMP1602,Programming 2,3,4,2,1,TRUE, +COMP1603,Programming 3,3,3,2,1,TRUE, +COMP1604,Mathematics for Computing,3,2,2,1,TRUE, +INFO1600, Introduction to Information Technology Concepts,3,1,1,1,TRUE, +INFO1601,Introduction to WWW Programming,3,1,2,1,TRUE, +MATH1115,Fundamental Mathematics for the General Sciences 1,3,1,1,1,TRUE, +COMP2611,Data Structures,3,5,1,2,TRUE,COMP1602 +COMP2601,Computer Architecture,3,5,1,2,TRUE,COMP1600 +MATH2250,Industrial Statistics,3,4,1,2,TRUE, +COMP3605,Introduction to Data Analytics,3,3,1,3,TRUE,"COMP2611,MATH2250" +COMP3610,Big Data Analytics,3,5,2,3,TRUE,COMP3605 +COMP2602,Computer Networks,3,1,1,2,TRUE,COMP1600 +COMP2603,Object-Oriented Programming 1,3,1,1,2,TRUE,COMP1603 +COMP2604,Operating Systems,3,5,1,2,TRUE,COMP1600 +COMP2605,Enterprise Database Systems,3,2,1,2,TRUE,COMP1602 +COMP2606,Software Engineering 1,3,3,1,2,TRUE,COMP1602 +INFO2602,Web Programming and Technologies 1,3,3,1,2,TRUE,INFO1601 +INFO2604,Information Systems Security,3,3,2,2,TRUE,COMP1602 +COMP3601,Design and Analysis of Algorithms,3,5,1,3,TRUE,COMP2611 +COMP3602,Theory of Computing,3,5,1,3,TRUE,COMP1604 +COMP3603,Human-Computer Interaction,3,1,1,3,TRUE,COMP2606 +INFO3604,Project,3,5,2,3,TRUE,"INFO2600,COMP2606" +FOUN1101,Caribbean Civilization,3,1,1,1,TRUE, +FOUN1105,Scientific and Technical Writing,3,1,1,1,TRUE, +FOUN1301,"Law, Governance, Economy and Society",3,1,2,1,TRUE, +COMP3606,Wireless and Mobile Computing,3,3,1,3,TRUE,"COMP2602,INFO2601" +COMP3607,Object-Oriented Programming II,3,2,1,3,TRUE,COMP2603 +COMP3608,Intelligent Systems,3,4,2,3,TRUE,"COMP2611,MATH2250" +COMP3609,Game Programming,3,1,2,3,TRUE,"COMP2603,COMP2606" +COMP3611,Modelling and Simulation,3,5,2,3,TRUE,MATH2250 +COMP3612,Special Topics in Computer,3,5,2,3,TRUE,"COMP2611,COMP2603" +COMP3613,Software Engineering 2,3,5,2,3,TRUE,COMP2606 +INFO2606, Internship,6,1,3,2,TRUE, diff --git a/testData/test.txt b/testData/test.txt index 427440bff..f67d7575d 100644 --- a/testData/test.txt +++ b/testData/test.txt @@ -1,4 +1,4 @@ -Computer Science Major +Testing COMP1600,1 COMP1601,1 COMP1602,1 @@ -6,20 +6,20 @@ COMP1603,1 COMP1604,1 INFO1600,1 INFO1601,1 -MATH1115,1 +MATH1115,2 COMP2601,1 COMP2602,1 COMP2603,1 COMP2604,1 -COMP2605,1 -COMP2606,1 +COMP2605,2 +COMP2606,2 COMP2611,1 COMP3601,1 COMP3602,1 COMP3603,1 INFO2602,1 -INFO2604,1 -INFO3604,1 +INFO2604,2 +INFO3604,2 MATH2250,1 COMP3605,2 COMP3606,2 diff --git a/wsgi.py b/wsgi.py index be2229779..8426753e1 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,9 +1,18 @@ + + import click, pytest, sys +import random import csv from flask import Flask -from App.controllers.student import create_student from flask.cli import with_appcontext, AppGroup + +from App.models.EasyCoursePlanner import EasyCoursePlanner +from App.models.FastCoursePlanner import FastCoursePlanner +from App.models.ElectiveCoursePlanner import ElectiveCoursePlanner +from App.models.CoursePlanner import CoursePlanner +from App.models.programCourses import ProgramCourses + from App.database import db, get_migrate from App.main import create_app from App.controllers import ( @@ -18,23 +27,25 @@ get_prerequisites, get_all_courses, create_programCourse, - addSemesterCourses, + createCourseOffering, + deleteCourseOffering, + getCourseOfferingsByYearAndSemester, create_student, create_staff, get_program_by_name, get_all_programCourses, addCoursetoHistory, - getCompletedCourseCodes, - get_allCore, + getCompletedCourses, addCourseToPlan, get_student_by_id, - generator + list_all_courses, + get_all_programs, + getProgramCoursesByType, + getCoursePlan, + getPlanCourses, + create_CoursePlan ) -test1 = ["COMP1600", "COMP1601", "COMP1602", "COMP1603", "COMP1604", "MATH1115", "INFO1600", "INFO1601", "FOUN1101", "FOUN1105", "FOUN1301", "COMP3605", "COMP3606", "COMP3607", "COMP3608",] - -file_path = "testData/test.txt" - # This commands file allow you to create convenient CLI commands for testing controllers @@ -49,15 +60,23 @@ def initialize(): db.create_all() create_user('bob', 'bobpass') createCoursesfromFile('testData/courseData.csv') - create_program("Computer Science Major", 69, 15, 9) - create_student(816, "boo", "testing", "Computer Science Major") - create_staff("adminpass","999", "admin") + create_program("Testing", 30, 54, 9) + create_student(816, "testpass", "test", "Testing") + create_student(8160, "pass", "blank", "Testing") + create_staff("staffpass","999", "staff") + + test1 = ["COMP1600", "COMP1601", "COMP1602", "COMP1603", "COMP1604", "MATH1115", "INFO1600", "INFO1601", "FOUN1101", "FOUN1105", "FOUN1301", "COMP3605", "COMP3606", "COMP3607", "COMP3608",] for c in test1: - addCoursetoHistory(816, c) + # grade = random.choice(['A', 'B', 'C', 'F1', 'F2', 'F3']) + grade = 'A' + addCoursetoHistory(816, c, grade) print('Student course history updated') + # add courses to program + file_path = "testData/test.txt" with open(file_path, 'r') as file: + programName="" for i, line in enumerate(file): line = line.strip() if i ==0: @@ -65,18 +84,32 @@ def initialize(): else: course = line.split(',') create_programCourse(programName, course[0],int(course[1])) + print('Program courses updated') - file_path1='testData/test2.txt' - with open(file_path1, 'r') as file: - for i, line in enumerate(file): - line = line.strip() - addSemesterCourses(line) - - - + # # add course offerings for semester 1 of the year 2023/2024 + # file_path1='testData/test2.txt' + # with open(file_path1, 'r') as file: + # for i, line in enumerate(file): + # line = line.strip() + # createCourseOffering(line, "2023/2024", 1) - print('database intialized') + file_path = "testData/courseData.csv" + try: + with open(file_path, 'r') as file: + csv_reader = csv.DictReader(file) + for row in csv_reader: + courseCode = row["courseCode"] + semester = int(row["semster"]) + createCourseOffering(courseCode, "2023/2024", semester) + print('Course offerings for 2023/2024 created') + except FileNotFoundError: + print("File not found.") + except Exception as e: + print(f"An error occurred: {e}") + return False + + print('Database initialized') ''' User Commands ''' @@ -105,10 +138,8 @@ def list_user_command(format): else: print(get_all_users_json()) -app.cli.add_command(user_cli) # add the group to the cli - -# ... (previous code remains the same) +app.cli.add_command(user_cli) # add the group to the cli ''' Student @@ -127,29 +158,52 @@ def create_student_command(student_id, password, name, programname): @student_cli.command("addCourse", help="Student adds a completed course to their history") @click.argument("student_id", type=str) @click.argument("code", type=str) -def addCourse(student_id, code): - addCoursetoHistory(student_id, code) +@click.argument("grade", type=str) +@click.pass_context +def addCourse(ctx, student_id, code, grade): + addCoursetoHistory(student_id, code, grade) + +addCourse.params = [ + click.Argument(["student_id"], type=str), + click.Argument(["code"], type=str), + click.Argument(["grade"], type=str) +] + + @student_cli.command("getCompleted", help="Get all of a student completed courses") @click.argument("student_id", type=str) def completed(student_id): - comp = getCompletedCourseCodes(student_id) + comp = getCompletedCourses(student_id) for c in comp: - print(f'{c}') + print(f'Course Code: {c.code}, Grade: {c.grade}') + + +# @student_cli.command("addCourseToPlan", help="Adds a course to a student's course plan") +# def courseToPlan(): +# student = get_student_by_id("816") +# addCourseToPlan(student, "COMP2611") -@student_cli.command("addCourseToPlan", help="Adds a course to a student's course plan") -def courseToPlan(): - student = get_student_by_id("816") - addCourseToPlan(student, "COMP2611") -@student_cli.command("generate", help="Generates a course plan based on what they request") +# @student_cli.command("generate", help="Generates a course plan based on what they request") +# @click.argument("student_id", type=str) +# @click.argument("command", type=str) +# def generatePlan(student_id, command): +# student = get_student_by_id(student_id) +# courses = generator(student, command) +# for c in courses: +# print(c) + +@student_cli.command("getcourseplan", help= "Get the specified courseplan for the student") @click.argument("student_id", type=str) -@click.argument("command", type=str) -def generatePlan(student_id, command): - student = get_student_by_id(student_id) - courses = generator(student, command) - for c in courses: - print(c) +@click.argument("academic_year", type=str) +@click.argument("semester", type=str) +def getcourseplan(student_id, academic_year, semester): + plan = getCoursePlan(student_id, academic_year, semester) + if plan: + print(f'{plan.get_json()}') + else: + print(f'Course plan requested not found') app.cli.add_command(student_cli) @@ -158,38 +212,49 @@ def generatePlan(student_id, command): Staff Commands ''' staff_cli = AppGroup('staff',help='testing staff commands') + @staff_cli.command("create",help="create staff") @click.argument("id", type=str) @click.argument("password", type=str) @click.argument("name", type=str) def create_staff_command(id, password, name): - newstaff=create_staff(password,id, name) + newstaff=create_staff(password, id, name) print(f'Staff {newstaff.name} created') -@staff_cli.command("addprogram",help='testing add program feature') -@click.argument("name", type=str) -@click.argument("core", type=int) -@click.argument("elective", type=int) -@click.argument("foun", type=int) -def create_program_command(name,core,elective,foun): - newprogram=create_program(name,core,elective,foun) - print(f'{newprogram.get_json()}') - -@staff_cli.command("addprogramcourse",help='testing add program feature') -@click.argument("name", type=str) +# @staff_cli.command("addprogram",help='testing add program feature') +# @click.argument("name", type=str) +# @click.argument("core", type=int) +# @click.argument("elective", type=int) +# @click.argument("foun", type=int) +# def create_program_command(name,core,elective,foun): +# newprogram=create_program(name,core,elective,foun) +# if newprogram: +# print(f'{newprogram.get_json()}') + +@staff_cli.command("addprogramcourse",help='testing add program course feature') +@click.argument("programname", type=str) +@click.argument("coursecode", type=str) +@click.argument("type", type=int) +def add_program_requirements(programname,coursecode,type): + response=create_programCourse(programname, coursecode, type) + if response is ProgramCourses: + print(f'{response.get_json()}') + +@staff_cli.command("addcourseoffering",help='testing add courses offering feature') @click.argument("code", type=str) -@click.argument("num", type=int) -def add_program_requirements(name,code,num): - response=create_programCourse(name, code, num) - print(response) - -@staff_cli.command("addofferedcourse",help='testing add courses offered feature') +@click.argument("year", type=str) +@click.argument("sem", type=int) +def add_course_offering(code, year, sem): + offering=createCourseOffering(code, year, sem) + if offering: + print(f'{offering.get_json()}') + +@staff_cli.command("removecourseoffering",help='testing remove courses offering feature') @click.argument("code", type=str) -def add_offered_course(code): - course=addSemesterCourses(code) - if course: - print(f'Course details: {course}') - +@click.argument("year", type=str) +@click.argument("sem", type=int) +def remove_course_offering(code, year, sem): + deleteCourseOffering(code, year, sem) app.cli.add_command(staff_cli) @@ -223,7 +288,7 @@ def courses_tests_command(type): @test.command("coursePlan", help="Run Course Plan tests") @click.argument("type", default="all") -def courses_tests_command(type): +def course_plan_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/coursePlan.py::CoursePlanUnitTests"])) elif type == "int": @@ -234,7 +299,7 @@ def courses_tests_command(type): #CoursesOfferedPerSemUnitTests @test.command("coursesOffered", help="Run Courses Offered Per Sem tests") @click.argument("type", default="all") -def courses_tests_command(type): +def courses_offered_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/coursesOfferedPerSem.py::CoursesOfferedPerSemUnitTests"])) elif type == "int": @@ -245,7 +310,7 @@ def courses_tests_command(type): @test.command("program", help="Run Program tests") @click.argument("type", default="all") -def courses_tests_command(type): +def program_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/program.py::ProgramUnitTests"])) elif type == "int": @@ -256,7 +321,7 @@ def courses_tests_command(type): @test.command("staff", help="Run Staff tests") @click.argument("type", default="all") -def courses_tests_command(type): +def staff_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/staff.py::StaffUnitTests"])) elif type == "int": @@ -266,7 +331,7 @@ def courses_tests_command(type): @test.command("student", help="Run Program tests") @click.argument("type", default="all") -def courses_tests_command(type): +def student_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/student.py::StudentUnitTest"])) elif type == "int": @@ -276,7 +341,7 @@ def courses_tests_command(type): @test.command("studentCH", help="Run Student Course History tests") @click.argument("type", default="all") -def courses_tests_command(type): +def student_history_tests_command(type): if type == "unit": sys.exit(pytest.main(["App/tests/studentCourseHistory.py::CourseHistoryUnitTest"])) elif type == "int": @@ -301,16 +366,24 @@ def courses_tests_command(type): @click.argument('elective', type=int) @click.argument('foun', type=int) def create_program_command(name, core, elective, foun): - program = create_program(name, core, elective, foun) + newprogram=create_program(name,core,elective,foun) + if newprogram: + print(f'{newprogram.get_json()}') +@program.command("getprograms", help='Get all programs') +def get_programs(): + programs=get_all_programs() + if programs: + for program in programs: + print(f'{program.get_json()}') @program.command('core', help='Get program core courses') -#@click.argument('programname', type=str) -def get_CoreCourses(): - create_programCourse("Computer Science Major", "COMP2611", 1) - create_programCourse("Computer Science Major", "COMP3605", 1) - create_programCourse("Computer Science Major", "COMP3610", 2) - core = get_allCore("Computer Science Major") +@click.argument('programname', type=str) +def get_CoreCourses(programname): + # create_programCourse("Computer Science Major", "COMP2611", 1) + # create_programCourse("Computer Science Major", "COMP3605", 1) + # create_programCourse("Computer Science Major", "COMP3610", 2) + core = getProgramCoursesByType(programname, 1) for c in core: print({c.code}) @@ -324,7 +397,8 @@ def get_CoreCredits(programname): @click.argument('programname', type=str) def allCourses(programname): all = get_all_courses(programname) - print(f'All courses are = {all}') if credits else print(f'error') + for course in all: + print(course.code) @program.command('getprogram', help='Get a program by name') @click.argument('programname', type=str) @@ -337,14 +411,16 @@ def getProgram(programname): @click.argument('code', type=str) @click.argument('type', type=int) def addProgramCourse(programname, code, type): - create_programCourse(programname, code, type) + c = create_programCourse(programname, code, type) + if c: + print(f'Course:{c.code} Type:{c.courseType}') @program.command('getprogramCourses', help='Get all courses of a program') @click.argument('programname', type=str) -def addProgramCourse(programname): +def getProgramCourses(programname): courses = get_all_programCourses(programname) for c in courses: - print(f'{c.code}') + print(f'{c.code} {c.courseType}') app.cli.add_command(program) ################################################################# @@ -352,8 +428,7 @@ def addProgramCourse(programname): ''' Course Commands ''' - -course = AppGroup('course', help = 'Program object commands') +course = AppGroup('course', help = 'Course object related commands') # @course.command('create', help='Create a new course') # @click.argument('file_path') @@ -361,6 +436,9 @@ def addProgramCourse(programname): # newcourse = create_course(file_path) # print(f'Course created with course code "{newcourse.courseCode}", name "{newcourse.courseName}", credits "{newcourse.credits}", ratings "{newcourse.rating}" and prerequites "{newcourse.prerequisites}"') +@course.command('all', help = 'get all the courses in the db') +def list_courses(): + print(list_all_courses()) @course.command('prereqs', help='Create a new course') @click.argument('code', type=str) @@ -372,31 +450,137 @@ def create_course_command(code): @click.argument('code', type=str) def get_course(code): course = get_course_by_courseCode(code) - course_json = course.get_json() - print(f'{course_json}') if course else print(f'error') + print(f'{course.get_json()}') if course else print(f'error') @course.command('getprereqs', help='Get all prerequistes for a course') @click.argument('code', type=str) -def get_course(code): +def get_course_prerequisites(code): prereqs = get_prerequisites(code) for r in prereqs: - print(f'{r.prereq_courseCode}') + print(f'{r.prereq_code}') + +# @course.command('nextsem', help='Add a course to offered courses') +# @click.argument('code', type=str) +# def add_course(code): +# course = addSemesterCourses(code) +# print(f'Course Name: {course.courseName}') if course else print(f'error') + +# @course.command('getNextSemCourses', help='Get all the courses offered next semester') +# def allSemCourses(): +# courses = get_all_OfferedCodes() + +# if courses: +# for c in courses: +# print({c}) +# else: +# print("empty") + +@course.command("offering", help='Get courses offerings for specified year and semester') +@click.argument("year", type=str) +@click.argument("sem", type=int) +def get_course_offering(year, sem): + offerings=getCourseOfferingsByYearAndSemester(year, sem) + if offerings: + for offering in offerings: + print(f'{offering.get_json()}') + +app.cli.add_command(course) -@course.command('nextsem', help='Add a course to offered courses') -@click.argument('code', type=str) -def add_course(code): - course = addSemesterCourses(code) - print(f'Course Name: {course.courseName}') if course else print(f'error') -@course.command('getNextSemCourses', help='Get all the courses offered next semester') -def allSemCourses(): - courses = get_all_OfferedCodes() +################################################################# + +''' +Course Plan Commands +''' - if courses: - for c in courses: - print({c}) +course_plan = AppGroup('course_plan', help = 'Course plan object related commands') + +@course_plan.command('newPlan', help = 'create a new courseplan for a student') +@click.argument('id', type=int) +@click.argument('year', type = str) +@click.argument('sem', type = int) +def new_course_plan(id, year, sem): + create_CoursePlan(id, year, sem) + # print("Course plan created!") + + +@course_plan.command('getPlan', help = 'get a courseplan for a student') +@click.argument('id', type=int) +@click.argument('year', type = str) +@click.argument('sem', type = int) +def get_course_plan(id, year, sem): + courseplan = getCoursePlan(id, year, sem) + print (courseplan.get_json()) + + +@course_plan.command('AddCourse', help = 'add a new course for a student') +@click.argument('id', type=int) +@click.argument('courseCode', type = str) +@click.argument('year', type = str) +@click.argument('sem', type = int) +def add_new_course(id, courseCode, year, sem): + plan = addCourseToPlan(id, courseCode, year, sem) + if (plan): + print(plan.get_json()) else: - print("empty") - + print("no success") + +@course_plan.command('getplancourses', help = "get a list of courses in the course plan") +@click.argument('student_id', type = int) +@click.argument('year', type = str) +@click.argument('sem', type = int) +def get_plan_courses(student_id, year, sem): + plan_courses = getPlanCourses(student_id, year, sem) + for plan in plan_courses: + print(plan.get_json()) + # print(getPlanCourses(student_id)) + + +app.cli.add_command(course_plan) + + +''' +Course Plan Generator Commands +''' + +generate = AppGroup('generate', help = 'Generate a course plan based on strategy selected') + +@generate.command('createplan', help = 'Generate a course plan based on strategy selected') +@click.argument('student_id', type=int) +@click.argument('strategy', type=str) +@click.argument('year', type=str) +@click.argument('semester', type=int) +def generate_plan(student_id, strategy, year, semester): + if strategy.lower() == "easy": + strategy = EasyCoursePlanner() + params = [student_id] + elif strategy.lower() == "elective": + electives = input("Please enter the electives you wish to pursue (e.g. COMP3606, COMP3607): ") + strategy = ElectiveCoursePlanner() + params = [student_id, year, electives] + elif strategy.lower() == "fast": + strategy = FastCoursePlanner() + params = [student_id, year, semester] + else: + print("Invalid planning strategy. Please choose 'easy', 'fast' or 'elective'.") + return + + context = CoursePlanner(strategy) + result = context.plan_courses(params) + + +# @generate.command("easyplan") +# @click.argument('student_id', type = int) +# def easyplantest(student_id): +# strategy = EasyCoursePlanner() +# context = CoursePlanner(strategy) +# result = context.plan_courses(student_id) + + + + + + + -app.cli.add_command(course) \ No newline at end of file +app.cli.add_command(generate) \ No newline at end of file