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

389 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-07-20 21:34 +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 

7 

8from app.controllers.main import main_bp 

9from app import app 

10from app.models.term import Term 

11from app.models.user import User 

12from app.models.note import Note 

13from app.models.event import Event 

14from app.models.program import Program 

15from app.models.interest import Interest 

16from app.models.eventRsvp import EventRsvp 

17from app.models.celtsLabor import CeltsLabor 

18from app.models.programBan import ProgramBan 

19from app.models.profileNote import ProfileNote 

20from app.models.insuranceInfo import InsuranceInfo 

21from app.models.certification import Certification 

22from app.models.programManager import ProgramManager 

23from app.models.backgroundCheck import BackgroundCheck 

24from app.models.emergencyContact import EmergencyContact 

25from app.models.eventParticipant import EventParticipant 

26from app.models.courseInstructor import CourseInstructor 

27from app.models.backgroundCheckType import BackgroundCheckType 

28 

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

30from app.logic.transcript import * 

31from app.logic.loginManager import logout 

32from app.logic.searchUsers import searchUsers 

33from app.logic.utils import selectSurroundingTerms 

34from app.logic.celtsLabor import getCeltsLaborHistory 

35from app.logic.createLogs import createRsvpLog, createActivityLog 

36from app.logic.certification import getCertRequirementsWithCompletion 

37from app.logic.landingPage import getManagerProgramDict, getActiveEventTab 

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

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

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

41 

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

43def redirectToLogout(): 

44 return redirect(logout()) 

45 

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

47def landingPage(): 

48 

49 managerProgramDict = getManagerProgramDict(g.current_user) 

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

51 

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

53 .join(Event) 

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

55 .distinct() 

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

57 # Limit returned list to events in the future 

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

59 

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

61 managerProgramDict=managerProgramDict, 

62 term=g.current_term, 

63 programsWithEventsList = futureEvents) 

64 

65 

66 

67 

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

69def goToEventsList(programID): 

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

71 

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

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

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

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

76def events(selectedTerm, activeTab, programID): 

77 

78 currentTime = datetime.datetime.now() 

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

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

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

82 

83 term = g.current_term 

84 if selectedTerm: 

85 term = selectedTerm 

86 

87 # Make sure we have a Term object 

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

89 if term is None: 

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

91 

92 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

93 volunteerOpportunities = getVolunteerOpportunities(term) 

94 countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) 

95 countPastVolunteerOpportunities = getPastVolunteerOpportunitiesCount(term, currentTime) 

96 trainingEvents = getTrainingEvents(term, g.current_user) 

97 engagementEvents = getEngagementEvents(term) 

98 bonnerEvents = getBonnerEvents(term) 

99 celtsLabor = getCeltsLabor(term) 

100 

101 managersProgramDict = getManagerProgramDict(g.current_user) 

102 

103 # Fetch toggle state from session  

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

105 

106 # compile all volunteer opportunitiesevents into one list 

107 studentEvents = [] 

108 for studentEvent in volunteerOpportunities.values(): 

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

110 

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

112 volunteerOpportunitiesCount: int = len(studentEvents) 

113 countUpcomingVolunteerOpportunitiesCount: int = len(countUpcomingVolunteerOpportunities) 

114 countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities) 

115 trainingEventsCount: int = len(trainingEvents) 

116 engagementEventsCount: int = len(engagementEvents) 

117 bonnerEventsCount: int = len(bonnerEvents) 

118 celtsLaborCount: int = len(celtsLabor) 

119 

120 # gets only upcoming events to display in indicators 

121 if (toggleState == 'unchecked'): 

122 for event in trainingEvents: 

123 if event.isPastEnd: 

124 trainingEventsCount -= 1 

125 for event in engagementEvents: 

126 if event.isPastEnd: 

127 engagementEventsCount -= 1 

128 for event in bonnerEvents: 

129 if event.isPastEnd: 

130 bonnerEventsCount -= 1 

131 for event in celtsLabor: 

132 if event.isPastEnd: 

133 celtsLaborCount -= 1 

134 

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

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

137 return jsonify({ 

138 "volunteerOpportunitiesCount": volunteerOpportunitiesCount, 

139 "countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount, 

140 "countUpcomingVolunteerOpportunitiesCount": countUpcomingVolunteerOpportunitiesCount, 

141 "trainingEventsCount": trainingEventsCount, 

142 "engagementEventsCount": engagementEventsCount, 

143 "bonnerEventsCount": bonnerEventsCount, 

144 "celtsLaborCount": celtsLaborCount, 

145 "toggleStatus": toggleState 

146 }) 

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

