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

389 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-12-18 20:14 +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 

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 trainingEvents = getTrainingEvents(term, g.current_user) 

96 engagementEvents = getEngagementEvents(term) 

97 bonnerEvents = getBonnerEvents(term) 

98 celtsLabor = getCeltsLabor(term) 

99 

100 managersProgramDict = getManagerProgramDict(g.current_user) 

101 

102 # Fetch toggle state from session  

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

104 

105 # compile all volunteer opportunitiesevents into one list 

106 studentEvents = [] 

107 for studentEvent in volunteerOpportunities.values(): 

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

109 

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

111 volunteerOpportunitiesCount: int = len(studentEvents) 

112 trainingEventsCount: int = len(trainingEvents) 

113 engagementEventsCount: int = len(engagementEvents) 

114 bonnerEventsCount: int = len(bonnerEvents) 

115 celtsLaborCount: int = len(celtsLabor) 

116 

117 # gets only upcoming events to display in indicators 

118 if (toggleState == 'unchecked'): 

119 for event in trainingEvents: 

120 if event.isPastEnd: 

121 trainingEventsCount -= 1 

122 for event in engagementEvents: 

123 if event.isPastEnd: 

124 engagementEventsCount -= 1 

125 for event in bonnerEvents: 

126 if event.isPastEnd: 

127 bonnerEventsCount -= 1 

128 for event in celtsLabor: 

129 if event.isPastEnd: 

130 celtsLaborCount -= 1 

131 

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

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

134 return jsonify({ 

135 "volunteerOpportunitiesCount": volunteerOpportunitiesCount, 

136 "trainingEventsCount": trainingEventsCount, 

137 "engagementEventsCount": engagementEventsCount, 

138 "bonnerEventsCount": bonnerEventsCount, 

139 "celtsLaborCount": celtsLaborCount, 

140 "toggleStatus": toggleState 

141 }) 

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

143 selectedTerm = term, 

144 volunteerOpportunities = volunteerOpportunities, 

145 trainingEvents = trainingEvents, 

146 engagementEvents = engagementEvents, 

147 bonnerEvents = bonnerEvents, 

148 celtsLabor = celtsLabor, 

149 listOfTerms = listOfTerms, 

150 rsvpedEventsID = rsvpedEventsID, 

151 currentEventRsvpAmount = currentEventRsvpAmount, 

152 currentTime = currentTime, 

153 user = g.current_user, 

154 activeTab = activeTab, 

155 programID = int(programID), 

156 managersProgramDict = managersProgramDict, 

157 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, 

158 toggleState = toggleState, 

159 ) 

160 

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

162def viewUsersProfile(username): 

163 """ 

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

165 """ 

166 try: 

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

168 except Exception as e: 

169 if g.current_user.isAdmin: 

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

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

172 else: 

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

174 

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

176 upcomingEvents = getUpcomingEventsForUser(volunteer) 

177 participatedEvents = getParticipatedEventsForUser(volunteer) 

178 programs = Program.select() 

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

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

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

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

183 

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

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

186 

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

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

189 

190 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

191 backgroundTypes = list(BackgroundCheckType.select()) 

192 

193 

194 

195 eligibilityTable = [] 

196 

197 for program in programs: 

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

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

200 .where(ProgramBan.user == volunteer, 

201 ProgramBan.program == program, 

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

203 onTranscriptQuery = list(ProgramBan.select(ProgramBan) 

204 .where(ProgramBan.user == volunteer, 

205 ProgramBan.program == program, 

206 ProgramBan.unbanNote.is_null(), 

207 ProgramBan.removeFromTranscript == 0)) 

208 

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

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

211 try: 

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

213 except KeyError: 

214 allTrainingsComplete = False 

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

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

217 "completedTraining": allTrainingsComplete, 

218 "trainingList": userParticipatedTrainingEvents, 

219 "isNotBanned": (not banNotes), 

220 "banNote": noteForDict, 

221 "onTranscript": onTranscript}), 

