Coverage for app/controllers/main/routes.py: 26%

308 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-01-16 20:21 +0000

1from flask import request, render_template, g, abort, flash, redirect, url_for 

2from peewee import JOIN 

3from playhouse.shortcuts import model_to_dict 

4import datetime 

5import json 

6from http import cookies 

7 

8from app import app 

9from app.models.program import Program 

10from app.models.event import Event 

11from app.models.backgroundCheck import BackgroundCheck 

12from app.models.backgroundCheckType import BackgroundCheckType 

13from app.models.user import User 

14from app.models.eventParticipant import EventParticipant 

15from app.models.interest import Interest 

16from app.models.programBan import ProgramBan 

17from app.models.term import Term 

18from app.models.eventRsvp import EventRsvp 

19from app.models.note import Note 

20from app.models.profileNote import ProfileNote 

21from app.models.programManager import ProgramManager 

22from app.models.courseInstructor import CourseInstructor 

23from app.models.certification import Certification 

24from app.models.emergencyContact import EmergencyContact 

25from app.models.insuranceInfo import InsuranceInfo 

26from app.models.celtsLabor import CeltsLabor 

27 

28from app.controllers.main import main_bp 

29from app.logic.loginManager import logout 

30from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo 

31from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent 

32from app.logic.events import * 

33from app.logic.searchUsers import searchUsers 

34from app.logic.transcript import * 

35from app.logic.landingPage import getManagerProgramDict, getActiveEventTab 

36from app.logic.utils import selectSurroundingTerms 

37from app.logic.certification import getCertRequirementsWithCompletion 

38from app.logic.createLogs import createRsvpLog, createAdminLog 

39from app.logic.celtsLabor import getCeltsLaborHistory 

40 

41@main_bp.route('/logout', methods=['GET']) 

42def redirectToLogout(): 

43 return redirect(logout()) 

44 

45@main_bp.route('/', methods=['GET']) 

46def landingPage(): 

47 

48 managerProgramDict = getManagerProgramDict(g.current_user) 

49 

50 # Optimize the query to fetch programs with non-canceled, non-past events in the current term 

51 programsWithEventsList = list(Program.select(Program, Event) 

52 .join(Event) 

53 .where((Event.term == g.current_term) and (Event.isCanceled == False) and (Event.isPast == False)) 

54 .distinct() 

55 .execute()) # Ensure only unique programs are included 

56 

57 return render_template("/main/landingPage.html", 

58 managerProgramDict=managerProgramDict, 

59 term=g.current_term, 

60 programsWithEventsList=programsWithEventsList) 

61 

62 

63@main_bp.route('/goToEventsList/<programID>', methods=['GET']) 

64def goToEventsList(programID): 

65 return {"activeTab": getActiveEventTab(programID)} 

66 

67@main_bp.route('/eventsList/<selectedTerm>', methods=['GET'], defaults={'activeTab': "studentLedEvents", 'programID': 0}) 

68@main_bp.route('/eventsList/<selectedTerm>/<activeTab>', methods=['GET'], defaults={'programID': 0}) 

69@main_bp.route('/eventsList/<selectedTerm>/<activeTab>/<programID>', methods=['GET']) 

70def events(selectedTerm, activeTab, programID): 

71 currentTerm = g.current_term 

72 if selectedTerm: 

73 currentTerm = selectedTerm 

74 currentTime = datetime.datetime.now() 

75 

76 listOfTerms = Term.select() 

77 participantRSVP = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == g.current_user) 

78 rsvpedEventsID = [event.event.id for event in participantRSVP] 

79 

80 term = Term.get_by_id(currentTerm) 

81 

82 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

83 studentLedEvents = getStudentLedEvents(term) 

84 countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime) 

85 trainingEvents = getTrainingEvents(term, g.current_user) 

86 bonnerEvents = getBonnerEvents(term) 

87 otherEvents = getOtherEvents(term) 

88 

89 managersProgramDict = getManagerProgramDict(g.current_user) 

90 

91 return render_template("/events/event_list.html", 

92 selectedTerm = term, 

93 studentLedEvents = studentLedEvents, 

94 trainingEvents = trainingEvents, 

95 bonnerEvents = bonnerEvents, 

96 otherEvents = otherEvents, 

97 listOfTerms = listOfTerms, 

98 rsvpedEventsID = rsvpedEventsID, 

99 currentEventRsvpAmount = currentEventRsvpAmount, 

100 currentTime = currentTime, 

101 user = g.current_user, 

102 activeTab = activeTab, 

103 programID = int(programID), 

104 managersProgramDict = managersProgramDict, 

105 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents 

106 ) 

107 

108@main_bp.route('/profile/<username>', methods=['GET']) 

109def viewUsersProfile(username): 