148 selectedTerm = term, 

149 volunteerOpportunities = volunteerOpportunities, 

150 trainingEvents = trainingEvents, 

151 engagementEvents = engagementEvents, 

152 bonnerEvents = bonnerEvents, 

153 celtsLabor = celtsLabor, 

154 listOfTerms = listOfTerms, 

155 rsvpedEventsID = rsvpedEventsID, 

156 currentEventRsvpAmount = currentEventRsvpAmount, 

157 currentTime = currentTime, 

158 user = g.current_user, 

159 activeTab = activeTab, 

160 programID = int(programID), 

161 managersProgramDict = managersProgramDict, 

162 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, 

163 countPastVolunteerOpportunities = countPastVolunteerOpportunities, 

164 toggleState = toggleState, 

165 ) 

166 

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

168def viewUsersProfile(username): 

169 """ 

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

171 """ 

172 try: 

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

174 except Exception as e: 

175 if g.current_user.isAdmin: 

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

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

178 else: 

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

180 

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

182 upcomingEvents = getUpcomingEventsForUser(volunteer) 

183 participatedEvents = getParticipatedEventsForUser(volunteer) 

184 programs = Program.select() 

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

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

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

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

189 

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

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

192 

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

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

195 

196 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

197 backgroundTypes = list(BackgroundCheckType.select()) 

198 

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 eventParticipant = list(EventParticipant.select(EventParticipant.hoursEarned) 

237 .join(Event) 

238 .where(EventParticipant.user == volunteer, EventParticipant.event == Event.id,) 

239 .order_by(Event.id.asc())) 

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 eventParticipant = eventParticipant 

260 ) 

261 abort(403) 

262 

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

264def emergencyContactInfo(username): 

265 """ 

266 This loads the Emergency Contact Page 

267 """ 

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

269 abort(403) 

270 

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

272 

273 if request.method == 'GET': 

274 readOnly = g.current_user.username != username 

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

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

277 username=username, 

278 contactInfo=contactInfo, 

279 readOnly=readOnly 

280 ) 

281 

282 elif request.method == 'POST': 

283 if g.current_user.username != username: 

284 abort(403) 

285 

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

287 if not rowsUpdated: 

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

289 

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

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

292 

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

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

295 else: 

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

297 

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

299def insuranceInfo(username): 

300 """ 

301 This loads the Insurance Information Page 

302 """ 

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

304 abort(403) 

305 

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

307 

308 if request.method == 'GET': 

309 readOnly = g.current_user.username != username 

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

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

312 username=username, 

313 userInsuranceInfo=userInsuranceInfo, 

314 readOnly=readOnly 

315 ) 

316 

317 # Save the form data 

318 elif request.method == 'POST': 

319 if g.current_user.username != username: 

320 abort(403) 

321 

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

323 

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

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

326 

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

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

329 else: 

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

331 

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

333def travelForm(username): 

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

335 abort(403) 

336 

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

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

339 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

341 if not list(user): 

342 abort(404) 

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

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

345 

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

347 userList = userList 

348 ) 

349 

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

351def eventTravelForm(eventID): 

352 try: 

353 event = Event.get_by_id(eventID) 

354 except DoesNotExist as e: 

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

356 abort(404) 

357 

358 if not (g.current_user.isCeltsAdmin): 

359 abort(403) 

360 

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

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

363 usernameList = usernameList.copy() 

364 userList = [] 

365 for username in usernameList: 

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

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

368 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

370 if not list(username): 

371 abort(404) 

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

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

374 userList.append(userData) 

375 

376 

377 else: 

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

379 

380 

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

382 usernameList = usernameList, 

383 userList = userList, 

384 ) 

385 

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

387def addNote(): 

388 """ 

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

390 """ 

391 postData = request.form 

392 try: 

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

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

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

396 except Exception as e: 

397 print("Error adding note", e) 

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

399 return "Failed to add profile note", 500 

400 

401 

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

403def deleteNote(username): 

404 """ 

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

406 """ 

407 try: 

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

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

410 except Exception as e: 

411 print("Error deleting note", e) 

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

413 return "success" 

414 

415# ===========================Ban=============================================== 

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

