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

384 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-03-10 19:32 +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 

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

392def deleteNote(username): 

393 """ 

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

395 """ 

396 try: 

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

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

399 except Exception as e: 

400 print("Error deleting note", e) 

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

402 return "success" 

403 

404# ===========================Ban=============================================== 

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

406def ban(program_id, username): 

407 """ 

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

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

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

411 """ 

412 postData = request.form 

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

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

415 

416 try: 

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

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

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

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

421 return "Successfully banned the volunteer." 

422 except Exception as e: 

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

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

425 return "Failed to ban the volunteer", 500 

426 

427# ===========================Unban=============================================== 

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

429def unban(program_id, username): 

430 """ 

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

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

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

434 """ 

435 postData = request.form 

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

437 try: 

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

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

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

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

442 return "Successfully unbanned the volunteer" 

443 

444 except Exception as e: 

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

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

447 return "Failed to unban the volunteer", 500 

448 

449 

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

451def addInterest(program_id, username): 

452 """ 

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

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

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

456 """ 

457 try: 

458 success = addUserInterest(program_id, username) 

459 if success: 

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

461 return "" 

462 else: 

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

464 

465 except Exception as e: 

466 print(e) 

467 return "Error Updating Interest", 500 

468 

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

470def removeInterest(program_id, username): 

471 """ 

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

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

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

475 """ 

476 try: 

477 removed = removeUserInterest(program_id, username) 

478 if removed: 

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

480 return "" 

481 else: 

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

483 except Exception as e: 

484 print(e) 

485 return "Error Updating Interest", 500 

486 

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

488def volunteerRegister(): 

489 """ 

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

491 for the event they have clicked register for. 

492 """ 

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

494 program = event.program 

495 user = g.current_user 

496 

497 isEligible = isEligibleForProgram(program, user) 

498 

499 personAdded = False 

500 if isEligible: 

501 personAdded = addPersonToEvent(user, event) 

502 if personAdded: 

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

504 else: 

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

506 else: 

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

508 

509 if 'from' in request.form: 

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

511 return '' 

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

513 

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

515def RemoveRSVP(): 

516 """ 

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

518 """ 

519 eventData = request.form 

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

521 

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

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

524 currentRsvpParticipant.delete_instance() 

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

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

527 if 'from' in eventData: 

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

529 return '' 

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

531 

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

533def serviceTranscript(username): 

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

535 if user is None: 

536 abort(404) 

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

538 abort(403) 

539 

540 slCourses = getSlCourseTranscript(username) 

541 totalHours = getTotalHours(username) 

542 allEventTranscript = getProgramTranscript(username) 

543 startDate = getStartYear(username) 

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

545 allEventTranscript = allEventTranscript, 

546 slCourses = slCourses.objects(), 

547 totalHours = totalHours, 

548 startDate = startDate, 

549 userData = user) 

550 

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

552def updateTranscript(username, program_id): 

553 # Check user permissions 

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

555 if user is None: 

556 abort(404) 

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

558 abort(403) 

559 

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

561 data = request.json 

562 

563 # Retrieve removeFromTranscript value from the request data 

564 removeFromTranscript = data.get('removeFromTranscript') 

565 

566 # Update the ProgramBan object matching the program_id and username 

567 try: 

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

569 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

570 bannedProgramForUser.save() 

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

572 except ProgramBan.DoesNotExist: 

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

574 

575 

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

577def searchUser(query): 

578 

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

580 

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

582 try: 

583 query = query.strip() 

584 search = query.upper() 

585 splitSearch = search.split() 

586 searchResults = searchUsers(query,category) 

587 return searchResults 

588 except Exception as e: 

589 print(e) 

590 return "Error in searching for user", 500 

591 

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

593def contributors(): 

594 return render_template("/contributors.html") 

595 

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

597def getDietInfo(): 

598 dietaryInfo = request.form 

599 user = dietaryInfo["user"] 

600 dietInfo = dietaryInfo["dietInfo"] 

601 

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

603 updateDietInfo(user, dietInfo) 

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

605 if len(dietInfo) > 0: 

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

607 else: 

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

609 

610 

611 return " " 

612 

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

614def indicateMinorInterest(username): 

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

616 data = request.get_json() 

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

618 

619 toggleMinorInterest(username, isAdding) 

620 

621 else: 

622 abort(403) 

623 

624 return "" 

625 

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

627def updateMinorDeclaration(username): 

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

629 declareMinorInterest(username) 

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

631 else: 

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

633 abort(403) 

634 

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

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

637