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

428 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-09-09 18:24 +0000

1import json 

2import datetime 

3from peewee import JOIN, DoesNotExist 

4from http import cookies 

5from playhouse.shortcuts import model_to_dict 

6from flask import request, render_template, jsonify, g, abort, flash, redirect, url_for, make_response, session, request 

7from dateutil.relativedelta import relativedelta 

8 

9 

10from app.controllers.main import main_bp 

11from app import app 

12from app.models.term import Term 

13from app.models.user import User 

14from app.models.note import Note 

15from app.models.event import Event 

16from app.models.program import Program 

17from app.models.interest import Interest 

18from app.models.eventRsvp import EventRsvp 

19from app.models.celtsLabor import CeltsLabor 

20from app.models.programBan import ProgramBan 

21from app.models.profileNote import ProfileNote 

22from app.models.insuranceInfo import InsuranceInfo 

23from app.models.certification import Certification 

24from app.models.programManager import ProgramManager 

25from app.models.backgroundCheck import BackgroundCheck 

26from app.models.emergencyContact import EmergencyContact 

27from app.models.courseInstructor import CourseInstructor 

28from app.models.backgroundCheckType import BackgroundCheckType 

29 

30from app.logic.events import getUpcomingEventsForUser, getParticipatedEventsForUser, getTrainingEvents, getEventRsvpCountsForTerm, getUpcomingVolunteerOpportunitiesCount, getVolunteerOpportunities, getBonnerEvents, getCeltsLabor, getEngagementEvents, getPastVolunteerOpportunitiesCount 

31from app.logic.transcript import * 

32from app.logic.loginManager import logout 

33from app.logic.searchUsers import searchUsers 

34from app.logic.utils import selectSurroundingTerms 

35from app.logic.celtsLabor import getCeltsLaborHistory 

36from app.logic.createLogs import createRsvpLog, createActivityLog 

37from app.logic.certification import getCertRequirementsWithCompletion 

38from app.logic.landingPage import getManagerProgramDict, getActiveEventTab 

39from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal 

40from app.logic.participants import hasGoneToTraining, unattendedRequiredEvents, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent 

41from app.logic.users import * 

42 

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

44def redirectToLogout(): 

45 return redirect(logout()) 

46 

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

48def landingPage(): 

49 

50 managerProgramDict = getManagerProgramDict(g.current_user) 

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

52 

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

54 .join(Event) 

55 .where((Event.term == g.current_term) & (Event.isCanceled == False)) 

56 .distinct() 

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

58 # Limit returned list to events in the future 

59 futureEvents = [p for p in programsWithEventsList if not p.event.isPastEnd] 

60 

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

62 managerProgramDict=managerProgramDict, 

63 term=g.current_term, 

64 programsWithEventsList = futureEvents) 

65 

66 

67 

68 

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

70def goToEventsList(programID): 

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

72 

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

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

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

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

77def events(selectedTerm, activeTab, programID): 

78 

79 currentTime = datetime.datetime.now() 

80 listOfTerms = Term.select().order_by(Term.termOrder) 

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

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

83 

84 term = g.current_term 

85 if selectedTerm: 

86 term = selectedTerm 

87 

88 # Make sure we have a Term object 

89 term = Term.get_or_none(Term.id == term) 

90 if term is None: 

91 term = Term.get(Term.isCurrentTerm == True) 

92 

93 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

94 volunteerOpportunities = getVolunteerOpportunities(term) 

95 countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) 

96 countPastVolunteerOpportunities = getPastVolunteerOpportunitiesCount(term, currentTime) 

97 trainingEvents = getTrainingEvents(term, g.current_user) 

98 engagementEvents = getEngagementEvents(term) 

99 bonnerEvents = getBonnerEvents(term) 

100 celtsLabor = getCeltsLabor(term) 

101 

102 managersProgramDict = getManagerProgramDict(g.current_user) 

103 

104 # Fetch toggle state from session  

105 toggleState = request.args.get('toggleState', 'unchecked') 

106 

107 # compile all volunteer opportunitiesevents into one list 

108 studentEvents = [] 

109 for studentEvent in volunteerOpportunities.values(): 

110 studentEvents += studentEvent # add all contents of studentEvent to the studentEvents list 