222 

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

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

225 managersProgramDict = getManagerProgramDict(g.current_user) 

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

227 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

228 

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

230 username=username, 

231 programs = programs, 

232 programsInterested = programsInterested, 

233 upcomingEvents = upcomingEvents, 

234 participatedEvents = participatedEvents, 

235 rsvpedEvents = rsvpedEvents, 

236 permissionPrograms = permissionPrograms, 

237 eligibilityTable = eligibilityTable, 

238 volunteer = volunteer, 

239 backgroundTypes = backgroundTypes, 

240 allBackgroundHistory = allBackgroundHistory, 

241 currentDateTime = datetime.datetime.now(), 

242 profileNotes = profileNotes, 

243 bonnerRequirements = bonnerRequirements, 

244 managersList = managersList, 

245 participatedInLabor = getCeltsLaborHistory(volunteer), 

246 totalSustainedEngagements = totalSustainedEngagements, 

247 ) 

248 abort(403) 

249 

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

251def emergencyContactInfo(username): 

252 """ 

253 This loads the Emergency Contact Page 

254 """ 

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

256 abort(403) 

257 

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

259 

260 if request.method == 'GET': 

261 readOnly = g.current_user.username != username 

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

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

264 username=username, 

265 contactInfo=contactInfo, 

266 readOnly=readOnly 

267 ) 

268 

269 elif request.method == 'POST': 

270 if g.current_user.username != username: 

271 abort(403) 

272 

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

274 if not rowsUpdated: 

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

276 

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

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

279 

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

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

282 else: 

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

284 

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

286def insuranceInfo(username): 

287 """ 

288 This loads the Insurance Information Page 

289 """ 

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

291 abort(403) 

292 

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

294 

295 if request.method == 'GET': 

296 readOnly = g.current_user.username != username 

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

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

299 username=username, 

300 userInsuranceInfo=userInsuranceInfo, 

301 readOnly=readOnly 

302 ) 

303 

304 # Save the form data 

305 elif request.method == 'POST': 

306 if g.current_user.username != username: 

307 abort(403) 

308 

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

310 

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

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

313 

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

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

316 else: 

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

318 

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

320def travelForm(username): 

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

322 abort(403) 

323 

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

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

326 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

328 if not list(user): 

329 abort(404) 

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

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

332 

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

334 userList = userList 

335 ) 

336 

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

338def eventTravelForm(eventID): 

339 try: 

340 event = Event.get_by_id(eventID) 

341 except DoesNotExist as e: 

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

343 abort(404) 

344 

345 if not (g.current_user.isCeltsAdmin): 

346 abort(403) 

347 

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

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

350 usernameList = usernameList.copy() 

351 userList = [] 

352 for username in usernameList: 

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

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

355 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

357 if not list(username): 

358 abort(404) 

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

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

361 userList.append(userData) 

362 

363 

364 else: 

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

366 

367 

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

369 usernameList = usernameList, 

370 userList = userList, 

371 ) 

372 

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

374def addNote(): 

375 """ 

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

377 """ 

378 postData = request.form 

379 try: 

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

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

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

383 except Exception as e: 

384 print("Error adding note", e) 

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

386 return "Failed to add profile note", 500 

387 

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

389def deleteNote(username): 

390 """ 

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

392 """ 

393 try: 

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

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

396 except Exception as e: 

397 print("Error deleting note", e) 

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

399 return "success" 

400 

401# ===========================Ban=============================================== 

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

403def ban(program_id, username): 

404 """ 

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

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

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

408 """ 

409 postData = request.form 

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

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

412 

413 try: 

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

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

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

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

418 return "Successfully banned the volunteer." 

419 except Exception as e: 

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

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

422 return "Failed to ban the volunteer", 500 

423 

424# ===========================Unban=============================================== 

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

426def unban(program_id, username): 

427 """ 

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

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

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

431 """ 

432 postData = request.form 

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

434 try: 

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

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

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

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

439 return "Successfully unbanned the volunteer" 

