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

429 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-09-04 14:55 +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.eventParticipant import EventParticipant 

28from app.models.courseInstructor import CourseInstructor 

29from app.models.backgroundCheckType import BackgroundCheckType 

30 

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

32from app.logic.transcript import * 

33from app.logic.loginManager import logout 

34from app.logic.searchUsers import searchUsers 

35from app.logic.utils import selectSurroundingTerms 

36from app.logic.celtsLabor import getCeltsLaborHistory 

37from app.logic.createLogs import createRsvpLog, createActivityLog 

38from app.logic.certification import getCertRequirementsWithCompletion 

39from app.logic.landingPage import getManagerProgramDict, getActiveEventTab 

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

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

42from app.logic.users import * 

43 

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

45def redirectToLogout(): 

46 return redirect(logout()) 

47 

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

49def landingPage(): 

50 

51 managerProgramDict = getManagerProgramDict(g.current_user) 

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

53 

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

55 .join(Event) 

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

57 .distinct() 

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

59 # Limit returned list to events in the future 

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

61 

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

63 managerProgramDict=managerProgramDict, 

64 term=g.current_term, 

65 programsWithEventsList = futureEvents) 

66 

67 

68 

69 

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

71def goToEventsList(programID): 

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

73 

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

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

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

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

78def events(selectedTerm, activeTab, programID): 

79 

80 currentTime = datetime.datetime.now() 

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

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

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

84 

85 term = g.current_term 

86 if selectedTerm: 

87 term = selectedTerm 

88 

89 # Make sure we have a Term object 

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

91 if term is None: 

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

93 

94 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

95 volunteerOpportunities = getVolunteerOpportunities(term) 

96 countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) 

97 countPastVolunteerOpportunities = getPastVolunteerOpportunitiesCount(term, currentTime) 

98 trainingEvents = getTrainingEvents(term, g.current_user) 

99 engagementEvents = getEngagementEvents(term) 

100 bonnerEvents = getBonnerEvents(term) 

101 celtsLabor = getCeltsLabor(term) 

102 

103 managersProgramDict = getManagerProgramDict(g.current_user) 

104 

105 # Fetch toggle state from session  

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

107 

108 # compile all volunteer opportunitiesevents into one list 

109 studentEvents = [] 

110 for studentEvent in volunteerOpportunities.values(): 

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

112 

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

114 volunteerOpportunitiesCount: int = len(studentEvents) 

115 countUpcomingVolunteerOpportunitiesCount: int = len(countUpcomingVolunteerOpportunities) 

116 countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities) 

117 trainingEventsCount: int = len(trainingEvents) 

118 engagementEventsCount: int = len(engagementEvents) 

119 bonnerEventsCount: int = len(bonnerEvents) 

120 celtsLaborCount: int = len(celtsLabor) 

121 

122 # gets only upcoming events to display in indicators 

123 if (toggleState == 'unchecked'): 

124 for event in trainingEvents: 

125 if event.isPastEnd: 

126 trainingEventsCount -= 1 

127 for event in engagementEvents: 

128 if event.isPastEnd: 

129 engagementEventsCount -= 1 

130 for event in bonnerEvents: 

131 if event.isPastEnd: 

132 bonnerEventsCount -= 1 

133 for event in celtsLabor: 

134 if event.isPastEnd: 

135 celtsLaborCount -= 1 

136 

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

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

139 return jsonify({ 

140 "volunteerOpportunitiesCount": volunteerOpportunitiesCount, 

141 "countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount, 

142 "countUpcomingVolunteerOpportunitiesCount": countUpcomingVolunteerOpportunitiesCount, 

143 "trainingEventsCount": trainingEventsCount, 

144 "engagementEventsCount": engagementEventsCount, 

145 "bonnerEventsCount": bonnerEventsCount, 

146 "celtsLaborCount": celtsLaborCount, 

147 "toggleStatus": toggleState 

148 }) 

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

150 selectedTerm = term, 

151 volunteerOpportunities = volunteerOpportunities, 

152 trainingEvents = trainingEvents, 

153 engagementEvents = engagementEvents, 

154 bonnerEvents = bonnerEvents, 

155 celtsLabor = celtsLabor, 

156 listOfTerms = listOfTerms, 

157 rsvpedEventsID = rsvpedEventsID, 

158 currentEventRsvpAmount = currentEventRsvpAmount, 

159 currentTime = currentTime, 

160 user = g.current_user, 

161 activeTab = activeTab, 

162 programID = int(programID), 

163 managersProgramDict = managersProgramDict, 

164 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, 

165 countPastVolunteerOpportunities = countPastVolunteerOpportunities, 

166 toggleState = toggleState, 

167 ) 

168 

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

170def viewUsersProfile(username): 

171 """ 

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

173 """ 

174 try: 

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

176 except Exception as e: 

177 if g.current_user.isAdmin: 

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

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

180 else: 

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

182 

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

184 upcomingEvents = getUpcomingEventsForUser(volunteer) 

185 participatedEvents = getParticipatedEventsForUser(volunteer) 