110 """ 

111 This function displays the information of a volunteer to the user 

112 """ 

113 try: 

114 volunteer = User.get(User.username == username) 

115 except Exception as e: 

116 if g.current_user.isAdmin: 

117 flash(f"{username} does not exist! ", category='danger') 

118 return redirect(url_for('admin.studentSearchPage')) 

119 else: 

120 abort(403) # Error 403 if non admin/student-staff user trys to access via url 

121 

122 if (g.current_user == volunteer) or g.current_user.isAdmin: 

123 upcomingEvents = getUpcomingEventsForUser(volunteer) 

124 participatedEvents = getParticipatedEventsForUser(volunteer) 

125 programs = Program.select() 

126 if not g.current_user.isBonnerScholar and not g.current_user.isAdmin: 

127 programs = programs.where(Program.isBonnerScholars == False) 

128 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == volunteer) 

129 programsInterested = [interest.program for interest in interests] 

130 

131 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer) 

132 rsvpedEvents = [event.event.id for event in rsvpedEventsList] 

133 

134 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer) 

135 permissionPrograms = [entry.program.id for entry in programManagerPrograms] 

136 

137 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

138 backgroundTypes = list(BackgroundCheckType.select()) 

139 

140 eligibilityTable = [] 

141 for program in programs: 

142 banNotes = list(ProgramBan.select(ProgramBan, Note) 

143 .join(Note, on=(ProgramBan.banNote == Note.id)) 

144 .where(ProgramBan.user == volunteer, 

145 ProgramBan.program == program, 

146 ProgramBan.endDate > datetime.datetime.now()).execute()) 

147 userParticipatedTrainingEvents = getParticipationStatusForTrainings(program, [volunteer], g.current_term) 

148 try: 

149 allTrainingsComplete = False not in [attended for event, attended in userParticipatedTrainingEvents[username]] # Did volunteer attend all events 

150 except KeyError: 

151 allTrainingsComplete = False 

152 noteForDict = banNotes[-1].banNote.noteContent if banNotes else "" 

153 eligibilityTable.append({"program": program, 

154 "completedTraining": allTrainingsComplete, 

155 "trainingList": userParticipatedTrainingEvents, 

156 "isNotBanned": (not banNotes), 

157 "banNote": noteForDict}) 

158 profileNotes = ProfileNote.select().where(ProfileNote.user == volunteer) 

159 

160 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer) 

161 

162 managersProgramDict = getManagerProgramDict(g.current_user) 

163 managersList = [id[1] for id in managersProgramDict.items()] 

164 participatedInLabor = getCeltsLaborHistory(volunteer) 

165 

166 return render_template ("/main/userProfile.html", 

167 programs = programs, 

168 programsInterested = programsInterested, 

169 upcomingEvents = upcomingEvents, 

170 participatedEvents = participatedEvents, 

171 rsvpedEvents = rsvpedEvents, 

172 permissionPrograms = permissionPrograms, 

173 eligibilityTable = eligibilityTable, 

174 volunteer = volunteer, 

175 backgroundTypes = backgroundTypes, 

176 allBackgroundHistory = allBackgroundHistory, 

177 currentDateTime = datetime.datetime.now(), 

178 profileNotes = profileNotes, 

179 bonnerRequirements = bonnerRequirements, 

180 managersList = managersList, 

181 participatedInLabor = participatedInLabor, 

182 ) 

183 abort(403) 

184 

185@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST']) 

186def emergencyContactInfo(username): 

187 """ 

188 This loads the Emergency Contact Page 

189 """ 

190 if not (g.current_user.username == username or g.current_user.isCeltsAdmin): 

191 abort(403) 

192 

193 

194 if request.method == 'GET': 

195 readOnly = g.current_user.username != username 

196 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username) 

197 return render_template ("/main/emergencyContactInfo.html", 

198 username=username, 

199 contactInfo=contactInfo, 

200 readOnly=readOnly 

201 ) 

202 

203 elif request.method == 'POST': 

204 if g.current_user.username != username: 

205 abort(403) 

206 

207 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute() 

208 if not rowsUpdated: 

209 EmergencyContact.create(user = username, **request.form) 

210 createAdminLog(f"{g.current_user} updated {username}'s emergency contact information.") 

211 flash('Emergency contact information saved successfully!', 'success') 

212 

213 if request.args.get('action') == 'exit': 

214 return redirect (f"/profile/{username}") 

215 else: 

216 return redirect (f"/profile/{username}/insuranceInfo") 

217 

218@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST']) 

219def insuranceInfo(username): 

220 """ 

221 This loads the Insurance Information Page 

222 """ 

223 if not (g.current_user.username == username or g.current_user.isCeltsAdmin): 

224 abort(403) 

225 