111 

112 # Get the count of all term events for each category to display in the event list page. 

113 volunteerOpportunitiesCount: int = len(studentEvents) 

114 countUpcomingVolunteerOpportunitiesCount: int = len(countUpcomingVolunteerOpportunities) 

115 countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities) 

116 trainingEventsCount: int = len(trainingEvents) 

117 engagementEventsCount: int = len(engagementEvents) 

118 bonnerEventsCount: int = len(bonnerEvents) 

119 celtsLaborCount: int = len(celtsLabor) 

120 

121 # gets only upcoming events to display in indicators 

122 if (toggleState == 'unchecked'): 

123 for event in trainingEvents: 

124 if event.isPastEnd: 

125 trainingEventsCount -= 1 

126 for event in engagementEvents: 

127 if event.isPastEnd: 

128 engagementEventsCount -= 1 

129 for event in bonnerEvents: 

130 if event.isPastEnd: 

131 bonnerEventsCount -= 1 

132 for event in celtsLabor: 

133 if event.isPastEnd: 

134 celtsLaborCount -= 1 

135 

136 # Handle ajax request for Event category header number notifiers and toggle 

137 if request.headers.get('X-Requested-With') == 'XMLHttpRequest': 

138 return jsonify({ 

139 "volunteerOpportunitiesCount": volunteerOpportunitiesCount, 

140 "countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount, 

141 "countUpcomingVolunteerOpportunitiesCount": countUpcomingVolunteerOpportunitiesCount, 

142 "trainingEventsCount": trainingEventsCount, 

143 "engagementEventsCount": engagementEventsCount, 

144 "bonnerEventsCount": bonnerEventsCount, 

145 "celtsLaborCount": celtsLaborCount, 

146 "toggleStatus": toggleState 

147 }) 

148 return render_template("/events/eventList.html", 

149 selectedTerm = term, 

150 volunteerOpportunities = volunteerOpportunities, 

151 trainingEvents = trainingEvents, 

152 engagementEvents = engagementEvents, 

153 bonnerEvents = bonnerEvents, 

154 celtsLabor = celtsLabor, 

155 listOfTerms = listOfTerms, 

156 rsvpedEventsID = rsvpedEventsID, 

157 currentEventRsvpAmount = currentEventRsvpAmount, 

158 currentTime = currentTime, 

159 user = g.current_user, 

160 activeTab = activeTab, 

161 programID = int(programID), 

162 managersProgramDict = managersProgramDict, 

163 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, 

164 countPastVolunteerOpportunities = countPastVolunteerOpportunities, 

165 toggleState = toggleState, 

166 ) 

167 

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

169def viewUsersProfile(username): 

170 """ 

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

172 """ 

173 try: 

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

175 except Exception as e: 

176 if g.current_user.isAdmin: 

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

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

179 else: 

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

181 

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

183 upcomingEvents = getUpcomingEventsForUser(volunteer) 

184 participatedEvents = getParticipatedEventsForUser(volunteer) 

185 programs = Program.select() 

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

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

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

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

190 

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

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

193 

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

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

196 

197 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

198 backgroundTypes = list(BackgroundCheckType.select()) 

199 

200 

201 eligibilityTable = [] 

202 

203 for program in programs: 

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

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

206 .where(ProgramBan.user == volunteer, 

207 ProgramBan.program == program, 

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

209 onTranscriptQuery = list(ProgramBan.select(ProgramBan) 

210 .where(ProgramBan.user == volunteer, 

211 ProgramBan.program == program, 

212 ProgramBan.unbanNote.is_null(), 

213 ProgramBan.removeFromTranscript == 0)) 

214 

215 onTranscript = True if len(onTranscriptQuery) > 0 else False 

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

217 try: 

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

219 except KeyError: 

220 allTrainingsComplete = False 

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

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

223 "completedTraining": allTrainingsComplete, 

224 "trainingList": userParticipatedTrainingEvents, 

225 "isNotBanned": (not banNotes), 

226 "banNote": noteForDict, 

227 "onTranscript": onTranscript}), 

228 

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

230 

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

232 

233 managersProgramDict = getManagerProgramDict(g.current_user) 

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

235 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

236 

237 handbookOverdue = getHandbookStatus(volunteer) 

