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

415 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-08-24 19:35 +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 addUserInterest, isBannedFromEvent, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo, trainedParticipants 

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

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

397 """ 

398 postData = request.form 

399 try: 

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

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

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

403 except Exception as e: 

404 print("Error adding note", e) 

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

406 return "Failed to add profile note", 500 

407 

408 

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

410def deleteNote(username): 

411 """ 

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

413 """ 

414 try: 

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

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

417 except Exception as e: 

418 print("Error deleting note", e) 

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

420 return "success" 

421 

422# ===========================Ban=============================================== 

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

424def ban(program_id, username): 

425 """ 

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

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

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

429 """ 

430 postData = request.form 

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

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

433 

434 try: 

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

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

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

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

439 return "Successfully marked the volunteer as ineligible." 

440 except Exception as e: 

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

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

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

444 

445# ===========================Unban=============================================== 

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

447def unban(program_id, username): 

448 """ 

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

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

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

452 """ 

453 postData = request.form 

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

455 try: 

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

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

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

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

460 return "Successfully marked the volunteer as eligible" 

461 

462 except Exception as e: 

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

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

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

466 

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

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

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

470 """ 

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

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

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

474 """ 

475 showFlash = False if showFlash == "False" else True 

476 try: 

477 success = addUserInterest(program_id, username) 

478 if success: 

479 if bool(showFlash): 

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

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

482 else: 

483 if bool(showFlash): 

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

485 

486 except Exception as e: 

487 print(e) 

488 return "Error Updating Interest", 500 

489 

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

491def removeInterest(program_id, username): 

492 """ 

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

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

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

496 """ 

497 try: 

498 removed = removeUserInterest(program_id, username) 

499 if removed: 

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

501 return "" 

502 else: 

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

504 except Exception as e: 

505 print(e) 

506 return "Error Updating Interest", 500 

507 

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

509def volunteerRegister(): 

510 """ 

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

512 for the event they have clicked register for. 

513 """ 

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

515 program = event.program 

516 user = g.current_user 

517 now = datetime.datetime.now() 

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

519 

520 personAdded = False 

521 if isEligible: 

522 personAdded = addPersonToEvent(user, event) 

523 if personAdded: 

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

525 else: 

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

527 else: 

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

529 

530 if 'from' in request.form: 

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

532 return '' 

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

534 

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

536def RemoveRSVP(): 

537 """ 

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

539 """ 

540 eventData = request.form 

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

542 

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

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

545 currentRsvpParticipant.delete_instance() 

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

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

548 if 'from' in eventData: 

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

550 return '' 

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

552 

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

554def serviceTranscript(username): 

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

556 if user is None: 

557 abort(404) 

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

559 abort(403) 

560 

561 slCourses = getSlCourseTranscript(username) 

562 totalHours = getTotalHours(username) 

563 allEventTranscript = getProgramTranscript(username) 

564 zeroHourEvents = getZeroHourEvents(username) 

565 startDate = getStartYear(username) 

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

567 allEventTranscript = allEventTranscript, 

568 zeroHourEvents = zeroHourEvents, 

569 slCourses = slCourses.objects(), 

570 totalHours = totalHours, 

571 startDate = startDate, 

572 userData = user) 

573 

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

575def updateTranscript(username, program_id): 

576 # Check user permissions 

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

578 if user is None: 

579 abort(404) 

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

581 abort(403) 

582 

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

584 data = request.json 

585 

586 # Retrieve removeFromTranscript value from the request data 

587 removeFromTranscript = data.get('removeFromTranscript') 

588 

589 # Update the ProgramBan object matching the program_id and username 

590 try: 

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

592 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

593 bannedProgramForUser.save() 

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

595 except ProgramBan.DoesNotExist: 

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

597 

598 

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

600def searchUser(query): 

601 

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

603 

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

605 try: 

606 query = query.strip() 

607 search = query.upper() 

608 splitSearch = search.split() 

609 searchResults = searchUsers(query,category) 

610 return searchResults 

611 except Exception as e: 

612 print(e) 

613 return "Error in searching for user", 500 

614 

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

616def contributors(): 

617 return render_template("/contributors.html") 

618 

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

620def getDietInfo(): 

621 dietaryInfo = request.form 

622 user = dietaryInfo["user"] 

623 dietInfo = dietaryInfo["dietInfo"] 

624 

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

626 updateDietInfo(user, dietInfo) 

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

628 if len(dietInfo) > 0: 

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

630 else: 

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

632 

633 

634 return " " 

635 

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

637def indicateMinorInterest(username): 

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

639 data = request.get_json() 

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

641 

642 toggleMinorInterest(username, isAdding) 

643 

644 else: 

645 abort(403) 

646 

647 return "" 

648 

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

650def updateMinorDeclaration(username): 

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

652 declareMinorInterest(username) 

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

654 else: 

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

656 abort(403) 

657 

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

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

660 

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

662def extravaganza(): 

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

664 Program.programName != "Hunger Initiatives", 

665 Program.programName != "Bonner Scholars") 

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

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

668 

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

670 for training in upcomingAllVolunteers: 

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

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

673 

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

675 

676 for training in upcomingTrainings: 

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

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

679 

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

681 programs = programs, 

682 programsInterested = programsInterested, 

683 upcomingTrainings = upcomingTrainings, 

684 upcomingAllVolunteers = upcomingAllVolunteers 

685 )