Coverage for app/controllers/serviceLearning/routes.py: 27%
207 statements
« prev ^ index » next coverage.py v7.10.2, created at 2026-06-18 19:50 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2026-06-18 19:50 +0000
1from flask import request, render_template, g, url_for, abort, redirect, flash, session, send_from_directory, send_file, jsonify
2from werkzeug.utils import safe_join
3import os
4from peewee import *
5from typing import Dict, Any, List
7from app.models.user import User
8from app.models.term import Term
9from app.models.course import Course
10from app.models.courseStatus import CourseStatus
11from app.models.courseInstructor import CourseInstructor
12from app.models.courseQuestion import CourseQuestion
13from app.models.attachmentUpload import AttachmentUpload
14from app.logic.utils import selectSurroundingTerms, getFilesFromRequest
15from app.logic.fileHandler import FileHandler
16from app.logic.serviceLearningCourses import getSLProposalInfoForUser, withdrawProposal, renewProposal, updateCourse, createCourse, approvedCourses, deleteCourseObject
17from app.logic.downloadFile import *
18from app.logic.utils import getRedirectTarget, setRedirectTarget
19from app.controllers.serviceLearning import serviceLearning_bp
21@serviceLearning_bp.route('/serviceLearning/courseManagement', methods = ['GET'])
22@serviceLearning_bp.route('/serviceLearning/courseManagement/<username>', methods = ['GET'])
23def serviceCourseManagement(username=None):
24 try:
25 user = User.get(User.username==username) if username else g.current_user
26 except DoesNotExist:
27 abort(404)
29 isRequestingForSelf = g.current_user == user
30 if g.current_user.isCeltsAdmin or (g.current_user.isFaculty and isRequestingForSelf):
31 setRedirectTarget(request.full_path)
32 courseDict = getSLProposalInfoForUser(user)
33 termList = selectSurroundingTerms(g.current_term, prevTerms=0)
34 return render_template('serviceLearning/slcManagement.html',
35 user=user,
36 courseDict=courseDict,
37 termList=termList)
38 else:
39 abort(403)
41@serviceLearning_bp.route('/serviceLearning/viewProposal/<courseID>', methods=['GET'])
42@serviceLearning_bp.route('/serviceLearning/editProposal/upload/<courseID>', methods=['GET'])
43@serviceLearning_bp.route('/serviceLearning/editProposal/<courseID>', methods=['GET'])
44def slcEditProposal(courseID):
45 """
46 Route for editing proposals, it will fill the form with the data found in the database
47 given a courseID.
48 """
49 instructors = CourseInstructor.select().where(CourseInstructor.course==courseID)
50 courseInstructors = [instructor.user for instructor in instructors]
51 isCourseCreator = Course.select().where(Course.createdBy == g.current_user, Course.id==courseID).exists()
53 if g.current_user.isCeltsAdmin or g.current_user in courseInstructors or isCourseCreator:
54 course = Course.get_by_id(courseID)
55 courseStatus = CourseStatus.get_by_id(course.status)
56 courseStatusInt = courseStatus.get_id()
57 approved = 3
58 # Condition to check the route you are comming from
59 if courseStatusInt==approved and request.path == f"/serviceLearning/editProposal/{courseID}":
60 return redirect(f"/serviceLearning/viewProposal/{courseID}")
61 else:
62 statusOfCourse = Course.select(Course.status)
63 questionData = (CourseQuestion.select().where(CourseQuestion.course == course))
64 questionAnswers = [question.questionContent for question in questionData]
65 courseInstructor = CourseInstructor.select().where(CourseInstructor.course == courseID)
66 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.course == course.id)
68 filePaths = FileHandler(courseId=course.id).retrievePath(associatedAttachments)
70 terms = selectSurroundingTerms(g.current_term, 0)
72 return render_template('serviceLearning/slcNewProposal.html',
73 course = course,
74 questionanswers = questionAnswers,
75 terms = terms,
76 statusOfCourse = statusOfCourse,
77 courseInstructor = courseInstructor,
78 filePaths = filePaths,
79 courseStatus = courseStatus,
80 redirectTarget = getRedirectTarget())
82 else:
83 abort(403)
86@serviceLearning_bp.route('/serviceLearning/createCourse', methods=['GET'])
87def slcCreateCourse():
88 """will give a new course ID so that it can redirect to an edit page"""
89 course = createCourse(g.current_user)
90 return redirect(url_for('serviceLearning.slcEditProposal', courseID = course.id))
92@serviceLearning_bp.route('/serviceLearning/canceledProposal', methods=['POST'])
93def slcCancelProposal():
94 courseID = request.form.get('courseID')
95 course = Course.get_by_id(courseID)
96 if not course.courseName and not course.courseAbbreviation:
97 CourseQuestion.delete().where(CourseQuestion.course == course).execute()
98 course.delete_instance()
99 return "Proposal Canceled"
102@serviceLearning_bp.route('/serviceLearning/exit', methods=['GET'])
103def slcExitView():
104 if getRedirectTarget():
105 return redirect(getRedirectTarget(True))
106 else:
107 return redirect("/serviceLearning/courseManagement")
110@serviceLearning_bp.route('/serviceLearning/saveExit', methods=['POST'])
111@serviceLearning_bp.route('/serviceLearning/saveProposal', methods=['POST'])
112def slcSaveContinue():
113 """Will update the the course proposal and return an empty string since ajax request needs a response
114 Also, it updates the course status as 'in progress'"""
115 course = updateCourse(request.form.copy(), attachments=getFilesFromRequest(request))
117 if not course:
118 flash("Error saving changes", "danger")
119 else:
120 course.status = CourseStatus.DRAFT
121 course.save()
122 flash(f"Proposal has been saved.", "success")
123 if request.path == "/serviceLearning/saveExit":
124 if getRedirectTarget():
125 return redirect(getRedirectTarget(True))
126 return redirect("/serviceLearning/courseManagement")
127 return redirect(f'/serviceLearning/editProposal/{request.form["courseID"]}?tab=2')
129@serviceLearning_bp.route('/serviceLearning/newProposal', methods=['GET', 'POST'])
130def slcCreateOrEdit():
131 if request.method == "POST":
132 course = updateCourse(request.form.copy())
133 if not course:
134 flash("Error saving changes", "danger")
135 else:
136 if getRedirectTarget(False):
137 return redirect('' + getRedirectTarget(True) + '')
138 return redirect('/serviceLearning/courseManagement')
140 terms = Term.select().where(Term.year >= g.current_term.year)
141 return render_template('serviceLearning/slcNewProposal.html',
142 terms = terms,
143 courseData = None,
144 redirectTarget = getRedirectTarget(True))
146@serviceLearning_bp.route('/serviceLearning/approveCourse', methods=['POST'])
147def approveCourse():
148 """
149 This function updates and approves a Service-Learning Course when using the
150 approve button.
151 return: empty string because AJAX needs to receive something
152 """
154 try:
155 # We are only approving, and not updating
156 if len(request.form) == 1:
157 course = Course.get_by_id(request.form['courseID'])
159 # We have data and need to update the course first
160 else:
161 course = updateCourse(request.form.copy())
163 course.status = CourseStatus.APPROVED
164 course.save()
165 flash("Course approved!", "success")
167 except Exception as e:
168 print(e)
169 flash("Course not approved!", "danger")
170 return ""
171@serviceLearning_bp.route('/serviceLearning/unapproveCourse', methods=['POST'])
172def unapproveCourse():
173 """
174 This function updates and unapproves a Service-Learning Course when using the
175 unapprove button.
176 return: empty string because AJAX needs to receive something
177 """
179 try:
180 if len(request.form) == 1:
181 course = Course.get_by_id(request.form['courseID'])
182 else:
183 course = updateCourse(request.form.copy())
185 course.status = CourseStatus.SUBMITTED
186 course.save()
187 flash("Course unapproved!", "success")
189 except Exception as e:
190 print(e)
191 flash("Course was not unapproved!", "danger")
193 return ""
195@serviceLearning_bp.route('/updateInstructorPhone', methods=['POST'])
196def updateInstructorPhone():
197 instructorData = request.get_json()
198 (User.update(phoneNumber=instructorData[1])
199 .where(User.username == instructorData[0])).execute()
200 return "success"
202@serviceLearning_bp.route('/serviceLearning/withdraw/<courseID>', methods = ['POST'])
203def withdrawCourse(courseID):
204 try:
205 if g.current_user.isAdmin or g.current_user.isFaculty:
206 withdrawProposal(courseID)
207 flash("Course successfully withdrawn", 'success')
208 return ""
209 else:
210 flash("Unauthorized to perform this action", 'warning')
211 except Exception as e:
212 print(e)
213 flash("Withdrawal Unsuccessful", 'warning')
215 return "", 500
218@serviceLearning_bp.route('/proposalReview/', methods = ['GET', 'POST'])
219def reviewProposal() -> str:
220 """
221 this function gets the submitted course id and returns the its data to the review proposal modal
222 """
223 courseID: Dict[str, Any] = request.form
224 course: Course = Course.get_by_id(courseID["course_id"])
225 instructorsData: List[CourseInstructor] = course.courseInstructors
226 return render_template('/serviceLearning/reviewProposal.html',
227 course=course,
228 instructorsData=instructorsData)
230@serviceLearning_bp.route('/serviceLearning/renew/<courseID>/<termID>/', methods = ['POST'])
231def renewCourse(courseID, termID):
232 """
233 This function checks to see if the user is a CELTS admin or is
234 an instructor of a course (faculty) and allows courses to be renewed.
235 :return: empty string because AJAX needs to receive something
236 """
237 instructors = CourseInstructor.select().where(CourseInstructor.course==courseID)
238 courseInstructors = [instructor.user for instructor in instructors]
239 isCourseCreator = Course.select().where(Course.createdBy == g.current_user, Course.id==courseID).exists()
240 try:
241 if g.current_user.isCeltsAdmin or g.current_user in courseInstructors or isCourseCreator:
242 renewedProposal = renewProposal(courseID, termID)
243 flash("Course successfully renewed", 'success')
244 return str(renewedProposal.id)
245 else:
246 flash("Unauthorized to perform this action", 'warning')
247 except Exception as e:
248 print(e)
249 flash("Renewal Unsuccessful", 'warning')
251 return "", 500
253@serviceLearning_bp.route('/serviceLearning/print/<courseID>', methods=['GET'])
254def printCourse(courseID):
255 """
256 This function will print a PDF of an SLC proposal.
257 """
258 instructors = CourseInstructor.select().where(CourseInstructor.course==courseID)
259 courseInstructors = [instructor.user for instructor in instructors]
260 isCreator = Course.select().where(Course.createdBy == g.current_user, Course.id==courseID).exists()
261 if g.current_user.isCeltsAdmin or g.current_user in courseInstructors or isCreator:
262 try:
263 course = Course.get_by_id(courseID)
264 pdfCourse = Course.select().where(Course.id == courseID)
265 pdfInstructor = CourseInstructor.select().where(CourseInstructor.course == courseID)
266 pdfQuestions = (CourseQuestion.select().where(CourseQuestion.course == course))
267 questionanswers = [question.questionContent for question in pdfQuestions]
269 return render_template('serviceLearning/slcFormPrint.html',
270 course = course,
271 pdfCourse = pdfCourse,
272 pdfInstructor = pdfInstructor,
273 pdfQuestions = pdfQuestions,
274 questionanswers=questionanswers
275 )
276 except Exception as e:
277 flash("An error was encountered when printing, please try again.", 'warning')
278 print(e)
279 return "", 500
280 else:
281 abort(403)
283@serviceLearning_bp.route("/uploadCourseFile", methods=['GET', "POST"])
284def uploadCourseFile():
285 try:
286 attachment = getFilesFromRequest(request)
287 courseID = request.form["courseID"]
288 addFile = FileHandler(attachment, courseId=courseID)
289 addFile.saveFiles()
290 except:
291 flash("No file selected.", "warning")
292 return redirect('/serviceLearning/editProposal/upload/'+courseID)
295@serviceLearning_bp.route("/deleteCourseFile", methods=["POST"])
296def deleteCourseFile():
297 fileData= request.form
298 courseFile=FileHandler(courseId=fileData["databaseId"])
299 courseFile.deleteFile(fileData["fileId"])
300 return ""
302@serviceLearning_bp.route('/serviceLearning/downloadApprovedCourses/<termID>', methods = ['GET'])
303def downloadApprovedCourses(termID):
304 """
305 This function allows the download of csv file
306 """
307 try:
308 designator = "downloadApprovedCourses"
309 csvInfo = approvedCourses(termID)
310 fileFormat = {"headers":["Course Name", "Course Number", "Faculty", "Term", "Previously Approved Course?"]}
311 filePath = safe_join(os.getcwd(), app.config['files']['base_path'])
312 newFile = fileMaker(designator, csvInfo, "CSV", fileFormat)
313 return send_from_directory(filePath, 'ApprovedCourses.csv', as_attachment=True)
315 except Exception as e:
316 print(e)
317 return ""