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

387 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2026-03-25 20:13 +0000

1import json 

2import datetime 

3from peewee import JOIN, DoesNotExist 

4from http import cookies 

5from playhouse.shortcuts import model_to_dict 

6from flask import request, render_template, jsonify, g, abort, flash, redirect, url_for, make_response, session, request 

7 

8from app.controllers.main import main_bp 

9from app import app 

10from app.models.term import Term 

11from app.models.user import User 

12from app.models.note import Note 

13from app.models.event import Event 

14from app.models.program import Program 

15from app.models.interest import Interest 

16from app.models.eventRsvp import EventRsvp 

17from app.models.celtsLabor import CeltsLabor 

18from app.models.programBan import ProgramBan 

19from app.models.profileNote import ProfileNote 

20from app.models.insuranceInfo import InsuranceInfo 

21from app.models.certification import Certification 

22from app.models.programManager import ProgramManager 

23from app.models.backgroundCheck import BackgroundCheck 

24from app.models.emergencyContact import EmergencyContact 

25from app.models.eventParticipant import EventParticipant 

26from app.models.courseInstructor import CourseInstructor 

27from app.models.backgroundCheckType import BackgroundCheckType 

28 

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

30from app.logic.transcript import * 

31from app.logic.loginManager import logout 

32from app.logic.searchUsers import searchUsers 

33from app.logic.utils import selectSurroundingTerms 

34from app.logic.celtsLabor import getCeltsLaborHistory 

35from app.logic.createLogs import createRsvpLog, createActivityLog 

36from app.logic.certification import getCertRequirementsWithCompletion 

37from app.logic.landingPage import getManagerProgramDict, getActiveEventTab 

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

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

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

41 

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

43def redirectToLogout(): 

44 return redirect(logout()) 

45 

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

47def landingPage(): 

48 

49 managerProgramDict = getManagerProgramDict(g.current_user) 

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

51 

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

53 .join(Event) 

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

55 .distinct() 

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

57 # Limit returned list to events in the future 

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

59 

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

61 managerProgramDict=managerProgramDict, 

62 term=g.current_term, 

63 programsWithEventsList = futureEvents) 

64 

65 

66 

67 

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

69def goToEventsList(programID): 

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

71 

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

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

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

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

76def events(selectedTerm, activeTab, programID): 

77 

78 currentTime = datetime.datetime.now() 

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

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

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

82 

83 term = g.current_term 

84 if selectedTerm: 

85 term = selectedTerm 

86 

87 # Make sure we have a Term object 

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

89 if term is None: 

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

91 

92 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

93 volunteerOpportunities = getVolunteerOpportunities(term) 

94 countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) 

95 countPastVolunteerOpportunities = getPastVolunteerOpportunitiesCount(term, currentTime) 

96 trainingEvents = getTrainingEvents(term, g.current_user) 

97 engagementEvents = getEngagementEvents(term) 

98 bonnerEvents = getBonnerEvents(term) 

99 celtsLabor = getCeltsLabor(term) 

100 

101 managersProgramDict = getManagerProgramDict(g.current_user) 

102 

103 # Fetch toggle state from session  

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

105 

106 # compile all volunteer opportunitiesevents into one list 

107 studentEvents = [] 

108 for studentEvent in volunteerOpportunities.values(): 

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

110 

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

112 volunteerOpportunitiesCount: int = len(studentEvents) 

113 countUpcomingVolunteerOpportunitiesCount: int = len(countUpcomingVolunteerOpportunities) 

114 countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities) 

115 trainingEventsCount: int = len(trainingEvents) 

116 engagementEventsCount: int = len(engagementEvents) 

117 bonnerEventsCount: int = len(bonnerEvents) 

118 celtsLaborCount: int = len(celtsLabor) 

119 

120 # gets only upcoming events to display in indicators 

121 if (toggleState == 'unchecked'): 

122 for event in trainingEvents: 

123 if event.isPastEnd: 

124 trainingEventsCount -= 1 

125 for event in engagementEvents: 

126 if event.isPastEnd: 

127 engagementEventsCount -= 1 

128 for event in bonnerEvents: 

129 if event.isPastEnd: 

130 bonnerEventsCount -= 1 

131 for event in celtsLabor: 

132 if event.isPastEnd: 

133 celtsLaborCount -= 1 

134 

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

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

137 return jsonify({ 

138 "volunteerOpportunitiesCount": volunteerOpportunitiesCount, 

139 "countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount, 

140 "countUpcomingVolunteerOpportunitiesCount": countUpcomingVolunteerOpportunitiesCount, 

141 "trainingEventsCount": trainingEventsCount, 

142 "engagementEventsCount": engagementEventsCount, 

143 "bonnerEventsCount": bonnerEventsCount, 

144 "celtsLaborCount": celtsLaborCount, 

145 "toggleStatus": toggleState 

146 }) 

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

148 selectedTerm = term, 

