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

389 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-08 16: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, getUpcomingStudentLedCount, getStudentLedEvents, getBonnerEvents, getOtherEvents, 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': "studentLedEvents", 'programID': 0}) 

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

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

75def events(selectedTerm, activeTab, programID): 

76 currentTerm = g.current_term 

77 if selectedTerm: 

78 currentTerm = selectedTerm 

79 

80 currentTime = datetime.datetime.now() 

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

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

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

84 

85 term: Term = Term.get_by_id(currentTerm) 

86 

87 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

88 studentLedEvents = getStudentLedEvents(term) 

89 countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime) 

90 trainingEvents = getTrainingEvents(term, g.current_user) 

91 engagementEvents = getEngagementEvents(term) 

92 bonnerEvents = getBonnerEvents(term) 

93 otherEvents = getOtherEvents(term) 

94 

95 managersProgramDict = getManagerProgramDict(g.current_user) 

96 

97 # Fetch toggle state from session  

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

99 

100 # compile all student led events into one list 

101 studentEvents = [] 

102 for studentEvent in studentLedEvents.values(): 

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

104 

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

106 studentLedEventsCount: int = len(studentEvents) 

107 trainingEventsCount: int = len(trainingEvents) 

108 engagementEventsCount: int = len(engagementEvents) 

109 bonnerEventsCount: int = len(bonnerEvents) 

110 otherEventsCount: int = len(otherEvents) 

111 

112 # gets only upcoming events to display in indicators 

113 if (toggleState == 'unchecked'): 

114 studentLedEventsCount: int = sum(list(countUpcomingStudentLedEvents.values())) 

115 for event in trainingEvents: 

116 if event.isPastEnd: 

117 trainingEventsCount -= 1 

118 for event in engagementEvents: 

119 if event.isPastEnd: 

120 engagementEventsCount -= 1 

121 for event in bonnerEvents: 

122 if event.isPastEnd: 

123 bonnerEventsCount -= 1 

124 for event in otherEvents: 

125 if event.isPastEnd: 

126 otherEventsCount -= 1 

127 

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

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

130 return jsonify({ 

131 "studentLedEventsCount": studentLedEventsCount, 

132 "trainingEventsCount": trainingEventsCount, 

133 "engagementEventsCount": engagementEventsCount, 

134 "bonnerEventsCount": bonnerEventsCount, 

135 "otherEventsCount": otherEventsCount, 

136 "toggleStatus": toggleState 

137 }) 

138 

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

140 selectedTerm = term, 

141 studentLedEvents = studentLedEvents, 

142 trainingEvents = trainingEvents, 

143 engagementEvents = engagementEvents, 

144 bonnerEvents = bonnerEvents, 

145 otherEvents = otherEvents, 

146 listOfTerms = listOfTerms, 

147 rsvpedEventsID = rsvpedEventsID, 

148 currentEventRsvpAmount = currentEventRsvpAmount, 

149 currentTime = currentTime, 

150 user = g.current_user, 

151 activeTab = activeTab, 

152 programID = int(programID), 

153 managersProgramDict = managersProgramDict, 

154 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents, 

155 toggleState = toggleState, 

156 ) 

157 

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

159def viewUsersProfile(username): 

160 """ 

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

162 """ 

163 try: 

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

165 except Exception as e: 

166 if g.current_user.isAdmin: 

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

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

169 else: 

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

171 

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

173 upcomingEvents = getUpcomingEventsForUser(volunteer) 

174 participatedEvents = getParticipatedEventsForUser(volunteer) 

175 programs = Program.select() 

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

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

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

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

180 

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

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

183 

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

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

186 

187 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

188 backgroundTypes = list(BackgroundCheckType.select()) 

189 

190 

191 

192 eligibilityTable = [] 

193 

194 for program in programs: 

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

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

197 .where(ProgramBan.user == volunteer, 

198 ProgramBan.program == program, 

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

200 onTranscriptQuery = list(ProgramBan.select(ProgramBan) 

201 .where(ProgramBan.user == volunteer, 

202 ProgramBan.program == program, 

203 ProgramBan.unbanNote.is_null(), 

204 ProgramBan.removeFromTranscript == 0)) 

205 

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

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

208 try: 

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

210 except KeyError: 

211 allTrainingsComplete = False 

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

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

214 "completedTraining": allTrainingsComplete, 

215 "trainingList": userParticipatedTrainingEvents, 

216 "isNotBanned": (not banNotes), 

217 "banNote": noteForDict, 

218 "onTranscript": onTranscript}), 