226 if request.method == 'GET': 

227 readOnly = g.current_user.username != username 

228 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username) 

229 return render_template ("/main/insuranceInfo.html", 

230 username=username, 

231 userInsuranceInfo=userInsuranceInfo, 

232 readOnly=readOnly 

233 ) 

234 

235 # Save the form data 

236 elif request.method == 'POST': 

237 if g.current_user.username != username: 

238 abort(403) 

239 

240 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute() 

241 if not rowsUpdated: 

242 InsuranceInfo.create(user = username, **request.form) 

243 createAdminLog(f"{g.current_user} updated {username}'s emergency contact information.") 

244 flash('Insurance information saved successfully!', 'success') 

245 

246 if request.args.get('action') == 'exit': 

247 return redirect (f"/profile/{username}") 

248 else: 

249 return redirect (f"/profile/{username}/emergencyContact") 

250 

251@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST']) 

252def travelForm(username): 

253 if not (g.current_user.username == username or g.current_user.isCeltsAdmin): 

254 abort(403) 

255 

256 user = (User.select(User, EmergencyContact, InsuranceInfo) 

257 .join(EmergencyContact, JOIN.LEFT_OUTER).switch() 

258 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

259 .where(User.username == username).limit(1)) 

260 if not list(user): 

261 abort(404) 

262 userData = list(user.dicts())[0] 

263 userData = {key: value if value else '' for (key, value) in userData.items()} 

264 

265 return render_template ('/main/travelForm.html', 

266 userData = userData 

267 ) 

268 

269 

270@main_bp.route('/profile/addNote', methods=['POST']) 

271def addNote(): 

272 """ 

273 This function adds a note to the user's profile. 

274 """ 

275 postData = request.form 

276 try: 

277 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"]) 

278 flash("Successfully added profile note", "success") 

279 return redirect(url_for("main.viewUsersProfile", username=postData["username"])) 

280 except Exception as e: 

281 print("Error adding note", e) 

282 flash("Failed to add profile note", "danger") 

283 return "Failed to add profile note", 500 

284 

285@main_bp.route('/<username>/deleteNote', methods=['POST']) 

286def deleteNote(username): 

287 """ 

288 This function deletes a note from the user's profile. 

289 """ 

290 try: 

291 deleteProfileNote(request.form["id"]) 

292 flash("Successfully deleted profile note", "success") 

293 except Exception as e: 

294 print("Error deleting note", e) 

295 flash("Failed to delete profile note", "danger") 

296 return "success" 

297 

298# ===========================Ban=============================================== 

299@main_bp.route('/<username>/ban/<program_id>', methods=['POST']) 

300def ban(program_id, username): 

301 """ 

302 This function updates the ban status of a username either when they are banned from a program. 

303 program_id: the primary id of the program the student is being banned from 

304 username: unique value of a user to correctly identify them 

305 """ 

306 postData = request.form 

307 banNote = postData["note"] # This contains the note left about the change 

308 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective 

309 try: 

310 banUser(program_id, username, banNote, banEndDate, g.current_user) 

311 programInfo = Program.get(int(program_id)) 

312 flash("Successfully banned the volunteer", "success") 

313 createAdminLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.') 

314 return "Successfully banned the volunteer." 

315 except Exception as e: 

316 print("Error while updating ban", e) 

317 flash("Failed to ban the volunteer", "danger") 

318 return "Failed to ban the volunteer", 500 

319 

320# ===========================Unban=============================================== 

321@main_bp.route('/<username>/unban/<program_id>', methods=['POST']) 

322def unban(program_id, username): 

323 """ 

324 This function updates the ban status of a username either when they are unbanned from a program. 

325 program_id: the primary id of the program the student is being unbanned from 

326 username: unique value of a user to correctly identify them 

327 """ 

328 postData = request.form 

329 unbanNote = postData["note"] # This contains the note left about the change 

330 try: 

331 unbanUser(program_id, username, unbanNote, g.current_user) 

332 programInfo = Program.get(int(program_id)) 

333 createAdminLog(f'Unbanned {username} from {programInfo.programName}.') 

334 flash("Successfully unbanned the volunteer", "success") 

335 return "Successfully unbanned the volunteer" 

336 

337 except Exception as e: 

338 print("Error while updating Unban", e) 

339 flash("Failed to unban the volunteer", "danger") 

340 return "Failed to unban the volunteer", 500 

341 

342 

343@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST']) 

344def addInterest(program_id, username): 

345 """ 

346 This function adds a program to the list of programs a user interested in 

347 program_id: the primary id of the program the student is adding interest of 

348 username: unique value of a user to correctly identify them 

349 """ 

350 try: 

351 success = addUserInterest(program_id, username) 

352 if success: 

353 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success") 

