Coverage for app/controllers/serviceLearning/routes.py: 27%

208 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-06-23 15:26 +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 

6 

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 

20 

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) 

28 

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) 

40 

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

52 

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) 

67 

68 filePaths = FileHandler(courseId=course.id).retrievePath(associatedAttachments) 

69 

70 terms = selectSurroundingTerms(g.current_term, 0) 

71 

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

81 

82 else: 

83 abort(403) 

84 

85 

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)) 

91 

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" 

100 

101 

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") 

108 

109 

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)) 

116 

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

128 

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

139 

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)) 

145 

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 """ 

153 

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']) 

158 

159 # We have data and need to update the course first 

160 else: 

161 course = updateCourse(request.form.copy()) 

162 

163 course.status = CourseStatus.APPROVED 

164 course.save() 

165 flash("Course approved!", "success") 

166 

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 """ 

178 

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

184 

185 course.status = CourseStatus.SUBMITTED 

186 course.save() 

187 flash("Course unapproved!", "success") 

188 

189 except Exception as e: 

190 print(e) 

191 flash("Course was not unapproved!", "danger") 

192 

193 return "" 

194 

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" 

201 

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 return "", 403 

212 except Exception as e: 

213 print(e) 

214 flash("Error: Failed to withdraw course", 'alert') 

215 

216 

217 return "", 500 

218 

219 

220@serviceLearning_bp.route('/proposalReview/', methods = ['GET', 'POST']) 

221def reviewProposal() -> str: 

222 """ 

223 this function gets the submitted course id and returns the its data to the review proposal modal 

224 """ 

225 courseID: Dict[str, Any] = request.form 

226 course: Course = Course.get_by_id(courseID["course_id"]) 

227 instructorsData: List[CourseInstructor] = course.courseInstructors 

228 return render_template('/serviceLearning/reviewProposal.html', 

229 course=course, 

230 instructorsData=instructorsData) 

231 

232@serviceLearning_bp.route('/serviceLearning/renew/<courseID>/<termID>/', methods = ['POST']) 

233def renewCourse(courseID, termID): 

234 """ 

235 This function checks to see if the user is a CELTS admin or is 

236 an instructor of a course (faculty) and allows courses to be renewed. 

237 :return: empty string because AJAX needs to receive something 

238 """ 

239 instructors = CourseInstructor.select().where(CourseInstructor.course==courseID) 

240 courseInstructors = [instructor.user for instructor in instructors] 

241 isCourseCreator = Course.select().where(Course.createdBy == g.current_user, Course.id==courseID).exists() 

242 try: 

243 if g.current_user.isCeltsAdmin or g.current_user in courseInstructors or isCourseCreator: 

244 renewedProposal = renewProposal(courseID, termID) 

245 flash("Course successfully renewed", 'success') 

246 return str(renewedProposal.id) 

247 else: 

248 flash("Unauthorized to perform this action", 'warning') 

249 except Exception as e: 

250 print(e) 

251 flash("Renewal Unsuccessful", 'warning') 

252 

253 return "", 500 

254 

255@serviceLearning_bp.route('/serviceLearning/print/<courseID>', methods=['GET']) 

256def printCourse(courseID): 

257 """ 

258 This function will print a PDF of an SLC proposal. 

259 """ 

260 instructors = CourseInstructor.select().where(CourseInstructor.course==courseID) 

261 courseInstructors = [instructor.user for instructor in instructors] 

262 isCreator = Course.select().where(Course.createdBy == g.current_user, Course.id==courseID).exists() 

263 if g.current_user.isCeltsAdmin or g.current_user in courseInstructors or isCreator: 

264 try: 

265 course = Course.get_by_id(courseID) 

266 pdfCourse = Course.select().where(Course.id == courseID) 

267 pdfInstructor = CourseInstructor.select().where(CourseInstructor.course == courseID) 

268 pdfQuestions = (CourseQuestion.select().where(CourseQuestion.course == course)) 

269 questionanswers = [question.questionContent for question in pdfQuestions] 

270 

271 return render_template('serviceLearning/slcFormPrint.html', 

272 course = course, 

273 pdfCourse = pdfCourse, 

274 pdfInstructor = pdfInstructor, 

275 pdfQuestions = pdfQuestions, 

276 questionanswers=questionanswers 

277 ) 

278 except Exception as e: 

279 flash("An error was encountered when printing, please try again.", 'warning') 

280 print(e) 

281 return "", 500 

282 else: 

283 abort(403) 

284 

285@serviceLearning_bp.route("/uploadCourseFile", methods=['GET', "POST"]) 

286def uploadCourseFile(): 

287 try: 

288 attachment = getFilesFromRequest(request) 

289 courseID = request.form["courseID"] 

290 addFile = FileHandler(attachment, courseId=courseID) 

291 addFile.saveFiles() 

292 except: 

293 flash("No file selected.", "warning") 

294 return redirect('/serviceLearning/editProposal/upload/'+courseID) 

295 

296 

297@serviceLearning_bp.route("/deleteCourseFile", methods=["POST"]) 

298def deleteCourseFile(): 

299 fileData= request.form 

300 courseFile=FileHandler(courseId=fileData["databaseId"]) 

301 courseFile.deleteFile(fileData["fileId"]) 

302 return "" 

303 

304@serviceLearning_bp.route('/serviceLearning/downloadApprovedCourses/<termID>', methods = ['GET']) 

305def downloadApprovedCourses(termID): 

306 """ 

307 This function allows the download of csv file 

308 """ 

309 try: 

310 designator = "downloadApprovedCourses" 

311 csvInfo = approvedCourses(termID) 

312 fileFormat = {"headers":["Course Name", "Course Number", "Faculty", "Term", "Previously Approved Course?"]} 

313 filePath = safe_join(os.getcwd(), app.config['files']['base_path']) 

314 newFile = fileMaker(designator, csvInfo, "CSV", fileFormat) 

315 return send_from_directory(filePath, 'ApprovedCourses.csv', as_attachment=True) 

316 

317 except Exception as e: 

318 print(e) 

319 return ""