219 

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

221 

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

223 

224 managersProgramDict = getManagerProgramDict(g.current_user) 

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

226 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

227 

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

229 username=username, 

230 programs = programs, 

231 programsInterested = programsInterested, 

232 upcomingEvents = upcomingEvents, 

233 participatedEvents = participatedEvents, 

234 rsvpedEvents = rsvpedEvents, 

235 permissionPrograms = permissionPrograms, 

236 eligibilityTable = eligibilityTable, 

237 volunteer = volunteer, 

238 backgroundTypes = backgroundTypes, 

239 allBackgroundHistory = allBackgroundHistory, 

240 currentDateTime = datetime.datetime.now(), 

241 profileNotes = profileNotes, 

242 bonnerRequirements = bonnerRequirements, 

243 managersList = managersList, 

244 participatedInLabor = getCeltsLaborHistory(volunteer), 

245 totalSustainedEngagements = totalSustainedEngagements, 

246 ) 

247 abort(403) 

248 

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

250def emergencyContactInfo(username): 

251 """ 

252 This loads the Emergency Contact Page 

253 """ 

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

255 abort(403) 

256 

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

258 

259 if request.method == 'GET': 

260 readOnly = g.current_user.username != username 

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

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

263 username=username, 

264 contactInfo=contactInfo, 

265 readOnly=readOnly 

266 ) 

267 

268 elif request.method == 'POST': 

269 if g.current_user.username != username: 

270 abort(403) 

271 

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

273 if not rowsUpdated: 

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

275 

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

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

278 

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

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

281 else: 

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

283 

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

285def insuranceInfo(username): 

286 """ 

287 This loads the Insurance Information Page 

288 """ 

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

290 abort(403) 

291 

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

293 

294 if request.method == 'GET': 

295 readOnly = g.current_user.username != username 

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

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

298 username=username, 

299 userInsuranceInfo=userInsuranceInfo, 

300 readOnly=readOnly 

301 ) 

302 

303 # Save the form data 

304 elif request.method == 'POST': 

305 if g.current_user.username != username: 

306 abort(403) 

307 

308 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute() 

309 if not rowsUpdated: 

310 InsuranceInfo.create(user = username, **request.form) 

311 

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

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

314 

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

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

317 else: 

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

319 

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

321def travelForm(username): 

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

323 abort(403) 

324 

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

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

327 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

329 if not list(user): 

330 abort(404) 

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

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

333 

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

335 userList = userList 

336 ) 

337 

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

339def eventTravelForm(eventID): 

340 try: 

341 event = Event.get_by_id(eventID) 

342 except DoesNotExist as e: 

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

344 abort(404) 

345 

346 if not (g.current_user.isCeltsAdmin): 

347 abort(403) 

348 

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

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

351 usernameList = usernameList.copy() 

352 userList = [] 

353 for username in usernameList: 

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

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

356 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

358 if not list(username): 

359 abort(404) 

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

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

362 userList.append(userData) 

363 

364 

365 else: 

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

367 

368 

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

370 usernameList = usernameList, 

371 userList = userList, 

372 ) 

373 

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

375def addNote(): 

376 """ 

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

378 """ 

379 postData = request.form 

380 try: 

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

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

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

384 except Exception as e: 

385 print("Error adding note", e) 

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

387 return "Failed to add profile note", 500 

388 

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

390def deleteNote(username): 

391 """ 

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

393 """ 

394 try: 

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

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

397 except Exception as e: 

398 print("Error deleting note", e) 

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

400 return "success" 

401 

402# ===========================Ban=============================================== 

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

404def ban(program_id, username): 

405 """ 

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

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

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

409 """ 

410 postData = request.form 

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

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

413 

414 try: 

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

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

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

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

419 return "Successfully banned the volunteer." 

420 except Exception as e: 

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

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

423 return "Failed to ban the volunteer", 500 

