diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..67419bc1 Binary files /dev/null and b/.DS_Store differ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..86b58d04 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.testing.unittestArgs": ["-v", "-s", "./App", "-p", "test_*.py"], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} diff --git a/App/.DS_Store b/App/.DS_Store new file mode 100644 index 00000000..876c08ef Binary files /dev/null and b/App/.DS_Store differ diff --git a/App/controllers/__init__.py b/App/controllers/__init__.py index 8b040957..fc0f1af3 100644 --- a/App/controllers/__init__.py +++ b/App/controllers/__init__.py @@ -1,2 +1,12 @@ from .user import * -from .auth import * \ No newline at end of file +from .auth import * +from .courses import * +from .program import * +from .staff import * +from .student import * +from .prerequistes import * +from .programCourses import * +from .studentCourseHistory import * +from .coursePlanCourses import * +from .coursesOfferedPerSem import * +from .coursePlan import * \ No newline at end of file diff --git a/App/controllers/coursePlan.py b/App/controllers/coursePlan.py new file mode 100644 index 00000000..50ffab72 --- /dev/null +++ b/App/controllers/coursePlan.py @@ -0,0 +1,278 @@ +from App.models import CoursePlan +from App.database import db +from App.controllers import ( + get_program_by_id, + get_course_by_courseCode, + get_credits, + getPrereqCodes, + getCompletedCourses, + createPlanCourse, + deleteCourseFromCoursePlan, + get_allCore, + get_allFoun, + get_allElectives, + getCompletedCourseCodes, + convertToList, + get_all_OfferedCodes, + isCourseOffered, + programCourses_SortedbyRating, + programCourses_SortedbyHighestCredits, + get_all_courses_by_planid, + +) + + + +def create_CoursePlan(id): + plan = CoursePlan(id) + db.session.add(plan) + db.session.commit() + return plan + +def getCoursePlan(studentid): + return CoursePlan.query.filter_by(studentId=studentid).first() + +def possessPrereqs(Student, course): + preqs = getPrereqCodes(course.courseName) + completed = getCompletedCourseCodes(Student.id) + for course in preqs: + if course not in completed: + return False + + return True + +def addCourseToPlan(Student, courseCode): + 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 + else: + plan = create_CoursePlan(Student.id) + createPlanCourse(plan.planId, courseCode) + print("Plan successfully created and Course was successfully added to course plan") + return plan + else: + print("Course is not offered") + else: + print("Course does not exist") + + +def removeCourse(Student, courseCode): + plan=getCoursePlan(Student.id) + 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) + remaining = [] + + if programme: + reqCore=get_allCore(programme.name) + completed = getCompletedCourses(Student.id) + remaining=getRemainingCourses(completed,reqCore) + + return remaining + + +def getRemainingFoun(Student): + programme = get_program_by_id(Student.program_id) + remaining =[] + + 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 + return requiredCreds + + +def findAvailable(courseList): + listing=get_all_OfferedCodes() + available=[] + + for code in courseList: + if code in listing: + available.append(code) + + return available #returns an array of course codes + + +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=[] + + #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)) + + 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 easyCourses(Student): + program = get_program_by_id(Student.program_id) + completed = getCompletedCourseCodes(Student.id) + codesSortedbyRating = programCourses_SortedbyRating(Student.program_id) + + coursesToDo = removeCoursesFromList(completed, codesSortedbyRating) + + elecCredits = remElecCredits(Student) + + if elecCredits == 0: + allElectives = convertToList(get_allElectives(program.name)) + coursesToDo = removeCoursesFromList(allElectives, coursesToDo) + + coursesToDo = findAvailable(coursesToDo) + + ableToDo = checkPrereq(Student, coursesToDo) + # for a in ableToDo: + # print(a) + + return getTopfive(ableToDo) + + +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) + + elecCredits = remElecCredits(Student) + + if elecCredits == 0: + allElectives = convertToList(get_allElectives(program.name)) + coursesToDo = removeCoursesFromList(allElectives, coursesToDo) + + coursesToDo = findAvailable(coursesToDo) + ableToDo = checkPrereq(Student, coursesToDo) + + return getTopfive(ableToDo) + +def commandCall(Student, command): + courses = [] + + if command == "electives": + courses = prioritizeElectives(Student) + + elif command == "easy": + courses = easyCourses(Student) + + elif command == "fastest": + courses = fastestGraduation(Student) + + else: + print("Invalid command") + + return courses + + +def generator(Student, command): + courses = [] + + plan = getCoursePlan(Student.id) + + if plan is None: + plan = plan = create_CoursePlan(Student.id) + + + courses = commandCall(Student, command) + + existingPlanCourses = get_all_courses_by_planid(plan.planId) + + planCourses = [] + for q in existingPlanCourses: + planCourses.append(q.code) + + for c in courses: + if c not in planCourses: + createPlanCourse(plan.planId, c) + + return courses \ No newline at end of file diff --git a/App/controllers/coursePlanCourses.py b/App/controllers/coursePlanCourses.py new file mode 100644 index 00000000..b3c5bc8b --- /dev/null +++ b/App/controllers/coursePlanCourses.py @@ -0,0 +1,25 @@ +from App.models import CoursePlanCourses +from App.database import db + +def createPlanCourse(planid, code): + course = CoursePlanCourses(planid, code) + db.session.add(course) + db.session.commit() + +def getCourseFromCoursePlan(planid, coursecode): + 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: + db.session.delete(course) + db.session.commit() + print("Course succesfully removed from course plan") + else: + print("Course is not in Course Plan") diff --git a/App/controllers/courses.py b/App/controllers/courses.py new file mode 100644 index 00000000..3d285f0f --- /dev/null +++ b/App/controllers/courses.py @@ -0,0 +1,80 @@ +from App.models import Course, Prerequisites +from App.controllers.prerequistes import (create_prereq, get_all_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() + + if prereq_course: + create_prereq(prereq_code,courseName) + +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) + + db.session.add(course) + db.session.commit() + return course + else: + return None + + +def createCoursesfromFile(file_path): + try: + with open(file_path, 'r') as file: + csv_reader = csv.DictReader(file) + for row in csv_reader: + courseCode = row["courseCode"] + courseName = row["courseName"] + credits = int(row["numCredits"]) + rating = int(row["rating"]) + prerequisites_codes = row["preReqs"].split(',') + + create_course(courseCode, courseName, rating, credits, prerequisites_codes) + + except FileNotFoundError: + print("File not found.") + + except Exception as e: + print(f"An error occurred: {e}") + return False + + print("Courses added successfully.") + +def get_course_by_courseCode(code): + return Course.query.filter_by(courseCode=code).first() + +def courses_Sorted_byRating(): + courses = Course.query.order_by(Course.rating.asc()).all() + codes = [] + + for c in courses: + codes.append(c.courseCode) + + return codes + +def courses_Sorted_byRating_Objects(): + return Course.query.order_by(Course.rating.asc()).all() + + +def get_prerequisites(code): + course = get_course_by_courseCode(code) + prereqs = get_all_prerequisites(course.courseName) + return prereqs + +def get_credits(code): + course = get_course_by_courseCode(code) + return course.credits if course else 0 + +def get_ratings(code): + course = get_course_by_courseCode(code) + return course.rating if course else 0 + + + diff --git a/App/controllers/coursesOfferedPerSem.py b/App/controllers/coursesOfferedPerSem.py new file mode 100644 index 00000000..d82985c6 --- /dev/null +++ b/App/controllers/coursesOfferedPerSem.py @@ -0,0 +1,39 @@ +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 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)}") + +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_OfferedCodes(): + offered = get_all_courses() + offeredcodes=[] + + for c in offered: + offeredcodes.append(c.code) + + return offeredcodes diff --git a/App/controllers/prerequistes.py b/App/controllers/prerequistes.py new file mode 100644 index 00000000..671db33c --- /dev/null +++ b/App/controllers/prerequistes.py @@ -0,0 +1,19 @@ +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 get_all_prerequisites(courseName): + return Prerequisites.query.filter(Prerequisites.courseName == courseName).all() + +def getPrereqCodes(courseName): + prereqs = get_all_prerequisites(courseName) + codes = [] + + for p in prereqs: + codes.append(p.prereq_courseCode) + + return codes \ No newline at end of file diff --git a/App/controllers/program.py b/App/controllers/program.py new file mode 100644 index 00000000..4f2cdc29 --- /dev/null +++ b/App/controllers/program.py @@ -0,0 +1,64 @@ +from App.models import Program +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 + + + +def get_program_by_name(programName): + return Program.query.filter_by(name=programName).first() + +def get_program_by_id(programId): + return Program.query.filter_by(id=programId).first() + +def get_level1_credits(programName): + program = get_program_by_name(programName) + return program.level1_credits if program else 0 + +def get_level1_courses(programName): + program = get_program_by_name(programName) + courses = program.str_level1_courses() + return courses if program else [] + +def get_core_credits(programName): + program = get_program_by_name(programName) + return program.core_credits if program else 0 + +def get_core_courses(programName): + program = get_program_by_name(programName) + courses = program.str_core_courses() + return courses if program else [] + +def get_elective_credits(programName): + program = get_program_by_name(programName) + return program.elective_credits if program else 0 + +def get_elective_courses(programName): + program = get_program_by_name(programName) + courses = program.str_elective_courses() + return courses if program else [] + +def get_foun_credits(programName): + program = get_program_by_name(programName) + return program.foun_credits if program else 0 + +def get_foun_courses(programName): + program = get_program_by_name(programName) + courses = program.str_foun_courses() + 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) + + all = core_courses + elective_courses + foun_courses + return all + + + diff --git a/App/controllers/programCourses.py b/App/controllers/programCourses.py new file mode 100644 index 00000000..955554d3 --- /dev/null +++ b/App/controllers/programCourses.py @@ -0,0 +1,82 @@ +from App.models import ProgramCourses, Program, Course +from App.controllers import (get_program_by_name, get_program_by_id, get_course_by_courseCode) +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 + else: + return "Invalid course code" + else: + return "Invalid program name" + +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_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 = [] + + for a in list: + codes.append(a.code) + + return codes + +def programCourses_SortedbyRating(programid): + program = get_program_by_id(programid) + programCourses = get_all_programCourses(program.name) + + sorted_courses = {1: [], 2: [], 3: [], 4: [], 5: []} + + for p in programCourses: + course = get_course_by_courseCode(p.code) + sorted_courses[course.rating].append(course.courseCode) + + sorted_courses_list = [course for rating_courses in sorted_courses.values() for course in rating_courses] + + return sorted_courses_list + +def programCourses_SortedbyHighestCredits(programid): + program = get_program_by_id(programid) + programCourses = get_all_programCourses(program.name) + + highTolow = [] + + for p in programCourses: + course = get_course_by_courseCode(p.code) + if course.credits > 3: + highTolow.insert(0,course.courseCode) + else: + highTolow.append(course.courseCode) + + return highTolow diff --git a/App/controllers/staff.py b/App/controllers/staff.py new file mode 100644 index 00000000..c90b3eec --- /dev/null +++ b/App/controllers/staff.py @@ -0,0 +1,67 @@ +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) + db.session.commit() + return new_staff + + +def verify_staff(username): + staff=Staff.query.filter_by(id=username).first() + if staff: + return True + return False + +def get_staff_by_id(ID): + return Staff.query.filter_by(id=ID).first() + + +# def add_program(self, program_name, description): +# try: +# new_program = Program(name=program_name, description=description) +# db.session.add(new_program) +# db.session.commit() +# return new_program +# except Exception as e: +# db.session.rollback() +# print(f"An error occurred while adding the program: {e}") + + +# def remove_program(self, program_name): +# try: +# program = Program.query.filter_by(name=program_name).first() +# if program: +# db.session.delete(program) +# db.session.commit() +# else: +# print(f"Program '{program_name}' not found.") +# except Exception as e: +# db.session.rollback() +# print(f"An error occurred while removing the program: {e}") + + +# def add_course(self, course_code, course_name, credits): +# try: +# new_course = Course(code=course_code, name=course_name, credits=credits) +# db.session.add(new_course) +# db.session.commit() +# return new_course +# except Exception as e: +# db.session.rollback() +# print(f"An error occurred while adding the course: {e}") + + +# def remove_course(self, course_code): +# try: +# course = Course.query.filter_by(code=course_code).first() +# if course: +# db.session.delete(course) +# db.session.commit() +# else: +# print(f"Course '{course_code}' not found.") +# except Exception as e: +# db.session.rollback() +# print(f"An error occurred while removing the course: {e}") diff --git a/App/controllers/student.py b/App/controllers/student.py new file mode 100644 index 00000000..e3248e4b --- /dev/null +++ b/App/controllers/student.py @@ -0,0 +1,56 @@ +from App.models import Student, CoursePlan, Program +from App.controllers import (get_program_by_name) +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") + +def get_student_by_id(ID): + return Student.query.filter_by(id=ID).first() + +def get_student(id): + return Student.query.get(id) + +def get_all_students(): + return Student.query.all() + +def get_all_students_json(): + students = Student.query.all() + if not students: + return [] + students_json = [student.get_json() for student in students] + return students_json + +def update_student(id, username): + student = get_student_by_id(id) + if student: + student.name = username + db.session.add(student) + db.session.commit() + return student + +def enroll_in_programme(student_id, programme_id): + student = get_student_by_id(student_id) + if student: + programme = Program.query.get(programme_id) + if programme: + student.program_id = programme_id + db.session.add(student) + db.session.commit() + return student.program_id + +def verify_student(username): + student=Student.query.filter_by(id=username).first() + if student: + return True + return False + + diff --git a/App/controllers/studentCourseHistory.py b/App/controllers/studentCourseHistory.py new file mode 100644 index 00000000..cb157ee6 --- /dev/null +++ b/App/controllers/studentCourseHistory.py @@ -0,0 +1,29 @@ +from App.models import StudentCourseHistory +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() + else: + print("Course doesn't exist") + else: + print("Student doesn't exist") + + +def getCompletedCourses(id): + return StudentCourseHistory.query.filter_by(studentID=id).all() + +def getCompletedCourseCodes(id): + completed = getCompletedCourses(id) + codes = [] + + for c in completed: + codes.append(c.code) + + return codes diff --git a/App/models/__init__.py b/App/models/__init__.py index 82da278c..c8b64520 100644 --- a/App/models/__init__.py +++ b/App/models/__init__.py @@ -1 +1,11 @@ -from .user import * \ No newline at end of file +from .user import * +from .courses import * +from .program import * +from .staff import * +from .student import * +from .coursesOfferedPerSem import * +from .programCourses import * +from .prerequisites import * +from .studentCourseHistory import * +from .coursePlanCourses import * +from .coursePlan import * diff --git a/App/models/coursePlan.py b/App/models/coursePlan.py new file mode 100644 index 00000000..e3cfaa1e --- /dev/null +++ b/App/models/coursePlan.py @@ -0,0 +1,19 @@ +from App.database import db + +class CoursePlan(db.Model): + planId=db.Column(db.Integer, primary_key=True) + studentId=db.Column(db.Integer, 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, ): + self.studentId = studentid + + + def get_json(self): + return{ + 'planId': self.planId, + 'studentId': self.studentId, + } \ No newline at end of file diff --git a/App/models/coursePlanCourses.py b/App/models/coursePlanCourses.py new file mode 100644 index 00000000..e4f982a9 --- /dev/null +++ b/App/models/coursePlanCourses.py @@ -0,0 +1,22 @@ +from App.database import db + + +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')) + + # associated_coursePlan = db.relationship('CoursePlan', back_populates='students', overlaps="coursePlan") + # associated_course = db.relationship('Course', back_populates='planIds', overlaps="courses") + + def __init__(self, plan, courseCode): + self.planId = plan + self.code = courseCode + + + def get_json(self): + return{ + 'Course Plan ID': self.planId, + 'Course': self.code + } + diff --git a/App/models/courses.py b/App/models/courses.py new file mode 100644 index 00000000..c198814c --- /dev/null +++ b/App/models/courses.py @@ -0,0 +1,31 @@ +from App.database import db +from App.models import prerequisites +import json + +class Course(db.Model): + 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) + + 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) + + # planIds = db.relationship('CoursePlanCourses', backref='courses', lazy=True) + + + def __init__(self, code, name, rating, credits): + self.courseCode = code + self.courseName = name + self.rating = rating + self.credits = credits + + def get_json(self): + return{ + 'Course Code:': self.courseCode, + 'Course Name: ': self.courseName, + '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 new file mode 100644 index 00000000..b3dcb1f5 --- /dev/null +++ b/App/models/coursesOfferedPerSem.py @@ -0,0 +1,16 @@ +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/prerequisites.py b/App/models/prerequisites.py new file mode 100644 index 00000000..4858c747 --- /dev/null +++ b/App/models/prerequisites.py @@ -0,0 +1,21 @@ +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)) + + associated_course = db.relationship('Course', back_populates='prerequisites', overlaps="courses") + + + + def __init__(self, prereqCode, nameofCourse): + self.prereq_courseCode = prereqCode + self.courseName = nameofCourse + + def get_json(self): + return{ + 'prereq_id': self.id, + 'prerequisite_courseCode': self.prereq_courseCode, + 'prerequisite_course':self.courseName + } \ No newline at end of file diff --git a/App/models/program.py b/App/models/program.py new file mode 100644 index 00000000..322983bb --- /dev/null +++ b/App/models/program.py @@ -0,0 +1,27 @@ +from App.database import db +class Program(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + core_credits = db.Column(db.Integer) + elective_credits = db.Column(db.Integer) + foun_credits = db.Column(db.Integer) + + students = db.relationship('Student', backref='program', lazy=True) + courses = db.relationship('ProgramCourses', backref='program', lazy=True) + + def __init__(self, name, core, elective, foun): + self.name = name + self.core_credits = core + self.elective_credits = elective + self.foun_credits = foun + + + def get_json(self): + return{ + 'Program ID:': self.id, + 'Program Name: ': self.name, + 'Core Credits: ': self.core_credits, + 'Elective Credits ': self.elective_credits, + 'Foundation Credits: ': self.foun_credits, + } + \ No newline at end of file diff --git a/App/models/programCourses.py b/App/models/programCourses.py new file mode 100644 index 00000000..c74cf113 --- /dev/null +++ b/App/models/programCourses.py @@ -0,0 +1,23 @@ +from App.database import db +class ProgramCourses(db.Model): + __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')) + courseType = db.Column(db.Integer) + + associated_program = db.relationship('Program', back_populates='courses', overlaps="program") + associated_course = db.relationship('Course', back_populates='programs', overlaps="courses") + + def __init__(self, programID, courseCode, num): + self.program_id = programID + self.code = courseCode + self.courseType = num + + def get_json(self): + return{ + 'Program Course ID:' : self.id, + 'Program ID:': self.program_id, + 'Course Code: ': self.code, + 'Course Type: ': self.courseType + } \ No newline at end of file diff --git a/App/models/staff.py b/App/models/staff.py new file mode 100644 index 00000000..bb601385 --- /dev/null +++ b/App/models/staff.py @@ -0,0 +1,20 @@ +from .user import User +from App.database import db + +class Staff(User): + + + id = db.Column(db.String(10), db.ForeignKey('user.id'), primary_key=True) + name = db.Column(db.String(50)) + + def __init__(self, password, staff_id, name): + super().__init__(staff_id, password) + self.id = staff_id + self.name = name + + def get_json(self): + return{ + 'staff_id': self.id, + 'name': self.name, + } + diff --git a/App/models/student.py b/App/models/student.py new file mode 100644 index 00000000..a0445ecb --- /dev/null +++ b/App/models/student.py @@ -0,0 +1,24 @@ +from App.models import User +from App.database import db + +class Student(User): + id = db.Column(db.String(10), db.ForeignKey('user.id'), primary_key=True) + name = db.Column(db.String(50)) + program_id = db.Column(db.ForeignKey('program.id')) + + 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 + self.name = name + self.program_id = program_id + + def get_json(self): + return{'student_id': self.id, + 'name': self.name, + 'program' : self.program_id + + } + diff --git a/App/models/studentCourseHistory.py b/App/models/studentCourseHistory.py new file mode 100644 index 00000000..b5d9e852 --- /dev/null +++ b/App/models/studentCourseHistory.py @@ -0,0 +1,20 @@ +from App.database import db + +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')) + + 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 get_json(self): + return{ + 'Program ID': self.id, #is this suppose to be id or program_id alone + 'Course Code': self.code + } \ No newline at end of file diff --git a/App/models/user.py b/App/models/user.py index 40caf3f4..22314f63 100644 --- a/App/models/user.py +++ b/App/models/user.py @@ -24,4 +24,3 @@ def set_password(self, password): def check_password(self, password): """Check hashed password.""" return check_password_hash(self.password, password) - diff --git a/App/tests/__init__.py b/App/tests/__init__.py index f5c872fa..84c894cb 100644 --- a/App/tests/__init__.py +++ b/App/tests/__init__.py @@ -1 +1,6 @@ -from .test_app import * \ No newline at end of file +from .test_app import * +from .courses import * +from .program import * +from .staff import * +from .coursePlan import * +from .coursesOfferedPerSem import * \ No newline at end of file diff --git a/App/tests/coursePlan.py b/App/tests/coursePlan.py new file mode 100644 index 00000000..af16c631 --- /dev/null +++ b/App/tests/coursePlan.py @@ -0,0 +1,170 @@ +import pytest, unittest +from App.models import CoursePlan, CoursePlanCourses +from App.controllers import create_CoursePlan, create_student, create_program, addCourseToPlan, enroll_in_programme, addSemesterCourses, generator, createCoursesfromFile, get_program_by_name, getCoursePlan, get_all_courses_by_planid, get_student, create_programCourse, removeCourse +from App.main import create_app +from App.database import db, create_db + +class CoursePlanUnitTests(unittest.TestCase): + + def test_new_courseplan(self): + student_id = "01234" + course_plan = CoursePlan(student_id) + self.assertEqual(course_plan.studentId, student_id) + + def test_courseplan_toJSON(self): + student_id = "01234" + course_plan = CoursePlan(student_id) + course_plan_json = course_plan.get_json() + self.assertDictEqual(course_plan_json, {"planId": None, "studentId": student_id}) + + + def test_new_courseplan_courses(self): + student_id = "01234" + course_plan = CoursePlan(student_id) + course_code = "INFO2605" + courseplan_courses = CoursePlanCourses("1", course_code) + self.assertEqual(courseplan_courses.code, course_code) + + + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + + +# Integration Tests +class CoursePlanIntegrationTests(unittest.TestCase): + + def test_create_course_plan(self): + # Create a student and program. + program = create_program("Computer Science Major", 69, 15, 9) + student = create_student("1234", "johnpass", "John Doe", program.name) + + #Create courses, semester courses and program coursess + createCoursesfromFile('testData/courseData.csv') + + file_path = "testData/test.txt" + with open(file_path, 'r') as file: + for i, line in enumerate(file): + line = line.strip() + if i ==0: + programName = line + else: + course = line.split(',') + create_programCourse(programName, course[0],int(course[1])) + + file_path1='testData/test2.txt' + with open(file_path1, 'r') as file: + for i, line in enumerate(file): + line = line.strip() + addSemesterCourses(line) + + + plan = create_CoursePlan(student.id) + self.assertIsNotNone(plan) + assert getCoursePlan(student.id) != None + + course_code = "INFO1601" + added_plan = addCourseToPlan(student, course_code) + courseplan_courses = get_all_courses_by_planid("1") + + self.assertIsNotNone(added_plan) + self.assertIn('INFO1601', [course.code for course in courseplan_courses]) + + + def test_remove_course_from_plan(self): + program = get_program_by_name("Computer Science Major") + student = get_student("1234") + plan_id = enroll_in_programme("1234", program.id) + courseplan_courses = get_all_courses_by_planid(plan_id) + self.assertIn('INFO1601', [course.code for course in courseplan_courses]) + removeCourse(student, "INFO1601") + courseplan_courses = get_all_courses_by_planid(plan_id) + self.assertNotIn('INFO1601', [course.code for course in courseplan_courses]) + + + def test_create_fastGraduation_course_plan(self): + program = get_program_by_name("Computer Science Major") + student = get_student("1234") + plan_id = enroll_in_programme("1234", program.id) + + courseplan_courses = get_all_courses_by_planid(plan_id) + course_codes = [course.code for course in courseplan_courses] + for course_code in course_codes: + removeCourse(student, course_code) + + generator(student, "fastest") + courseplan_courses = get_all_courses_by_planid(plan_id) + + print("Courses in Course Plan:") + for course in courseplan_courses: + print(course.code) + + self.assertIsNotNone(courseplan_courses) + self.assertIn('COMP1601', [course.code for course in courseplan_courses]) + + + def test_create_easy_course_plan(self): + program = get_program_by_name("Computer Science Major") + student = get_student("1234") + plan_id = enroll_in_programme("1234", program.id) + + courseplan_courses = get_all_courses_by_planid(plan_id) + course_codes = [course.code for course in courseplan_courses] + for course_code in course_codes: + removeCourse(student, course_code) + + # Create a easy graduation course plan + generator(student, "easy") + courseplan_courses = get_all_courses_by_planid(plan_id) + + self.assertIsNotNone(courseplan_courses) + self.assertIn('COMP1601', [course.code for course in courseplan_courses]) + + def test_create_electives_course_plan(self): + program = get_program_by_name("Computer Science Major") + student = get_student("1234") + plan_id = enroll_in_programme("1234", program.id) + + courseplan_courses = get_all_courses_by_planid(plan_id) + course_codes = [course.code for course in courseplan_courses] + for course_code in course_codes: + removeCourse(student, course_code) + + # Create an electives graduation course plan + generator(student, "electives") + courseplan_courses = get_all_courses_by_planid(plan_id) + + self.assertIsNotNone(courseplan_courses) + self.assertIn('COMP1601', [course.code for course in courseplan_courses]) + + def test_get_course_plan_json(self): + + program = get_program_by_name("Computer Science Major") + student = get_student("1234") + plan_id = enroll_in_programme("1234", program.id) + + courseplan_courses = get_all_courses_by_planid(plan_id) + course_codes = [course.code for course in courseplan_courses] + for course_code in course_codes: + removeCourse(student, course_code) + + course_code = "INFO1601" + added_plan = addCourseToPlan(student, course_code) + courseplan_courses = get_all_courses_by_planid(plan_id) + courseplan = getCoursePlan("1234") + self.assertIsNotNone(added_plan) + self.assertIn('INFO1601', [course.code for course in courseplan_courses]) + + course_plan_json = courseplan.get_json() + + expected_json = { + 'planId': 1, + 'studentId': 1234 + } + self.assertEqual(course_plan_json, expected_json) + + diff --git a/App/tests/courses.py b/App/tests/courses.py new file mode 100644 index 00000000..2d0a17b6 --- /dev/null +++ b/App/tests/courses.py @@ -0,0 +1,92 @@ +import pytest, unittest +from App.models import Course, Prerequisites +from App.controllers import create_course, courses_Sorted_byRating_Objects, get_course_by_courseCode, create_prereq, getPrereqCodes +from App.main import create_app +from App.database import db, create_db + +class CourseUnitTests(unittest.TestCase): + + def test_new_course(self): + courseCode = "INFO2605" + courseName = "Professional Ethics and Law" + credits = 3 + rating = 4 + + course = Course(courseCode, courseName, rating, credits) + + self.assertEqual(course.courseCode, courseCode) + self.assertEqual(course.courseName, courseName) + self.assertEqual(course.credits, credits) + self.assertEqual(course.rating, rating) + + def test_course_json(self): + courseCode = "INFO2605" + courseName = "Professional Ethics and Law" + credits = 3 + rating = 4 + + course = Course(courseCode, courseName, rating, credits) + course_json = course.get_json() + + self.assertDictEqual(course_json, { + 'Course Code:': courseCode, + 'Course Name: ': courseName, + 'Course Rating: ': rating, + 'No. of Credits: ': credits + }) + + + def test_new_prerequisite(self): + prereq=Prerequisites("INFO2605","Introduction to Information Technology Concepts") + assert prereq.prereq_courseCode=="INFO2605" + + def test_prerequisite_toJSON(self): + prereq=Prerequisites("INFO2605","Introduction to Information Technology Concepts") + prereq_json=prereq.get_json() + self.assertDictEqual(prereq_json,{ + 'prereq_id': None, + 'prerequisite_courseCode': 'INFO2605', + 'prerequisite_course': 'Introduction to Information Technology Concepts' + }) + + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + +def test_create_course(): + courseCode = "INFO2605" + courseName = "Professional Ethics and Law" + credits = 3 + rating = 4 + prereqs=[] + + course = create_course(courseCode, courseName, rating, credits, prereqs) + + assert get_course_by_courseCode("INFO2605") != None + + +class CourseIntegrationTests(unittest.TestCase): + def test_courses_sorted_by_rating(self): + prereqs=[] + + create_course("COMP6000", "DNS", 3, 3, prereqs) + create_course("COMP6001", "DSN", 1, 3, prereqs) + sortedCourses = courses_Sorted_byRating_Objects() + + self.assertTrue(sortedCourses) + + for i in range(len(sortedCourses) - 1): + self.assertLessEqual(sortedCourses[i].rating, sortedCourses[i + 1].rating) + + + def test_create_prerequisite(self): + create_course("MATH1115", "Fundamental Mathematics for the General Sciences 1",1,6,[]) + create_course("MATH2250", "Industrial Statistics",4,3,[]) + + create_prereq("MATH1115","Industrial Statistics") + prereqs=getPrereqCodes("Industrial Statistics") + self.assertEqual(['MATH1115'],prereqs) diff --git a/App/tests/coursesOfferedPerSem.py b/App/tests/coursesOfferedPerSem.py new file mode 100644 index 00000000..3ed964b1 --- /dev/null +++ b/App/tests/coursesOfferedPerSem.py @@ -0,0 +1,68 @@ +import pytest, unittest +from App.models import CoursesOfferedPerSem +from App.controllers import addSemesterCourses, create_course, isCourseOffered, get_all_OfferedCodes, delete_all_records +from App.main import create_app +from App.database import db, create_db + + +class CoursesOfferedPerSemUnitTests(unittest.TestCase): + + def test_new_offered_course(self): + course_code = "INFO2605" + offered_course = CoursesOfferedPerSem(course_code) + self.assertEqual(offered_course.code, course_code) + + def test_offered_course_toJSON(self): + course_code = "INFO2605" + offered_course = CoursesOfferedPerSem(course_code) + offered_course_json = offered_course.get_json() + self.assertDictEqual(offered_course_json, {"ID:": None, "Course Code:": course_code}) + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + +class CoursesOfferedPerSemIntegrationTests(unittest.TestCase): + def test_add_offered_course(self): + prereqs=[] + course_code = "INFO2605" + create_course(course_code, "Professional Ethics and Law", 3, 4, prereqs) + addSemesterCourses(course_code) + self.assertTrue(isCourseOffered(course_code)) + + def test_get_all_offered_courses_json(self): + create_course("MATH2250", "Industrial Statistics",4,3,[]) + create_course("INFO2605", "Professional Ethics and Law", 3, 4, []) + addSemesterCourses("MATH2250") + addSemesterCourses("INFO2605") + offered_courses = get_all_OfferedCodes() + + assert "MATH2250" in offered_courses + assert "INFO2605" in offered_courses + + def test_remove_all_offered(self): + + create_course("MATH2250", "Industrial Statistics", 4, 3, []) + create_course("INFO2605", "Professional Ethics and Law", 3, 4, []) + addSemesterCourses("MATH2250") + addSemesterCourses("INFO2605") + + # Check that courses are offered + assert isCourseOffered("MATH2250") + assert isCourseOffered("INFO2605") + + # Remove all offered courses + delete_all_records() + + # Check that courses are no longer offered + assert not isCourseOffered("MATH2250") + assert not isCourseOffered("INFO2605") + + + + + + diff --git a/App/tests/program.py b/App/tests/program.py new file mode 100644 index 00000000..e8784dc2 --- /dev/null +++ b/App/tests/program.py @@ -0,0 +1,80 @@ +import unittest, pytest +from App.models import Program, ProgramCourses +from App.main import create_app +from App.database import db, create_db +from App.controllers import create_program, get_program_by_name,create_course, create_programCourse, get_all_programCourses, programCourses_SortedbyRating,programCourses_SortedbyHighestCredits + +class ProgramUnitTests(unittest.TestCase): + + def test_new_program(self): + programname = "Information Technology Special" + core_credits = 69 + elective_credits = 15 + foun_credits = 9 + program = Program(programname, core_credits, elective_credits, foun_credits) + self.assertEqual(program.name, programname) + self.assertEqual(program.core_credits, core_credits) + self.assertEqual(program.elective_credits, elective_credits) + self.assertEqual(program.foun_credits, foun_credits) + + def test_program_toJSON(self): + programname = "Information Technology Special" + core_credits = 69 + elective_credits = 15 + foun_credits = 9 + + program = Program(programname, core_credits, elective_credits, foun_credits) + program_json = program.get_json() + + self.assertDictEqual(program_json, { + 'Program ID:': None, + 'Program Name: ': programname, + 'Core Credits: ': core_credits, + 'Elective Credits ': elective_credits, + 'Foundation Credits: ': foun_credits, + }) + + def test_new_program_course(self): + programcourse=ProgramCourses("1","INFO2605","2") + assert programcourse.code=="INFO2605" + + def test_program_course_toJSON(self): + programcourse=ProgramCourses("1","INFO2605","2") + programcourse_json=programcourse.get_json() + self.assertDictEqual(programcourse_json,{'Program Course ID:':None, 'Program ID:':'1','Course Code: ':'INFO2605','Course Type: ':'2'}) + + + + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + +class ProgramIntegrationTests(unittest.TestCase): + def test_create_program(self): + program = create_program("IT", 69, 15, 9) + assert get_program_by_name("IT") != None + + def test_create_program_requirement(self): + create_course("MATH1115", "Fundamental Mathematics for the General Sciences 1",1,6,[]) + create_course("MATH2250", "Industrial Statistics",4,3,[]) + create_course("INFO2606", "Internship",1,6,[]) + + create_programCourse("IT","MATH1115",1) + program_courses=get_all_programCourses("IT") + assert any(course.code == "MATH1115" for course in program_courses) + + def test_programCourses_sorted_by_credits(self): + create_programCourse("IT","INFO2606",2) + program=get_program_by_name("IT") + credits_sorted=programCourses_SortedbyHighestCredits(program.id) + self.assertListEqual(credits_sorted,['INFO2606', 'MATH1115']) + + def test_programCourses_sorted_by_rating(self): + create_programCourse("IT","MATH2250",1) + program=get_program_by_name("IT") + rating_list=programCourses_SortedbyRating(program.id) + self.assertListEqual(rating_list,['MATH1115', 'INFO2606', 'MATH2250']) \ No newline at end of file diff --git a/App/tests/staff.py b/App/tests/staff.py new file mode 100644 index 00000000..47d04e78 --- /dev/null +++ b/App/tests/staff.py @@ -0,0 +1,59 @@ +import unittest, pytest +from App.main import create_app +from App.database import db, create_db +from App.models import Staff +from App.controllers import create_staff, get_staff_by_id, login +from werkzeug.security import generate_password_hash + +class StaffUnitTests(unittest.TestCase): + + def test_new_staff(self): + staffid = 999 + staffName = "Jane Doe" + staffpass = "janepass" + staff = Staff(staffpass, staffid, staffName) + self.assertEqual(staff.name, staffName) + self.assertEqual(staff.id, staffid) + + def test_staff_toJSON(self): + staffid = 999 + staffName = "Jane Doe" + staffpass = "janepass" + + staff = Staff(staffpass, staffid, staffName) + staff_json = staff.get_json() + + self.assertDictEqual(staff_json, { + 'staff_id': staffid, + 'name': staffName, + }) + + def test_set_password(self): + password = "mypass" + staff = Staff(password, 999, "Jane Doe") + assert staff.password != password + + def test_check_password(self): + password = "mypass" + staff = Staff(password, 999, "Jane Doe") + assert staff.check_password(password) + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + +class StaffIntegrationTests(unittest.TestCase): + def test_create_staff(self): + staffid = 9 + staffName = "Jane Doe" + staffpass = "janepass" + staff = create_staff(staffpass, staffid, staffName) + + assert staff is not None + assert get_staff_by_id(9) != None + + + \ No newline at end of file diff --git a/App/tests/student.py b/App/tests/student.py new file mode 100644 index 00000000..76803367 --- /dev/null +++ b/App/tests/student.py @@ -0,0 +1,128 @@ +import os +import tempfile +import pytest +import logging +import unittest +from werkzeug.security import check_password_hash, generate_password_hash + +from App.main import create_app +from App.database import db, create_db +from App.models import User, Student, Program, StudentCourseHistory +from App.controllers import ( + create_user, + get_all_users_json, + login, + get_user, + update_user, + create_student, + addCoursetoHistory, + create_program, + create_course, + enroll_in_programme, + get_all_students_json, + update_student, + getCompletedCourses, +) + + +LOGGER = logging.getLogger(__name__) + + +class StudentUnitTest(unittest.TestCase): + + def test_new_student(self): + student = Student("01234", "johnpass", "John Doe", 1) + assert student.name == "John Doe" + + def test_student_toJSON(self): + student = Student("01234", "johnpass", "John Doe", 1) + student_json = student.get_json() + self.assertDictEqual( + {"name": "John Doe", "student_id": "01234", "program": 1}, student_json) + + +class StudentIntegrationTests(unittest.TestCase): + + def setUp(self): + app = create_app( + {'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + self.app = app.test_client() + + with app.app_context(): + create_db() + db.create_all() + + def tearDown(self): + with self.app: + db.session.remove() + db.drop_all() + db.engine.dispose() + + def test_create_student(self): + program = create_program("Computer Science Major", 3, 4, 5) + student = create_student( + "01234", "johnpass", "John Doe", program.name) + assert student.name == "John Doe" + + def test_get_all_student_json(self): + program = create_program("Computer Science Major", 3, 4, 5) + create_student("01234", "johnpass", "John Doe", program.name) + users_json = get_all_students_json() + self.assertListEqual( + [{"name": "John Doe", "student_id": "01234", "program": 1}], users_json) + + def test_update_student(self): + program = create_program("Computer Science Major", 3, 4, 5) + create_student("01234", "johnpass", "John Doe", program.name) + student = update_student("01234", "Bill") + assert student.name == "Bill" + + # def test_add_course_to_plan(self): + # course_code = "INFO2605" + # prereqs = [] + # create_course("INFO2605", "Professional Ethics and Law", 3, 4, prereqs) + # addSemesterCourses(course_code) + # program = create_program("Computer Science Major", 3, 4, 5) + # student = create_student( + # "01234", "johnpass", "John Doe", program.name) + # self.assertTrue(addCourseToPlan(student, course_code)) + + # def test_remove_course_from_plan(self): + # course_code = "INFO2605" + # prereqs = [] + # create_course("INFO2605", "Professional Ethics and Law", 3, 4, prereqs) + # addSemesterCourses(course_code) + # program = create_program("Computer Science Major", 3, 4, 5) + # student = create_student( + # "01234", "johnpass", "John Doe", program.name) + # plan = create_CoursePlan(1) + # addSemesterCourses(course_code) + # addCourseToPlan(student, course_code) + # enroll_in_programme(student.id, 1) + # removeCourse(student, course_code) + # course_from_course_plan = getCourseFromCoursePlan(plan.planId, course_code) + # self.assertEqual(course_from_course_plan.planId, 1) + # self.assertEqual(course_from_course_plan.code, "INFO2605") + + def test_enroll_in_programme(self): + program = create_program("Computer Science Major", 3, 4, 5) + student = create_student( + "01234", "johnpass", "John Doe", program.name) + enroll_in_programme(student.id, 1) + assert enroll_in_programme(student.id, 1) == 1 + + # def test_view_course_plan(self): + # course_code = "MATH2250" + # create_course( + # "/Users/jervalthomas/Desktop/Programming /Year 4 Sem 1/COMP 3613/flaskmvc/testData/courseData.csv") + # addSemesterCourses(course_code) + # program = create_program("Computer Science Major", 3, 4, 5) + # student = create_student( + # "816025522", "Password", "Jerval", program.name) + # create_CoursePlan(1) + # addSemesterCourses(course_code) + # addCourseToPlan(student, course_code) + # enroll_in_programme(student.id, 1) + # plan_json = view_course_plan(student) + # self.assertListEqual( + # [{"name": "Jerval", "student_id": "816025522", "program": 1}], plan_json) \ No newline at end of file diff --git a/App/tests/studentCourseHistory.py b/App/tests/studentCourseHistory.py new file mode 100644 index 00000000..abf7e473 --- /dev/null +++ b/App/tests/studentCourseHistory.py @@ -0,0 +1,72 @@ +import os +import tempfile +import pytest +import logging +import unittest +from werkzeug.security import check_password_hash, generate_password_hash + +from App.main import create_app +from App.database import db, create_db +from App.models import User, Student, Program, StudentCourseHistory +from App.controllers import ( + create_user, + get_all_users_json, + login, + get_user, + update_user, + create_student, + addCoursetoHistory, + create_program, + create_course, + enroll_in_programme, + get_all_students_json, + update_student, + getCompletedCourses, +) + + +LOGGER = logging.getLogger(__name__) + +class CourseHistoryUnitTest(unittest.TestCase): + + def test_create_course_history(self): + student_course_history = StudentCourseHistory(123, "INFO2605") + self.assertEqual(student_course_history.studentID, 123) + self.assertEqual(student_course_history.code, "INFO2605") + + def test_course_history_toJSON(self): + student_course_history = StudentCourseHistory(123, 'MATH1115') + result = student_course_history.get_json() + expected_result = {"Program ID": None, "Course Code": "MATH1115"} + self.assertDictEqual(expected_result, result) + + +class CourseHistoryIntegrationTests(unittest.TestCase): + + def setUp(self): + app = create_app( + {'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + self.app = app.test_client() + + with app.app_context(): + create_db() + db.create_all() + + def tearDown(self): + with self.app: + db.session.remove() + db.drop_all() + db.engine.dispose() + + def test_add_Course_to_History(self): + program = create_program("Computer Science Major", 3, 4, 5) + student = create_student( + "01234", "johnpass", "John Doe", program.name) + prereqs = [] + create_course("INFO2605", "Professional Ethics and Law", 3, 4, prereqs) + addCoursetoHistory(student.id, "INFO2605") + completed_courses = getCompletedCourses(student.id) + assert len(completed_courses) == 1 + for course_history in completed_courses: + self.assertIsInstance(course_history, StudentCourseHistory) + self.assertEqual(course_history.studentID, student.id) diff --git a/App/tests/test_app.py b/App/tests/test_app.py index ed1fac9b..f90573b0 100644 --- a/App/tests/test_app.py +++ b/App/tests/test_app.py @@ -1,16 +1,27 @@ -import os, tempfile, pytest, logging, unittest +import os +import tempfile +import pytest +import logging +import unittest from werkzeug.security import check_password_hash, generate_password_hash from App.main import create_app from App.database import db, create_db -from App.models import User +from App.models import User, Student, Program, StudentCourseHistory, CoursePlan from App.controllers import ( create_user, get_all_users_json, login, get_user, - get_user_by_username, - update_user + update_user, + create_student, + addCoursetoHistory, + create_program, + create_course, + enroll_in_programme, + get_all_students_json, + update_student, + getCompletedCourses, ) @@ -19,6 +30,8 @@ ''' Unit Tests ''' + + class UserUnitTests(unittest.TestCase): def test_new_user(self): @@ -29,8 +42,8 @@ def test_new_user(self): def test_get_json(self): user = User("bob", "bobpass") user_json = user.get_json() - self.assertDictEqual(user_json, {"id":None, "username":"bob"}) - + self.assertDictEqual(user_json, {"id": None, "username": "bob"}) + def test_hashed_password(self): password = "mypass" hashed = generate_password_hash(password, method='sha256') @@ -42,15 +55,19 @@ def test_check_password(self): user = User("bob", password) assert user.check_password(password) + ''' Integration Tests ''' # This fixture creates an empty database for the test and deletes it after the test # scope="class" would execute the fixture once and resued for all methods in the class + + @pytest.fixture(autouse=True, scope="module") def empty_db(): - app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + app = create_app( + {'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) create_db() yield app.test_client() db.drop_all() @@ -60,6 +77,7 @@ def test_authenticate(): user = create_user("bob", "bobpass") assert login("bob", "bobpass") != None + class UsersIntegrationTests(unittest.TestCase): def test_create_user(self): @@ -68,10 +86,10 @@ def test_create_user(self): def test_get_all_users_json(self): users_json = get_all_users_json() - self.assertListEqual([{"id":1, "username":"bob"}, {"id":2, "username":"rick"}], users_json) + self.assertListEqual([{"id": 1, "username": "bob"}, { + "id": 2, "username": "rick"}], users_json) - # Tests data changes in the database def test_update_user(self): update_user(1, "ronnie") user = get_user(1) - assert user.username == "ronnie" + assert user.username == "ronnie" \ No newline at end of file diff --git a/App/views/__init__.py b/App/views/__init__.py index 3a4791e1..756127bb 100644 --- a/App/views/__init__.py +++ b/App/views/__init__.py @@ -3,7 +3,8 @@ from .user import user_views from .index import index_views from .auth import auth_views +from .staff import staff_views +from .student import student_views - -views = [user_views, index_views, auth_views] +views = [user_views, index_views, auth_views, staff_views, student_views] # blueprints must be added to this list \ No newline at end of file diff --git a/App/views/auth.py b/App/views/auth.py index 291a02bb..f1fb9eb7 100644 --- a/App/views/auth.py +++ b/App/views/auth.py @@ -7,7 +7,8 @@ from App.controllers import ( create_user, jwt_authenticate, - login + login, + get_all_users ) auth_views = Blueprint('auth_views', __name__, template_folder='../templates') @@ -34,8 +35,8 @@ def login_action(): user = login(data['username'], data['password']) if user: login_user(user) - return 'user logged in!' - return 'bad username or password given', 401 + return jsonify({"token":jwt_authenticate(data['username'],data['password'])}) + return jsonify({"error":"invalid credentials"}), 401 @auth_views.route('/logout', methods=['GET']) def logout_action(): diff --git a/App/views/index.py b/App/views/index.py index cff58abd..0f7ca6ad 100644 --- a/App/views/index.py +++ b/App/views/index.py @@ -1,6 +1,6 @@ from flask import Blueprint, redirect, render_template, request, send_from_directory, jsonify from App.models import db -from App.controllers import create_user +from App.controllers import (create_course, create_staff,createCoursesfromFile) index_views = Blueprint('index_views', __name__, template_folder='../templates') @@ -12,8 +12,9 @@ def index_page(): def init(): db.drop_all() db.create_all() - create_user('bob', 'bobpass') - return jsonify(message='db initialized!') + create_staff("adminpass","999", "admin") + createCoursesfromFile('testData/courseData.csv') + return jsonify(message='staff created, courses created, db initialized!') @index_views.route('/health', methods=['GET']) def health_check(): diff --git a/App/views/staff.py b/App/views/staff.py new file mode 100644 index 00000000..91b13472 --- /dev/null +++ b/App/views/staff.py @@ -0,0 +1,130 @@ +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 ( + create_user, + create_program, + create_programCourse, + jwt_authenticate, + get_all_users, + get_all_users_json, + jwt_required, + addSemesterCourses, + get_all_OfferedCodes, + get_all_programCourses, + verify_staff +) + +staff_views = Blueprint('staff_views', __name__, template_folder='../templates') + +@staff_views.route('/staff/offeredCourses', methods=['GET']) +@login_required +def getOfferedCourses(): + 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() + return jsonify({'message':'Success', 'offered_courses':listing}), 200 + +@staff_views.route('/staff/program', methods=['POST']) +@login_required +def addProgram(): + data=request.json + name=data['name'] + core=data['core'] + elective=data['elective'] + foun=data['foun'] + + 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 + + #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: + return jsonify({'message': 'Program already exists'}), 400 + + if not isinstance(core, int): + return jsonify({"error": "'core' must be an integer"}), 400 + + if not isinstance(elective, int): + return jsonify({"error": "'elective' must be an integer"}), 400 + + if not isinstance(foun, int): + return jsonify({"error": "'foun' must be an integer"}), 400 + + newprogram = create_program(name, core, elective, foun) + if newprogram: + return jsonify({'message': f"Program {newprogram.name} added"}), 200 + else: + return jsonify({'message': "Program creation unsucessful"}), 400 + + +@staff_views.route('/programRequirement', methods=['POST']) +@login_required +def addProgramRequirements(): + data=request.json + name=data['name'] + code=data['code'] + num=data['type'] + + 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 + + #verify program existance + programs=Program.query.all() + programNames=[] + for p in programs: + programNames.append(p.name) + if name not in programNames: + return jsonify({'message': 'Program does not exist'}), 400 + + #verify that the course isn't already a requirement + courseList=get_all_programCourses(name) + courseCodeList=[] + for c in courseList: + courseCodeList.append(c.code) + + code=code.replace(" ","").upper() + if code in courseCodeList: + return jsonify({'message': f'{code} is already a requirement for {name}'}), 400 + + #verify that the course type is valid; Core (1) Elective (2) Foundation (3) + if num<1 or num>3: + return jsonify({'message': 'Invalid course type. Core (1) Elective (2) Foundation (3)'}), 400 + + response=create_programCourse(name, code, num) + 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 \ No newline at end of file diff --git a/App/views/student.py b/App/views/student.py new file mode 100644 index 00000000..927928cc --- /dev/null +++ b/App/views/student.py @@ -0,0 +1,112 @@ +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.index import index_views + +from App.controllers import ( + create_user, + jwt_authenticate, + get_all_users, + get_all_users_json, + jwt_required, + create_student, + get_program_by_name, + get_student_by_id, + get_course_by_courseCode, + addCoursetoHistory, + getCompletedCourseCodes, + generator, + addCourseToPlan, + verify_student +) + +student_views = Blueprint('student_views', __name__, template_folder='../templates') + +##Create student +@student_views.route('/student', methods=['POST']) +def create_student_route(): + student_id = request.json['student_id'] + password = request.json['password'] + name = request.json['name'] + programname = request.json['programname'] + + if not all([student_id, password, name, programname]): + return jsonify({'Error': 'Missing required fields. Please provide student id, password, name, and program name.'}), 400 + + student = get_student_by_id(student_id) + if student: + return jsonify({'Error': 'Student id found'}), 400 + + program = get_program_by_name(programname) + if not program: + return jsonify({'Error': 'Incorrect program name'}), 400 + + create_student(student_id, password, name, programname) + return jsonify({'Success!': f"user {student_id} created"}), 201 + +##Add course to course history + +@student_views.route('/student/add_course', methods=['POST']) +@login_required +def add_course_to_student_route(): + student_id = request.json['student_id'] + course_code = request.json['course_code'] + + 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 + + if not student_id or not course_code: + return jsonify({'Error': 'Missing required fields'}), 400 + + # Check if the student and course exist + student = get_student_by_id(student_id) + course = get_course_by_courseCode(course_code) + + if not student: + return jsonify({'Error': 'Student not found'}), 400 + if not course: + return jsonify({'Error': 'Course not found'}), 400 + + # Check if the course is already in the student's completed courses + completed_courses = getCompletedCourseCodes(student_id) + if course_code in completed_courses: + return jsonify({'Error': 'Course already completed'}), 400 + + addCoursetoHistory(student_id, course_code) + return jsonify({'Success!': f"Course {course_code} added to student {student_id}'s course history"}), 200 + + +##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'] + + 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) + + 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/requirements.txt b/requirements.txt index a6ad05ee..ae8e09c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ psycopg2-binary==2.9.3 pytest==7.0.1 python-dotenv==0.21.1 Flask-JWT-Extended==4.4.4 -Flask-Migrate==3.1.0 \ No newline at end of file +Flask-Migrate==3.1.0 +Werkzeug==2.2.2 \ No newline at end of file diff --git a/testData/courseData.csv b/testData/courseData.csv new file mode 100644 index 00000000..9ef96328 --- /dev/null +++ b/testData/courseData.csv @@ -0,0 +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, diff --git a/testData/courseTest.txt b/testData/courseTest.txt new file mode 100644 index 00000000..aef0a1b6 --- /dev/null +++ b/testData/courseTest.txt @@ -0,0 +1,16 @@ +COMP3605 +Data Analytics +3 +3 +COMP2611, MATH2250 + +COMP3610 +Big Data Analytics +3 +3 +COMP3605 + +"COMP2611","Data Structures","3","5","" +"MATH2250","Industrial Statistics","3","4","" +"COMP3605","Data Analytics","3","3","COMP2611, MATH2250" +"COMP3610","Big Data Analytics","3","5","COMP3605" \ No newline at end of file diff --git a/testData/test.txt b/testData/test.txt new file mode 100644 index 00000000..427440bf --- /dev/null +++ b/testData/test.txt @@ -0,0 +1,36 @@ +Computer Science Major +COMP1600,1 +COMP1601,1 +COMP1602,1 +COMP1603,1 +COMP1604,1 +INFO1600,1 +INFO1601,1 +MATH1115,1 +COMP2601,1 +COMP2602,1 +COMP2603,1 +COMP2604,1 +COMP2605,1 +COMP2606,1 +COMP2611,1 +COMP3601,1 +COMP3602,1 +COMP3603,1 +INFO2602,1 +INFO2604,1 +INFO3604,1 +MATH2250,1 +COMP3605,2 +COMP3606,2 +COMP3607,2 +COMP3608,2 +COMP3609,2 +COMP3610,2 +COMP3611,2 +COMP3612,2 +COMP3613,2 +FOUN1101,3 +FOUN1105,3 +FOUN1301,3 +INFO2606,2 \ No newline at end of file diff --git a/testData/test2.txt b/testData/test2.txt new file mode 100644 index 00000000..32a5342e --- /dev/null +++ b/testData/test2.txt @@ -0,0 +1,13 @@ +COMP1600 +COMP1601 +INFO1600 +INFO1601 +MATH2250 +COMP2611 +COMP2602 +COMP2601 +COMP3602 +COMP3603 +COMP2605 +COMP3605 +INFO2606 \ No newline at end of file diff --git a/wsgi.py b/wsgi.py index 87b4a3ba..be222977 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,22 +1,80 @@ import click, pytest, sys +import csv from flask import Flask +from App.controllers.student import create_student from flask.cli import with_appcontext, AppGroup from App.database import db, get_migrate from App.main import create_app -from App.controllers import ( create_user, get_all_users_json, get_all_users ) +from App.controllers import ( + create_user, + get_all_users_json, + get_all_users, + create_program, + get_all_OfferedCodes, + get_core_credits, + createCoursesfromFile, + get_course_by_courseCode, + get_prerequisites, + get_all_courses, + create_programCourse, + addSemesterCourses, + create_student, + create_staff, + get_program_by_name, + get_all_programCourses, + addCoursetoHistory, + getCompletedCourseCodes, + get_allCore, + addCourseToPlan, + get_student_by_id, + generator + ) + +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 app = create_app() migrate = get_migrate(app) + # This command creates and initializes the database @app.cli.command("init", help="Creates and initializes the database") def initialize(): db.drop_all() 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") + + for c in test1: + addCoursetoHistory(816, c) + print('Student course history updated') + + with open(file_path, 'r') as file: + for i, line in enumerate(file): + line = line.strip() + if i ==0: + programName = line + else: + course = line.split(',') + create_programCourse(programName, course[0],int(course[1])) + + file_path1='testData/test2.txt' + with open(file_path1, 'r') as file: + for i, line in enumerate(file): + line = line.strip() + addSemesterCourses(line) + + + + print('database intialized') ''' @@ -49,6 +107,92 @@ def list_user_command(format): app.cli.add_command(user_cli) # add the group to the cli + +# ... (previous code remains the same) + +''' +Student +''' +student_cli = AppGroup("student", help="Student object commands") + +# Define the student create command +@student_cli.command("create", help="Creates a student") +@click.argument("student_id", type=str) +@click.argument("password", type=str) +@click.argument("name", type=str) +@click.argument("programName", type=str) +def create_student_command(student_id, password, name, programname): + create_student(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) + +@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) + for c in comp: + print(f'{c}') + +@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") +@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) + + +app.cli.add_command(student_cli) + +''' +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) + 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) +@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("code", type=str) +def add_offered_course(code): + course=addSemesterCourses(code) + if course: + print(f'Course details: {course}') + + +app.cli.add_command(staff_cli) + ''' Test Commands ''' @@ -64,6 +208,195 @@ def user_tests_command(type): sys.exit(pytest.main(["-k", "UserIntegrationTests"])) else: sys.exit(pytest.main(["-k", "App"])) + +@test.command("course", help="Run Course tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/courses.py::CourseUnitTests"])) + + elif type == "int": + sys.exit(pytest.main(["App/tests/courses.py::CourseIntegrationTests"])) + + else: + sys.exit(pytest.main(["App/tests/courses.py"])) + +@test.command("coursePlan", help="Run Course Plan tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/coursePlan.py::CoursePlanUnitTests"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/coursePlan.py::CoursePlanIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/coursePlan.py"])) + +#CoursesOfferedPerSemUnitTests +@test.command("coursesOffered", help="Run Courses Offered Per Sem tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/coursesOfferedPerSem.py::CoursesOfferedPerSemUnitTests"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/coursesOfferedPerSem.py::CoursesOfferedPerSemIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/coursesOfferedPerSem.py"])) + + +@test.command("program", help="Run Program tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/program.py::ProgramUnitTests"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/program.py::ProgramIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/program.py"])) + + +@test.command("staff", help="Run Staff tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/staff.py::StaffUnitTests"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/staff.py::StaffIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/staff.py"])) + +@test.command("student", help="Run Program tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/student.py::StudentUnitTest"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/student.py::StudentIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/student.py"])) + +@test.command("studentCH", help="Run Student Course History tests") +@click.argument("type", default="all") +def courses_tests_command(type): + if type == "unit": + sys.exit(pytest.main(["App/tests/studentCourseHistory.py::CourseHistoryUnitTest"])) + elif type == "int": + sys.exit(pytest.main(["App/tests/studentCourseHistory.py::CourseHistoryIntegrationTests"])) + else: + sys.exit(pytest.main(["App/tests/studentCourseHistory.py"])) + + + +app.cli.add_command(test) +################################################################# + +''' +Program Commands +''' + +program = AppGroup('program', help = 'Program object commands') + +@program.command('create', help='Create a new program') +@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): + program = create_program(name, core, elective, foun) + + +@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") + for c in core: + print({c.code}) + +@program.command('corecredits', help='Get program core courses') +@click.argument('programname', type=str) +def get_CoreCredits(programname): + credits = get_core_credits(programname) + print(f'Total Core Credits = {credits}') if credits else print(f'error') + +@program.command('allcourses', help='Get all courses') +@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') + +@program.command('getprogram', help='Get a program by name') +@click.argument('programname', type=str) +def getProgram(programname): + program = get_program_by_name(programname) + print(f'{program.id}') + +@program.command('addCourse', help='Add a course to a program') +@click.argument('programname', type=str) +@click.argument('code', type=str) +@click.argument('type', type=int) +def addProgramCourse(programname, code, type): + create_programCourse(programname, code, type) + +@program.command('getprogramCourses', help='Get all courses of a program') +@click.argument('programname', type=str) +def addProgramCourse(programname): + courses = get_all_programCourses(programname) + for c in courses: + print(f'{c.code}') + +app.cli.add_command(program) +################################################################# + +''' +Course Commands +''' + +course = AppGroup('course', help = 'Program object commands') + +# @course.command('create', help='Create a new course') +# @click.argument('file_path') +# def create_course_command(file_path): +# 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('prereqs', help='Create a new course') +@click.argument('code', type=str) +def create_course_command(code): + prereqs = get_prerequisites(code) + print(f'These are the prerequisites for {code}: {prereqs}') if prereqs else print(f'error') + +@course.command('getcourse', help='Get a course by course 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') + +@course.command('getprereqs', help='Get all prerequistes for a course') +@click.argument('code', type=str) +def get_course(code): + prereqs = get_prerequisites(code) + for r in prereqs: + print(f'{r.prereq_courseCode}') + +@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") -app.cli.add_command(test) \ No newline at end of file +app.cli.add_command(course) \ No newline at end of file