149 volunteerOpportunities = volunteerOpportunities, 

150 trainingEvents = trainingEvents, 

151 engagementEvents = engagementEvents, 

152 bonnerEvents = bonnerEvents, 

153 celtsLabor = celtsLabor, 

154 listOfTerms = listOfTerms, 

155 rsvpedEventsID = rsvpedEventsID, 

156 currentEventRsvpAmount = currentEventRsvpAmount, 

157 currentTime = currentTime, 

158 user = g.current_user, 

159 activeTab = activeTab, 

160 programID = int(programID), 

161 managersProgramDict = managersProgramDict, 

162 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, 

163 countPastVolunteerOpportunities = countPastVolunteerOpportunities, 

164 toggleState = toggleState, 

165 ) 

166 

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

168def viewUsersProfile(username): 

169 """ 

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

171 """ 

172 try: 

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

174 except Exception as e: 

175 if g.current_user.isAdmin: 

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

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

178 else: 

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

180 

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

182 upcomingEvents = getUpcomingEventsForUser(volunteer) 

183 participatedEvents = getParticipatedEventsForUser(volunteer) 

184 programs = Program.select() 

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

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

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

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

189 

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

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

192 

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

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

195 

196 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

197 backgroundTypes = list(BackgroundCheckType.select()) 

198 

199 

200 

201 eligibilityTable = [] 

202 

203 for program in programs: 

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

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

206 .where(ProgramBan.user == volunteer, 

207 ProgramBan.program == program, 

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

209 onTranscriptQuery = list(ProgramBan.select(ProgramBan) 

210 .where(ProgramBan.user == volunteer, 

211 ProgramBan.program == program, 

212 ProgramBan.unbanNote.is_null(), 

213 ProgramBan.removeFromTranscript == 0)) 

214 

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

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

217 try: 

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

219 except KeyError: 

220 allTrainingsComplete = False 

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

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

223 "completedTraining": allTrainingsComplete, 

224 "trainingList": userParticipatedTrainingEvents, 

225 "isNotBanned": (not banNotes), 

226 "banNote": noteForDict, 

227 "onTranscript": onTranscript}), 

228 

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

230 

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

232 

233 managersProgramDict = getManagerProgramDict(g.current_user) 

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

235 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

236 

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

238 username=username, 

239 programs = programs, 

240 programsInterested = programsInterested, 

241 upcomingEvents = upcomingEvents, 

242 participatedEvents = participatedEvents, 

243 rsvpedEvents = rsvpedEvents, 

244 permissionPrograms = permissionPrograms, 

245 eligibilityTable = eligibilityTable, 

246 volunteer = volunteer, 

247 backgroundTypes = backgroundTypes, 

248 allBackgroundHistory = allBackgroundHistory, 

249 currentDateTime = datetime.datetime.now(), 

250 profileNotes = profileNotes, 

251 bonnerRequirements = bonnerRequirements, 

252 managersList = managersList, 

253 participatedInLabor = getCeltsLaborHistory(volunteer), 

254 totalSustainedEngagements = totalSustainedEngagements, 

255 ) 

256 abort(403) 

257 

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

259def emergencyContactInfo(username): 

260 """ 

261 This loads the Emergency Contact Page 

262 """ 

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

264 abort(403) 

265 

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

267 

268 if request.method == 'GET': 

269 readOnly = g.current_user.username != username 

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

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

272 username=username, 

273 contactInfo=contactInfo, 

274 readOnly=readOnly 

275 ) 

276 

277 elif request.method == 'POST': 

278 if g.current_user.username != username: 

279 abort(403) 

280 

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

282 if not rowsUpdated: 

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

284 

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

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

287 

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

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

290 else: 

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

292 

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

294def insuranceInfo(username): 

295 """ 

296 This loads the Insurance Information Page 

297 """ 

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

299 abort(403) 

300 

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

302 

303 if request.method == 'GET': 

304 readOnly = g.current_user.username != username 

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

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

307 username=username, 

308 userInsuranceInfo=userInsuranceInfo, 

309 readOnly=readOnly 

310 ) 

311 

312 # Save the form data 

313 elif request.method == 'POST': 

314 if g.current_user.username != username: 

315 abort(403) 

316 

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

318 

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

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

321 

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

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

324 else: 

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

326 

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

328def travelForm(username): 

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

330 abort(403) 

331 

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

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

334 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

336 if not list(user): 

337 abort(404) 

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

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

340 

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

342 userList = userList 

343 ) 

344 

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

346def eventTravelForm(eventID): 

347 try: 

348 event = Event.get_by_id(eventID) 

349 except DoesNotExist as e: 

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

351 abort(404) 

352 

353 if not (g.current_user.isCeltsAdmin): 

354 abort(403) 

355 

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

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

358 usernameList = usernameList.copy() 

359 userList = [] 

360 for username in usernameList: 

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

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

363 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

365 if not list(username): 