186 programs = Program.select() 

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

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

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

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

191 

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

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

194 

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

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

197 

198 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

199 backgroundTypes = list(BackgroundCheckType.select()) 

200 

201 

202 eligibilityTable = [] 

203 

204 for program in programs: 

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

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

207 .where(ProgramBan.user == volunteer, 

208 ProgramBan.program == program, 

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

210 onTranscriptQuery = list(ProgramBan.select(ProgramBan) 

211 .where(ProgramBan.user == volunteer, 

212 ProgramBan.program == program, 

213 ProgramBan.unbanNote.is_null(), 

214 ProgramBan.removeFromTranscript == 0)) 

215 

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

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

218 try: 

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

220 except KeyError: 

221 allTrainingsComplete = False 

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

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

224 "completedTraining": allTrainingsComplete, 

225 "trainingList": userParticipatedTrainingEvents, 

226 "isNotBanned": (not banNotes), 

227 "banNote": noteForDict, 

228 "onTranscript": onTranscript}), 

229 

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

231 

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

233 

234 managersProgramDict = getManagerProgramDict(g.current_user) 

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

236 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

237 handbookOverdue = getHandbookStatus(volunteer) 

238 

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

240 

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

242 username=username, 

243 programs = programs, 

244 programsInterested = programsInterested, 

245 upcomingEvents = upcomingEvents, 

246 participatedEvents = participatedEvents, 

247 rsvpedEvents = rsvpedEvents, 

248 permissionPrograms = permissionPrograms, 

249 eligibilityTable = eligibilityTable, 

250 volunteer = volunteer, 

251 backgroundTypes = backgroundTypes, 

252 allBackgroundHistory = allBackgroundHistory, 

253 currentDateTime = datetime.datetime.now(), 

254 profileNotes = profileNotes, 

255 bonnerRequirements = bonnerRequirements, 

256 managersList = managersList, 

257 participatedInLabor = getCeltsLaborHistory(volunteer), 

258 totalSustainedEngagements = totalSustainedEngagements, 

259 handbookOverdue = handbookOverdue, 

260 training = training, 

261 ) 

262 abort(403) 

263 

264def getHandbookStatus(volunteer): 

265 handbookOverdue = False 

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

267 handbookOverdue = True 

268 return handbookOverdue 

269 

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

271def emergencyContactInfo(username): 

272 """ 

273 This loads the Emergency Contact Page 

274 """ 

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

276 abort(403) 

277 

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

279 

280 if request.method == 'GET': 

281 readOnly = g.current_user.username != username 

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

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

284 username=username, 

285 contactInfo=contactInfo, 

286 readOnly=readOnly 

287 ) 

288 

289 elif request.method == 'POST': 

290 if g.current_user.username != username: 

291 abort(403) 

292 

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

294 if not rowsUpdated: 

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

296 

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

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

299 

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

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

302 else: 

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

304 

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

306def insuranceInfo(username): 

307 """ 

308 This loads the Insurance Information Page 

309 """ 

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

311 abort(403) 

312 

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

314 

315 if request.method == 'GET': 

316 readOnly = g.current_user.username != username 

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

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

319 username=username, 

320 userInsuranceInfo=userInsuranceInfo, 

321 readOnly=readOnly 

322 ) 

323 

324 # Save the form data 

325 elif request.method == 'POST': 

326 if g.current_user.username != username: 

327 abort(403) 

328 

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

330 

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

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

333 

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

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

336 else: 

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

338 

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

340def travelForm(username): 

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

342 abort(403) 

343 

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

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

346 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

348 if not list(user): 

349 abort(404) 

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

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

352 

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

354 userList = userList 

355 ) 

356 

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

358def eventTravelForm(eventID): 

359 try: 

360 event = Event.get_by_id(eventID) 

361 except DoesNotExist as e: 

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

363 abort(404) 

364 

365 if not (g.current_user.isCeltsAdmin): 

366 abort(403) 

367 

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

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

370 usernameList = usernameList.copy() 

371 userList = [] 

372 for username in usernameList: 

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

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

375 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

377 if not list(username): 

378 abort(404) 

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

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

381 userList.append(userData) 

382 

383 

384 else: 

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

386 

387 

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

389 usernameList = usernameList, 

390 userList = userList, 

391 ) 

392 

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

394def addNote(): 

395 try: 

396 noteData = getProfileNoteData( 

397 request.form, 

398 includeUsername=True, 

399 ) 

400 addProfileNote(**noteData) 

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

402 except Exception as error: 

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

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

405 return str(error), 500 

406 return "success" 

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

408def editProfileNote(username): 

409 try: 

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

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

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

413 abort(403) 

414 updateProfileNote(**noteData) 

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

416 except Exception as error: 

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

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

419 return str(error), 500 

420 return "success" 

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

422def deleteNote(username): 

423 """ 

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

425 """ 

426 try: 

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

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

429 except Exception as e: 

430 print("Error deleting note", e) 

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

432 return "success" 

433 

434# ===========================Ban=============================================== 

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

436def ban(program_id, username): 