417def ban(program_id, username): 

418 """ 

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

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

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

422 """ 

423 postData = request.form 

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

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

426 

427 try: 

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

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

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

431 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.') 

432 return "Successfully banned the volunteer." 

433 except Exception as e: 

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

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

436 return "Failed to ban the volunteer", 500 

437 

438# ===========================Unban=============================================== 

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

440def unban(program_id, username): 

441 """ 

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

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

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

445 """ 

446 postData = request.form 

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

448 try: 

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

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

451 createActivityLog(f'Unbanned {username} from {programInfo.programName}.') 

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

453 return "Successfully unbanned the volunteer" 

454 

455 except Exception as e: 

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

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

458 return "Failed to unban the volunteer", 500 

459 

460 

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

462def addInterest(program_id, username): 

463 """ 

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

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

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

467 """ 

468 try: 

469 success = addUserInterest(program_id, username) 

470 if success: 

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

472 return "" 

473 else: 

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

475 

476 except Exception as e: 

477 print(e) 

478 return "Error Updating Interest", 500 

479 

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

481def removeInterest(program_id, username): 

482 """ 

483 This function removes 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 try: 

488 removed = removeUserInterest(program_id, username) 

489 if removed: 

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

491 return "" 

492 else: 

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

494 except Exception as e: 

495 print(e) 

496 return "Error Updating Interest", 500 

497 

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

499def volunteerRegister(): 

500 """ 

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

502 for the event they have clicked register for. 

503 """ 

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

505 program = event.program 

506 user = g.current_user 

507 

508 isEligible = isEligibleForProgram(program, user) 

509 

510 personAdded = False 

511 if isEligible: 

512 personAdded = addPersonToEvent(user, event) 

513 if personAdded: 

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

515 else: 

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

517 else: 

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

519 

520 if 'from' in request.form: 

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

522 return '' 

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

524 

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

526def RemoveRSVP(): 

527 """ 

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

529 """ 

530 eventData = request.form 

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

532 

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

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

535 currentRsvpParticipant.delete_instance() 

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

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

538 if 'from' in eventData: 

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

540 return '' 

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

542 

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

544def serviceTranscript(username): 

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

546 if user is None: 

547 abort(404) 

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

549 abort(403) 

550 

551 slCourses = getSlCourseTranscript(username) 

552 totalHours = getTotalHours(username) 

553 allEventTranscript = getProgramTranscript(username) 

554 zeroHourEvents = getZeroHourEvents(username) 

555 startDate = getStartYear(username) 

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

557 allEventTranscript = allEventTranscript, 

558 zeroHourEvents = zeroHourEvents, 

559 slCourses = slCourses.objects(), 

560 totalHours = totalHours, 

561 startDate = startDate, 

562 userData = user) 

563 

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

565def updateTranscript(username, program_id): 

566 # Check user permissions 

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 # Get the data sent from the client-side JavaScript 

574 data = request.json 

575 

576 # Retrieve removeFromTranscript value from the request data 

577 removeFromTranscript = data.get('removeFromTranscript') 

578 

579 # Update the ProgramBan object matching the program_id and username 

580 try: 

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

582 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

583 bannedProgramForUser.save() 

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

585 except ProgramBan.DoesNotExist: 

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

587 

588 

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

590def searchUser(query): 

591 

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

593 

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

595 try: 

596 query = query.strip() 

597 search = query.upper() 

598 splitSearch = search.split() 

599 searchResults = searchUsers(query,category) 

600 return searchResults 

601 except Exception as e: 

602 print(e) 

603 return "Error in searching for user", 500 

604 

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

606def contributors(): 

607 return render_template("/contributors.html") 

608 

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

610def getDietInfo(): 

611 dietaryInfo = request.form 

612 user = dietaryInfo["user"] 

613 dietInfo = dietaryInfo["dietInfo"] 

614 

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

616 updateDietInfo(user, dietInfo) 

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

618 if len(dietInfo) > 0: 

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

620 else: 

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

622 

623 

624 return " " 

625 

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

627def indicateMinorInterest(username): 

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

629 data = request.get_json() 

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

631 

632 toggleMinorInterest(username, isAdding) 

633 

634 else: 

635 abort(403) 

636 

637 return "" 

638 

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

640def updateMinorDeclaration(username): 

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

642 declareMinorInterest(username) 

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

644 else: 

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

646 abort(403) 

647 

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

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

650