366 abort(404) 

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

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

369 userList.append(userData) 

370 

371 

372 else: 

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

374 

375 

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

377 usernameList = usernameList, 

378 userList = userList, 

379 ) 

380 

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

382def addNote(): 

383 """ 

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

385 """ 

386 postData = request.form 

387 try: 

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

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

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

391 except Exception as e: 

392 print("Error adding note", e) 

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

394 return "Failed to add profile note", 500 

395 

396 

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

398def deleteNote(username): 

399 """ 

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

401 """ 

402 try: 

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

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

405 except Exception as e: 

406 print("Error deleting note", e) 

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

408 return "success" 

409 

410# ===========================Ban=============================================== 

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

412def ban(program_id, username): 

413 """ 

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

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

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

417 """ 

418 postData = request.form 

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

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

421 

422 try: 

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

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

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

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

427 return "Successfully banned the volunteer." 

428 except Exception as e: 

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

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

431 return "Failed to ban the volunteer", 500 

432 

433# ===========================Unban=============================================== 

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

435def unban(program_id, username): 

436 """ 

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

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

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

440 """ 

441 postData = request.form 

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

443 try: 

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

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

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

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

448 return "Successfully unbanned the volunteer" 

449 

450 except Exception as e: 

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

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

453 return "Failed to unban the volunteer", 500 

454 

455 

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

457def addInterest(program_id, username): 

458 """ 

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

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

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

462 """ 

463 try: 

464 success = addUserInterest(program_id, username) 

465 if success: 

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

467 return "" 

468 else: 

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

470 

471 except Exception as e: 

472 print(e) 

473 return "Error Updating Interest", 500 

474 

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

476def removeInterest(program_id, username): 

477 """ 

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

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

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

481 """ 

482 try: 

483 removed = removeUserInterest(program_id, username) 

484 if removed: 

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

486 return "" 

487 else: 

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

489 except Exception as e: 

490 print(e) 

491 return "Error Updating Interest", 500 

492 

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

494def volunteerRegister(): 

495 """ 

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

497 for the event they have clicked register for. 

498 """ 

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

500 program = event.program 

501 user = g.current_user 

502 

503 isEligible = isEligibleForProgram(program, user) 

504 

505 personAdded = False 

506 if isEligible: 

507 personAdded = addPersonToEvent(user, event) 

508 if personAdded: 

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

510 else: 

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

512 else: 

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

514 

515 if 'from' in request.form: 

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

517 return '' 

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

519 

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

521def RemoveRSVP(): 

522 """ 

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

524 """ 

525 eventData = request.form 

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

527 

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

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

530 currentRsvpParticipant.delete_instance() 

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

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

533 if 'from' in eventData: 

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

535 return '' 

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

537 

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

539def serviceTranscript(username): 

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

541 if user is None: 

542 abort(404) 

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

544 abort(403) 

545 

546 slCourses = getSlCourseTranscript(username) 

547 totalHours = getTotalHours(username) 

548 allEventTranscript = getProgramTranscript(username) 

549 startDate = getStartYear(username) 

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

551 allEventTranscript = allEventTranscript, 

552 slCourses = slCourses.objects(), 

553 totalHours = totalHours, 

554 startDate = startDate, 

555 userData = user) 

556 

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

558def updateTranscript(username, program_id): 

559 # Check user permissions 

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

561 if user is None: 

562 abort(404) 

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

564 abort(403) 

565 

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

567 data = request.json 

568 

569 # Retrieve removeFromTranscript value from the request data 

570 removeFromTranscript = data.get('removeFromTranscript') 

571 

572 # Update the ProgramBan object matching the program_id and username 

573 try: 

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

575 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

576 bannedProgramForUser.save() 

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

578 except ProgramBan.DoesNotExist: 

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

580 

581 

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

583def searchUser(query): 

584 

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

586 

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

588 try: 

589 query = query.strip() 

590 search = query.upper() 

591 splitSearch = search.split() 

592 searchResults = searchUsers(query,category) 

593 return searchResults 

594 except Exception as e: 

595 print(e) 

596 return "Error in searching for user", 500 

597 

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

599def contributors(): 

600 return render_template("/contributors.html") 

601 

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

603def getDietInfo(): 

604 dietaryInfo = request.form 

605 user = dietaryInfo["user"] 

606 dietInfo = dietaryInfo["dietInfo"] 

607 

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

609 updateDietInfo(user, dietInfo) 

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

611 if len(dietInfo) > 0: 

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

613 else: 

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

615 

616 

617 return " " 

618 

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

620def indicateMinorInterest(username): 

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

622 data = request.get_json() 

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

624 

625 toggleMinorInterest(username, isAdding) 

626 

627 else: 

628 abort(403) 

629 

630 return "" 

631 

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

633def updateMinorDeclaration(username): 

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

635 declareMinorInterest(username) 

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

637 else: 

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

639 abort(403) 

640 

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

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

643