238 training = hasGoneToTraining(g.current_user, g.current_term) 

239 

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

241 username=username, 

242 programs = programs, 

243 programsInterested = programsInterested, 

244 upcomingEvents = upcomingEvents, 

245 participatedEvents = participatedEvents, 

246 rsvpedEvents = rsvpedEvents, 

247 permissionPrograms = permissionPrograms, 

248 eligibilityTable = eligibilityTable, 

249 volunteer = volunteer, 

250 backgroundTypes = backgroundTypes, 

251 allBackgroundHistory = allBackgroundHistory, 

252 currentDateTime = datetime.datetime.now(), 

253 profileNotes = profileNotes, 

254 bonnerRequirements = bonnerRequirements, 

255 managersList = managersList, 

256 participatedInLabor = getCeltsLaborHistory(volunteer), 

257 totalSustainedEngagements = totalSustainedEngagements, 

258 handbookOverdue = handbookOverdue, 

259 training = training, 

260 ) 

261 abort(403) 

262 

263def getHandbookStatus(volunteer): 

264 handbookOverdue = False 

265 if not volunteer.signatureTerm or volunteer.signatureTerm.academicYear != g.current_term.academicYear: 

266 handbookOverdue = True 

267 return handbookOverdue 

268 

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

270def emergencyContactInfo(username): 

271 """ 

272 This loads the Emergency Contact Page 

273 """ 

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

275 abort(403) 

276 

277 user = User.get(User.username == username) 

278 

279 if request.method == 'GET': 

280 readOnly = g.current_user.username != username 

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

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

283 username=username, 

284 contactInfo=contactInfo, 

285 readOnly=readOnly 

286 ) 

287 

288 elif request.method == 'POST': 

289 if g.current_user.username != username: 

290 abort(403) 

291 

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

293 if not rowsUpdated: 

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

295 

296 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s emergency contact information.") 

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

298 

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

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

301 else: 

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

303 

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

305def insuranceInfo(username): 

306 """ 

307 This loads the Insurance Information Page 

308 """ 

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

310 abort(403) 

311 

312 user = User.get(User.username == username) 

313 

314 if request.method == 'GET': 

315 readOnly = g.current_user.username != username 

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

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

318 username=username, 

319 userInsuranceInfo=userInsuranceInfo, 

320 readOnly=readOnly 

321 ) 

322 

323 # Save the form data 

324 elif request.method == 'POST': 

325 if g.current_user.username != username: 

326 abort(403) 

327 

328 InsuranceInfo.replace({**request.form, "user": username}).execute() 

329 

330 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.") 

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

332 

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

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

335 else: 

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

337 

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

339def travelForm(username): 

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

341 abort(403) 

342 

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

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

345 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

347 if not list(user): 

348 abort(404) 

349 userList = list(user.dicts())[0] 

350 userList = [{key: value if value else '' for (key, value) in userList.items()}] 

351 

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

353 userList = userList 

354 ) 

355 

356@main_bp.route('/event/<eventID>/travelForm', methods=['GET', 'POST']) 

357def eventTravelForm(eventID): 

358 try: 

359 event = Event.get_by_id(eventID) 

360 except DoesNotExist as e: 

361 print(f"No event found for {eventID}", e) 

362 abort(404) 

363 

364 if not (g.current_user.isCeltsAdmin): 

365 abort(403) 

366 

367 if request.method == "POST" and request.form.getlist("username") != []: 

368 usernameList = request.form.getlist("username") 

369 usernameList = usernameList.copy() 

370 userList = [] 

371 for username in usernameList: 

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

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

374 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

376 if not list(username): 

377 abort(404) 

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

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

380 userList.append(userData) 

381 

382 

383 else: 

384 return redirect(f"/event/{eventID}/volunteer_details") 

385 

386 

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

388 usernameList = usernameList, 

389 userList = userList, 

390 ) 

391 

392@main_bp.route("/profile/addNote", methods=["POST"]) 

393def addNote(): 

394 try: 

395 noteData = getProfileNoteData( 

396 request.form, 

397 includeUsername=True, 

398 ) 

399 addProfileNote(**noteData) 

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

401 except Exception as error: 

402 print("Error adding profile note:", error) 

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