437 """ 

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

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

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

441 """ 

442 postData = request.form 

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

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

445 

446 try: 

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

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

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

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

451 return "Successfully marked the volunteer as ineligible." 

452 except Exception as e: 

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

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

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

456 

457# ===========================Unban=============================================== 

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

459def unban(program_id, username): 

460 """ 

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

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

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

464 """ 

465 postData = request.form 

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

467 try: 

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

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

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

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

472 return "Successfully marked the volunteer as eligible" 

473 

474 except Exception as e: 

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

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

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

478 

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

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

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

482 """ 

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

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

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

486 """ 

487 showFlash = False if showFlash == "False" else True 

488 try: 

489 success = addUserInterest(program_id, username) 

490 if success: 

491 if bool(showFlash): 

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

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

494 else: 

495 if bool(showFlash): 

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

497 

498 except Exception as e: 

499 print(e) 

500 return "Error Updating Interest", 500 

501 

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

503def removeInterest(program_id, username): 

504 """ 

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

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

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

508 """ 

509 try: 

510 removed = removeUserInterest(program_id, username) 

511 if removed: 

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

513 return "" 

514 else: 

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

516 except Exception as e: 

517 print(e) 

518 return "Error Updating Interest", 500 

519 

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

521def volunteerRegister(): 

522 """ 

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

524 for the event they have clicked register for. 

525 """ 

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

527 program = event.program 

528 user = g.current_user 

529 now = datetime.datetime.now() 

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

531 

532 personAdded = False 

533 if isEligible: 

534 personAdded = addPersonToEvent(user, event) 

535 if personAdded: 

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

537 else: 

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

539 else: 

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

541 

542 if 'from' in request.form: 

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

544 return '' 

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

546 

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

548def RemoveRSVP(): 

549 """ 

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

551 """ 

552 eventData = request.form 

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

554 

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

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

557 currentRsvpParticipant.delete_instance() 

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

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

560 if 'from' in eventData: 

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

562 return '' 

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

564 

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

566def serviceTranscript(username): 

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

568 if user is None: 

569 abort(404) 

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

571 abort(403) 

572 

573 slCourses = getSlCourseTranscript(username) 

574 totalHours = getTotalHours(username) 

575 allEventTranscript = getProgramTranscript(username) 

576 zeroHourEvents = getZeroHourEvents(username) 

577 startDate = getStartYear(username) 

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

579 allEventTranscript = allEventTranscript, 

580 zeroHourEvents = zeroHourEvents, 

581 slCourses = slCourses.objects(), 

582 totalHours = totalHours, 

583 startDate = startDate, 

584 userData = user) 

585 

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

587def updateTranscript(username, program_id): 

588 # Check user permissions 

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

590 if user is None: 

591 abort(404) 

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

593 abort(403) 

594 

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

596 data = request.json 

597 

598 # Retrieve removeFromTranscript value from the request data 

599 removeFromTranscript = data.get('removeFromTranscript') 

600 

601 # Update the ProgramBan object matching the program_id and username 

602 try: 

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

604 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

605 bannedProgramForUser.save() 

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

607 except ProgramBan.DoesNotExist: 

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

609 

610 

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

612def searchUser(query): 

613 

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

615 

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

617 try: 

618 query = query.strip() 

619 search = query.upper() 

620 splitSearch = search.split() 

621 searchResults = searchUsers(query,category) 

622 return searchResults 

623 except Exception as e: 

624 print(e) 

625 return "Error in searching for user", 500 

626 

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

628def contributors(): 

629 return render_template("/contributors.html") 

630 

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

632def getDietInfo(): 

633 dietaryInfo = request.form 

634 user = dietaryInfo["user"] 

635 dietInfo = dietaryInfo["dietInfo"] 

636 

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

638 updateDietInfo(user, dietInfo) 

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

640 if len(dietInfo) > 0: 

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

642 else: 

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

644 

645 

646 return " " 

647 

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

649def indicateMinorInterest(username): 

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

651 data = request.get_json() 

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

653 

654 toggleMinorInterest(username, isAdding) 

655 

656 else: 

657 abort(403) 

658 

659 return "" 

660 

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

662def updateMinorDeclaration(username): 

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

664 declareMinorInterest(username) 

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

666 else: 

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

668 abort(403) 

669 

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

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

672 

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

674def extravaganza(): 

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

676 Program.programName != "Hunger Initiatives", 

677 Program.programName != "Bonner Scholars") 

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

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

680 

681 upcomingAllVolunteers = Event.select().join(Term).where(Event.isAllVolunteerTraining, Term.academicYear == g.current_term.academicYear) 

682 for training in upcomingAllVolunteers: 

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

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

685 

686 upcomingTrainings = Event.select().join(Term).where(Event.isTraining, Term.academicYear == g.current_term.academicYear) 

687 

688 for training in upcomingTrainings: 

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

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

691 

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

693 programs = programs, 

694 programsInterested = programsInterested, 

695 upcomingTrainings = upcomingTrainings, 

696 upcomingAllVolunteers = upcomingAllVolunteers 

697 )