424 

425# ===========================Unban=============================================== 

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

427def unban(program_id, username): 

428 """ 

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

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

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

432 """ 

433 postData = request.form 

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

435 try: 

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

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

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

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

440 return "Successfully unbanned the volunteer" 

441 

442 except Exception as e: 

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

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

445 return "Failed to unban the volunteer", 500 

446 

447 

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

449def addInterest(program_id, username): 

450 """ 

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

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

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

454 """ 

455 try: 

456 success = addUserInterest(program_id, username) 

457 if success: 

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

459 return "" 

460 else: 

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

462 

463 except Exception as e: 

464 print(e) 

465 return "Error Updating Interest", 500 

466 

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

468def removeInterest(program_id, username): 

469 """ 

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

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

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

473 """ 

474 try: 

475 removed = removeUserInterest(program_id, username) 

476 if removed: 

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

478 return "" 

479 else: 

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

481 except Exception as e: 

482 print(e) 

483 return "Error Updating Interest", 500 

484 

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

486def volunteerRegister(): 

487 """ 

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

489 for the event they have clicked register for. 

490 """ 

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

492 program = event.program 

493 user = g.current_user 

494 

495 isAdded = checkUserRsvp(user, event) 

496 isEligible = isEligibleForProgram(program, user) 

497 listOfRequirements = unattendedRequiredEvents(program, user) 

498 

499 personAdded = False 

500 if isEligible: 

501 personAdded = addPersonToEvent(user, event) 

502 if personAdded and listOfRequirements: 

503 reqListToString = ', '.join(listOfRequirements) 

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

505 elif personAdded: 

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

507 else: 

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

509 else: 

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

511 

512 

513 if 'from' in request.form: 

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

515 return '' 

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

517 

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

519def RemoveRSVP(): 

520 """ 

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

522 """ 

523 eventData = request.form 

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

525 

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

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

528 currentRsvpParticipant.delete_instance() 

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

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

531 if 'from' in eventData: 

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

533 return '' 

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

535 

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

537def serviceTranscript(username): 

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

539 if user is None: 

540 abort(404) 

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

542 abort(403) 

543 

544 slCourses = getSlCourseTranscript(username) 

545 totalHours = getTotalHours(username) 

546 allEventTranscript = getProgramTranscript(username) 

547 startDate = getStartYear(username) 

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

549 allEventTranscript = allEventTranscript, 

550 slCourses = slCourses.objects(), 

551 totalHours = totalHours, 

552 startDate = startDate, 

553 userData = user) 

554 

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

556def updateTranscript(username, program_id): 

557 # Check user permissions 

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

559 if user is None: 

560 abort(404) 

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

562 abort(403) 

563 

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

565 data = request.json 

566 

567 # Retrieve removeFromTranscript value from the request data 

568 removeFromTranscript = data.get('removeFromTranscript') 

569 

570 # Update the ProgramBan object matching the program_id and username 

571 try: 

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

573 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

574 bannedProgramForUser.save() 

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

576 except ProgramBan.DoesNotExist: 

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

578 

579 

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

581def searchUser(query): 

582 

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

584 

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

586 try: 

587 query = query.strip() 

588 search = query.upper() 

589 splitSearch = search.split() 

590 searchResults = searchUsers(query,category) 

591 return searchResults 

592 except Exception as e: 

593 print(e) 

594 return "Error in searching for user", 500 

595 

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

597def contributors(): 

598 return render_template("/contributors.html") 

599 

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

601def getDietInfo(): 

602 dietaryInfo = request.form 

603 user = dietaryInfo["user"] 

604 dietInfo = dietaryInfo["dietInfo"] 

605 

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

607 updateDietInfo(user, dietInfo) 

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

609 if len(dietInfo) > 0: 

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

611 else: 

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

613 

614 

615 return " " 

616 

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

618def indicateMinorInterest(username): 

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

620 data = request.get_json() 

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

622 

623 toggleMinorInterest(username, isAdding) 

624 

625 else: 

626 abort(403) 

627 

628 return "" 

629 

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

631def updateMinorDeclaration(username): 

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

633 declareMinorInterest(username) 

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

635 else: 

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

637 abort(403) 

638 

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

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

641