354 return "" 

355 else: 

356 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger") 

357 

358 except Exception as e: 

359 print(e) 

360 return "Error Updating Interest", 500 

361 

362@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST']) 

363def removeInterest(program_id, username): 

364 """ 

365 This function removes a program to the list of programs a user interested in 

366 program_id: the primary id of the program the student is adding interest of 

367 username: unique value of a user to correctly identify them 

368 """ 

369 try: 

370 removed = removeUserInterest(program_id, username) 

371 if removed: 

372 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success") 

373 return "" 

374 else: 

375 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger") 

376 except Exception as e: 

377 print(e) 

378 return "Error Updating Interest", 500 

379 

380@main_bp.route('/rsvpForEvent', methods = ['POST']) 

381def volunteerRegister(): 

382 """ 

383 This function selects the user ID and event ID and registers the user 

384 for the event they have clicked register for. 

385 """ 

386 event = Event.get_by_id(request.form['id']) 

387 program = event.program 

388 user = g.current_user 

389 

390 isAdded = checkUserRsvp(user, event) 

391 isEligible = isEligibleForProgram(program, user) 

392 listOfRequirements = unattendedRequiredEvents(program, user) 

393 

394 personAdded = False 

395 if isEligible: 

396 personAdded = addPersonToEvent(user, event) 

397 if personAdded and listOfRequirements: 

398 reqListToString = ', '.join(listOfRequirements) 

399 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success") 

400 elif personAdded: 

401 flash("Successfully registered for event!","success") 

402 else: 

403 flash(f"RSVP Failed due to an unknown error.", "danger") 

404 else: 

405 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger") 

406 

407 

408 if 'from' in request.form: 

409 if request.form['from'] == 'ajax': 

410 return '' 

411 return redirect(url_for("admin.eventDisplay", eventId=event.id)) 

412 

413@main_bp.route('/rsvpRemove', methods = ['POST']) 

414def RemoveRSVP(): 

415 """ 

416 This function deletes the user ID and event ID from database when RemoveRSVP is clicked 

417 """ 

418 eventData = request.form 

419 event = Event.get_by_id(eventData['id']) 

420 

421 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event) 

422 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd" 

423 currentRsvpParticipant.delete_instance() 

424 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.") 

425 flash("Successfully unregistered for event!", "success") 

426 if 'from' in eventData: 

427 if eventData['from'] == 'ajax': 

428 return '' 

429 return redirect(url_for("admin.eventDisplay", eventId=event.id)) 

430 

431@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET']) 

432def serviceTranscript(username): 

433 user = User.get_or_none(User.username == username) 

434 if user is None: 

435 abort(404) 

436 if user != g.current_user and not g.current_user.isAdmin: 

437 abort(403) 

438 

439 slCourses = getSlCourseTranscript(username) 

440 totalHours = getTotalHours(username) 

441 allEventTranscript = getProgramTranscript(username) 

442 startDate = getStartYear(username) 

443 return render_template('main/serviceTranscript.html', 

444 allEventTranscript = allEventTranscript, 

445 slCourses = slCourses.objects(), 

446 totalHours = totalHours, 

447 startDate = startDate, 

448 userData = user) 

449 

450@main_bp.route('/searchUser/<query>', methods = ['GET']) 

451def searchUser(query): 

452 

453 category= request.args.get("category") 

454 

455 '''Accepts user input and queries the database returning results that matches user search''' 

456 try: 

457 query = query.strip() 

458 search = query.upper() 

459 splitSearch = search.split() 

460 searchResults = searchUsers(query,category) 

461 return searchResults 

462 except Exception as e: 

463 print(e) 

464 return "Error in searching for user", 500 

465 

466@main_bp.route('/contributors',methods = ['GET']) 

467def contributors(): 

468 return render_template("/contributors.html") 

469 

470@main_bp.route('/proposalReview/', methods = ['GET', 'POST']) 

471def reviewProposal(): 

472 """ 

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

474 """ 

475 courseID=request.form 

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

477 instructors_data=course.courseInstructors 

478 return render_template('/main/reviewproposal.html', 

479 course=course, 

480 instructors_data=instructors_data) 

481 

482@main_bp.route('/updateDietInformation', methods = ['GET', 'POST']) 

483def getDietInfo(): 

484 dietaryInfo = request.form 

485 user = dietaryInfo["user"] 

486 dietInfo = dietaryInfo["dietInfo"] 

487 

488 if (g.current_user.username == user) or g.current_user.isAdmin: 

489 updateDietInfo(user, dietInfo) 

490 userInfo = User.get(User.username == user) 

491 if len(dietInfo) > 0: 

492 createAdminLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None 

493 else: 

494 createAdminLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.") 

495 

496 

497 return " "