404 return str(error), 500 

405 return "success" 

406@main_bp.route("/<username>/editNote", methods=["POST"]) 

407def editProfileNote(username): 

408 try: 

409 noteData = getProfileNoteData( request.form, includeId=True, ) 

410 profileNote = ProfileNote.get_by_id(noteData["profileNoteID"] ) 

411 if (profileNote.user.username != username or (profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin) ): 

412 abort(403) 

413 updateProfileNote(**noteData) 

414 flash("Successfully updated profile note", "success") 

415 except Exception as error: 

416 print("Error updating profile note:", error) 

417 flash("Failed to update profile note", "danger") 

418 return str(error), 500 

419 return "success" 

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

421def deleteNote(username): 

422 """ 

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

424 """ 

425 try: 

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

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

428 except Exception as e: 

429 print("Error deleting note", e) 

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

431 return "success" 

432 

433# ===========================Ban=============================================== 

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

435def ban(program_id, username): 

436 """ 

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

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

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

440 """ 

441 postData = request.form 

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

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

444 

445 try: 

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

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

448 flash("Successfully marked the volunteer as ineligible", "success") 

449 createActivityLog(f'Marked {username} as ineligible from {programInfo.programName} until {banEndDate}.') 

450 return "Successfully marked the volunteer as ineligible." 

451 except Exception as e: 

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

453 flash("Failed to mark the volunteer as ineligible", "danger") 

454 return "Failed to mark the volunteer as ineligible", 500 

455 

456# ===========================Unban=============================================== 

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

458def unban(program_id, username): 

459 """ 

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

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

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

463 """ 

464 postData = request.form 

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

466 try: 

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

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

469 createActivityLog(f'marked {username} as eligible from {programInfo.programName}.') 

470 flash("Successfully marked the volunteer as eligible", "success") 

471 return "Successfully marked the volunteer as eligible" 

472 

473 except Exception as e: 

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

475 flash("Failed to mark the volunteer as eligible", "danger") 

476 return "Failed to mark the volunteer as eligible", 500 

477 

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

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

480def addInterest(program_id, username, showFlash = True): 

481 """ 

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

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

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

485 """ 

486 showFlash = False if showFlash == "False" else True 

487 try: 

488 success = addUserInterest(program_id, username) 

489 if success: 

490 if bool(showFlash): 

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

492 return jsonify(model_to_dict(User.get_or_none(User.username == username))) 

493 else: 

494 if bool(showFlash): 

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

496 

497 except Exception as e: 

498 print(e) 

499 return "Error Updating Interest", 500 

500 

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

502def removeInterest(program_id, username): 

503 """ 

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

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

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

507 """ 

508 try: 

509 removed = removeUserInterest(program_id, username) 

510 if removed: 

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

512 return "" 

513 else: 

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

515 except Exception as e: 

516 print(e) 

517 return "Error Updating Interest", 500 

518 

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

520def volunteerRegister(): 

521 """ 

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

523 for the event they have clicked register for. 

524 """ 

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

526 program = event.program 

527 user = g.current_user 

528 now = datetime.datetime.now() 

529 isEligible = False if isBannedFromEvent(user, event) else True 

530 

531 personAdded = False 

532 if isEligible: 

533 personAdded = addPersonToEvent(user, event) 

534 if personAdded: 

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

536 else: 

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

538 else: 

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

540 

541 if 'from' in request.form: 

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

543 return '' 

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

545 

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

547def RemoveRSVP(): 

548 """ 

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

550 """ 

551 eventData = request.form 

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

553 

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

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

556 currentRsvpParticipant.delete_instance() 

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

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

559 if 'from' in eventData: 

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

561 return '' 

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

563 

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

565def serviceTranscript(username): 

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

567 if user is None: 

568 abort(404) 

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

570 abort(403) 

571 

572 slCourses = getSlCourseTranscript(username) 

573 totalHours = getTotalHours(username) 

574 allEventTranscript = getProgramTranscript(username) 

575 zeroHourEvents = getZeroHourEvents(username) 

576 startDate = getStartYear(username) 

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

578 allEventTranscript = allEventTranscript, 

579 zeroHourEvents = zeroHourEvents, 

580 slCourses = slCourses.objects(), 

581 totalHours = totalHours, 