440 

441 except Exception as e: 

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

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

444 return "Failed to unban the volunteer", 500 

445 

446 

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

448def addInterest(program_id, username): 

449 """ 

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

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

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

453 """ 

454 try: 

455 success = addUserInterest(program_id, username) 

456 if success: 

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

458 return "" 

459 else: 

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

461 

462 except Exception as e: 

463 print(e) 

464 return "Error Updating Interest", 500 

465 

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

467def removeInterest(program_id, username): 

468 """ 

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

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

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

472 """ 

473 try: 

474 removed = removeUserInterest(program_id, username) 

475 if removed: 

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

477 return "" 

478 else: 

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

480 except Exception as e: 

481 print(e) 

482 return "Error Updating Interest", 500 

483 

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

485def volunteerRegister(): 

486 """ 

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

488 for the event they have clicked register for. 

489 """ 

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

491 program = event.program 

492 user = g.current_user 

493 

494 isAdded = checkUserRsvp(user, event) 

495 isEligible = isEligibleForProgram(program, user) 

496 listOfRequirements = unattendedRequiredEvents(program, user) 

497 

498 personAdded = False 

499 if isEligible: 

500 personAdded = addPersonToEvent(user, event) 

501 if personAdded and listOfRequirements: 

502 reqListToString = ', '.join(listOfRequirements) 

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

504 elif personAdded: 

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

506 else: 

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

508 else: 

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

510 

511 

512 if 'from' in request.form: 

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

514 return '' 

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

516 

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

518def RemoveRSVP(): 

519 """ 

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

521 """ 

522 eventData = request.form 

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

524 

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

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

527 currentRsvpParticipant.delete_instance() 

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

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

530 if 'from' in eventData: 

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

532 return '' 

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

534 

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

536def serviceTranscript(username): 

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

538 if user is None: 

539 abort(404) 

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

541 abort(403) 

542 

543 slCourses = getSlCourseTranscript(username) 

544 totalHours = getTotalHours(username) 

545 allEventTranscript = getProgramTranscript(username) 

546 startDate = getStartYear(username) 

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

548 allEventTranscript = allEventTranscript, 

549 slCourses = slCourses.objects(), 

550 totalHours = totalHours, 

551 startDate = startDate, 

552 userData = user) 

553 

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

555def updateTranscript(username, program_id): 

556 # Check user permissions 

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

558 if user is None: 

559 abort(404) 

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

561 abort(403) 

562 

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

564 data = request.json 

565 

566 # Retrieve removeFromTranscript value from the request data 

567 removeFromTranscript = data.get('removeFromTranscript') 

568 

569 # Update the ProgramBan object matching the program_id and username 

570 try: 

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

572 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

573 bannedProgramForUser.save() 

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

575 except ProgramBan.DoesNotExist: 

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

577 

578 

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

580def searchUser(query): 

581 

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

583 

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

585 try: 

586 query = query.strip() 

587 search = query.upper() 

588 splitSearch = search.split() 

589 searchResults = searchUsers(query,category) 

590 return searchResults 

591 except Exception as e: 

592 print(e) 

593 return "Error in searching for user", 500 

594 

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

596def contributors(): 

597 return render_template("/contributors.html") 

598 

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

600def getDietInfo(): 

601 dietaryInfo = request.form 

602 user = dietaryInfo["user"] 

603 dietInfo = dietaryInfo["dietInfo"] 

604 

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

606 updateDietInfo(user, dietInfo) 

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

608 if len(dietInfo) > 0: 

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

610 else: 

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

612 

613 

614 return " " 

615 

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

617def indicateMinorInterest(username): 

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

619 data = request.get_json() 

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

621 

622 toggleMinorInterest(username, isAdding) 

623 

624 else: 

625 abort(403) 

626 

627 return "" 

628 

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

630def updateMinorDeclaration(username): 

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

632 declareMinorInterest(username) 

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

634 else: 

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

636 abort(403) 

637 

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

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

640