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

384 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-02-27 13:40 +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 

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

226 

227 managersProgramDict = getManagerProgramDict(g.current_user) 

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

229 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

230 

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

232 username=username, 

233 programs = programs, 

234 programsInterested = programsInterested, 

235 upcomingEvents = upcomingEvents, 

236 participatedEvents = participatedEvents, 

237 rsvpedEvents = rsvpedEvents, 

238 permissionPrograms = permissionPrograms, 

239 eligibilityTable = eligibilityTable, 

240 volunteer = volunteer, 

241 backgroundTypes = backgroundTypes, 

242 allBackgroundHistory = allBackgroundHistory, 

243 currentDateTime = datetime.datetime.now(), 

244 profileNotes = profileNotes, 

245 bonnerRequirements = bonnerRequirements, 

246 managersList = managersList, 

247 participatedInLabor = getCeltsLaborHistory(volunteer), 

248 totalSustainedEngagements = totalSustainedEngagements, 

249 ) 

250 abort(403) 

251 

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

253def emergencyContactInfo(username): 

254 """ 

255 This loads the Emergency Contact Page 

256 """ 

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

258 abort(403) 

259 

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

261 

262 if request.method == 'GET': 

263 readOnly = g.current_user.username != username 

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

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

266 username=username, 

267 contactInfo=contactInfo, 

268 readOnly=readOnly 

269 ) 

270 

271 elif request.method == 'POST': 

272 if g.current_user.username != username: 

273 abort(403) 

274 

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

276 if not rowsUpdated: 

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

278 

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

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

281 

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

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

284 else: 

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

286 

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

288def insuranceInfo(username): 

289 """ 

290 This loads the Insurance Information Page 

291 """ 

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

293 abort(403) 

294 

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

296 

297 if request.method == 'GET': 

298 readOnly = g.current_user.username != username 

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

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

301 username=username, 

302 userInsuranceInfo=userInsuranceInfo, 

303 readOnly=readOnly 

304 ) 

305 

306 # Save the form data 

307 elif request.method == 'POST': 

308 if g.current_user.username != username: 

309 abort(403) 

310 

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

312 

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

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

315 

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

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

318 else: 

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

320 

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

322def travelForm(username): 

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

324 abort(403) 

325 

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

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

328 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

330 if not list(user): 

331 abort(404) 

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

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

334 

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

336 userList = userList 

337 ) 

338 

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

340def eventTravelForm(eventID): 

341 try: 

342 event = Event.get_by_id(eventID) 

343 except DoesNotExist as e: 

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

345 abort(404) 

346 

347 if not (g.current_user.isCeltsAdmin): 

348 abort(403) 

349 

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

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

352 usernameList = usernameList.copy() 

353 userList = [] 

354 for username in usernameList: 

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

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

357 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

359 if not list(username): 

360 abort(404) 

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

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

363 userList.append(userData) 

364 

365 

366 else: 

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

368 

369 

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

371 usernameList = usernameList, 

372 userList = userList, 

373 ) 

374 

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

376def addNote(): 

377 """ 

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

379 """ 

380 postData = request.form 

381 try: 

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

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

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

385 except Exception as e: 

386 print("Error adding note", e) 

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

388 return "Failed to add profile note", 500 

389 

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

391def deleteNote(username): 

392 """ 

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

394 """ 

395 try: 

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

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

398 except Exception as e: 

399 print("Error deleting note", e) 

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

401 return "success" 

402 

403# ===========================Ban=============================================== 

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

405def ban(program_id, username): 

406 """ 

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

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

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

410 """ 

411 postData = request.form 

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

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

414 

415 try: 

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

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

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

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

420 return "Successfully banned the volunteer." 

421 except Exception as e: 

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

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

424 return "Failed to ban the volunteer", 500 

425 

426# ===========================Unban=============================================== 

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

428def unban(program_id, username): 

429 """ 

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

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

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

433 """ 

434 postData = request.form 

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

436 try: 

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

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

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

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

441 return "Successfully unbanned the volunteer" 

442 

443 except Exception as e: 

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

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

446 return "Failed to unban the volunteer", 500 

447 

448 

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

450def addInterest(program_id, username): 

451 """ 

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

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

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

455 """ 

456 try: 

457 success = addUserInterest(program_id, username) 

458 if success: 

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

460 return "" 

461 else: 

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

463 

464 except Exception as e: 

465 print(e) 

466 return "Error Updating Interest", 500 

467 

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

469def removeInterest(program_id, username): 

470 """ 

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

476 removed = removeUserInterest(program_id, username) 

477 if removed: 

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

479 return "" 

480 else: 

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

482 except Exception as e: 

483 print(e) 

484 return "Error Updating Interest", 500 

485 

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

487def volunteerRegister(): 

488 """ 

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

490 for the event they have clicked register for. 

491 """ 

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

493 program = event.program 

494 user = g.current_user 

495 

496 isEligible = isEligibleForProgram(program, user) 

497 

498 personAdded = False 

499 if isEligible: 

500 personAdded = addPersonToEvent(user, event) 

501 if personAdded: 

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

503 else: 

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

505 else: 

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

507 

508 if 'from' in request.form: 

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

510 return '' 

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

512 

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

514def RemoveRSVP(): 

515 """ 

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

517 """ 

518 eventData = request.form 

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

520 

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

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

523 currentRsvpParticipant.delete_instance() 

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

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

526 if 'from' in eventData: 

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

528 return '' 

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

530 

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

532def serviceTranscript(username): 

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

534 if user is None: 

535 abort(404) 

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

537 abort(403) 

538 

539 slCourses = getSlCourseTranscript(username) 

540 totalHours = getTotalHours(username) 

541 allEventTranscript = getProgramTranscript(username) 

542 startDate = getStartYear(username) 

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

544 allEventTranscript = allEventTranscript, 

545 slCourses = slCourses.objects(), 

546 totalHours = totalHours, 

547 startDate = startDate, 

548 userData = user) 

549 

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

551def updateTranscript(username, program_id): 

552 # Check user permissions 

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

554 if user is None: 

555 abort(404) 

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

557 abort(403) 

558 

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

560 data = request.json 

561 

562 # Retrieve removeFromTranscript value from the request data 

563 removeFromTranscript = data.get('removeFromTranscript') 

564 

565 # Update the ProgramBan object matching the program_id and username 

566 try: 

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

568 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

569 bannedProgramForUser.save() 

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

571 except ProgramBan.DoesNotExist: 

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

573 

574 

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

576def searchUser(query): 

577 

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

579 

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

581 try: 

582 query = query.strip() 

583 search = query.upper() 

584 splitSearch = search.split() 

585 searchResults = searchUsers(query,category) 

586 return searchResults 

587 except Exception as e: 

588 print(e) 

589 return "Error in searching for user", 500 

590 

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

592def contributors(): 

593 return render_template("/contributors.html") 

594 

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

596def getDietInfo(): 

597 dietaryInfo = request.form 

598 user = dietaryInfo["user"] 

599 dietInfo = dietaryInfo["dietInfo"] 

600 

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

602 updateDietInfo(user, dietInfo) 

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

604 if len(dietInfo) > 0: 

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

606 else: 

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

608 

609 

610 return " " 

611 

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

613def indicateMinorInterest(username): 

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

615 data = request.get_json() 

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

617 

618 toggleMinorInterest(username, isAdding) 

619 

620 else: 

621 abort(403) 

622 

623 return "" 

624 

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

626def updateMinorDeclaration(username): 

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

628 declareMinorInterest(username) 

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

630 else: 

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

632 abort(403) 

633 

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

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

636