582 startDate = startDate, 

583 userData = user) 

584 

585@main_bp.route('/profile/<username>/updateTranscript/<program_id>', methods=['POST']) 

586def updateTranscript(username, program_id): 

587 # Check user permissions 

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

589 if user is None: 

590 abort(404) 

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

592 abort(403) 

593 

594 # Get the data sent from the client-side JavaScript 

595 data = request.json 

596 

597 # Retrieve removeFromTranscript value from the request data 

598 removeFromTranscript = data.get('removeFromTranscript') 

599 

600 # Update the ProgramBan object matching the program_id and username 

601 try: 

602 bannedProgramForUser = ProgramBan.get((ProgramBan.program == program_id) & (ProgramBan.user == user) & (ProgramBan.unbanNote.is_null())) 

603 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

604 bannedProgramForUser.save() 

605 return jsonify({'status': 'success'}) 

606 except ProgramBan.DoesNotExist: 

607 return jsonify({'status': 'error', 'message': 'ProgramBan not found'}) 

608 

609 

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

611def searchUser(query): 

612 

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

614 

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

616 try: 

617 query = query.strip() 

618 search = query.upper() 

619 splitSearch = search.split() 

620 searchResults = searchUsers(query,category) 

621 return searchResults 

622 except Exception as e: 

623 print(e) 

624 return "Error in searching for user", 500 

625 

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

627def contributors(): 

628 return render_template("/contributors.html") 

629 

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

631def getDietInfo(): 

632 dietaryInfo = request.form 

633 user = dietaryInfo["user"] 

634 dietInfo = dietaryInfo["dietInfo"] 

635 

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

637 updateDietInfo(user, dietInfo) 

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

639 if len(dietInfo) > 0: 

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

641 else: 

642 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.") 

643 

644 

645 return " " 

646 

647@main_bp.route('/profile/<username>/indicateInterest', methods=['POST']) 

648def indicateMinorInterest(username): 

649 if g.current_user.isCeltsAdmin or g.current_user.username == username: 

650 data = request.get_json() 

651 isAdding = data.get("isAdding", False) 

652 

653 toggleMinorInterest(username, isAdding) 

654 

655 else: 

656 abort(403) 

657 

658 return "" 

659 

660@main_bp.route('/profile/<username>/updateMinorDeclaration', methods=["POST"]) 

661def updateMinorDeclaration(username): 

662 if g.current_user.isCeltsAdmin or g.current_user.username == username: 

663 declareMinorInterest(username) 

664 flash("Candidate minor successfully updated", "success") 

665 else: 

666 flash("Error updating candidate minor status", "danger") 

667 abort(403) 

668 

669 tab = request.args.get("tab", "interested") 

670 return redirect(url_for('admin.manageMinor', tab=tab)) 

671 

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

673def extravaganza(): 

674 programs = Program.select().where(Program.isOtherCeltsSponsored == False, 

675 Program.programName != "Hunger Initiatives", 

676 Program.programName != "Bonner Scholars") 

677 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == g.current_user) 

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

679 

680 upcomingAllVolunteers = (Event.select() 

681 .join(Term) 

682 .where(Event.isAllVolunteerTraining, 

683 Term.academicYear == g.current_term.academicYear, 

684 Event.deletionDate == None, 

685 Event.isCanceled == False) 

686 ) 

687 for training in upcomingAllVolunteers: 

688 training.startDate = training.startDate.strftime("%b %d") 

689 training.timeStart = training.timeStart.strftime("%I:%M %p") 

690 

691 upcomingTrainings = (Event.select() 

692 .join(Term) 

693 .where(Event.isTraining, 

694 Term.academicYear == g.current_term.academicYear, 

695 Event.deletionDate == None, 

696 Event.isCanceled == False) 

697 ) 

698 

699 for training in upcomingTrainings: 

700 training.startDate = training.startDate.strftime("%b %d") 

701 training.timeStart = training.timeStart.strftime("%I:%M %p") 

702 

703 return render_template("main/extravanganzaWelcome.html", 

704 programs = programs, 

705 programsInterested = programsInterested, 

706 upcomingTrainings = upcomingTrainings, 

707 upcomingAllVolunteers = upcomingAllVolunteers 

708 )