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

387 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-22 19:51 +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 InsuranceInfo.replace({**request.form, "user": username}).execute() 

309 

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

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

312 

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

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

315 else: 

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

317 

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

319def travelForm(username): 

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

321 abort(403) 

322 

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

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

325 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

327 if not list(user): 

328 abort(404) 

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

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

331 

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

333 userList = userList 

334 ) 

335 

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

337def eventTravelForm(eventID): 

338 try: 

339 event = Event.get_by_id(eventID) 

340 except DoesNotExist as e: 

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

342 abort(404) 

343 

344 if not (g.current_user.isCeltsAdmin): 

345 abort(403) 

346 

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

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

349 usernameList = usernameList.copy() 

350 userList = [] 

351 for username in usernameList: 

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

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

354 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

356 if not list(username): 

357 abort(404) 

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

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

360 userList.append(userData) 

361 

362 

363 else: 

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

365 

366 

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

368 usernameList = usernameList, 

369 userList = userList, 

370 ) 

371 

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

373def addNote(): 

374 """ 

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

376 """ 

377 postData = request.form 

378 try: 

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

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

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

382 except Exception as e: 

383 print("Error adding note", e) 

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

385 return "Failed to add profile note", 500 

386 

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

388def deleteNote(username): 

389 """ 

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

391 """ 

392 try: 

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

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

395 except Exception as e: 

396 print("Error deleting note", e) 

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

398 return "success" 

399 

400# ===========================Ban=============================================== 

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

402def ban(program_id, username): 

403 """ 

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

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

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

407 """ 

408 postData = request.form 

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

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

411 

412 try: 

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

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

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

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

417 return "Successfully banned the volunteer." 

418 except Exception as e: 

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

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

421 return "Failed to ban the volunteer", 500 

422 

423# ===========================Unban=============================================== 

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

425def unban(program_id, username): 

426 """ 

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

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

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

430 """ 

431 postData = request.form 

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

433 try: 

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

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

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

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

438 return "Successfully unbanned the volunteer" 

439 

440 except Exception as e: 

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

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

443 return "Failed to unban the volunteer", 500 

444 

445 

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

447def addInterest(program_id, username): 

448 """ 

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

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

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

452 """ 

453 try: 

454 success = addUserInterest(program_id, username) 

455 if success: 

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

457 return "" 

458 else: 

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

460 

461 except Exception as e: 

462 print(e) 

463 return "Error Updating Interest", 500 

464 

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

466def removeInterest(program_id, username): 

467 """ 

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

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

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

471 """ 

472 try: 

473 removed = removeUserInterest(program_id, username) 

474 if removed: 

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

476 return "" 

477 else: 

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

479 except Exception as e: 

480 print(e) 

481 return "Error Updating Interest", 500 

482 

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

484def volunteerRegister(): 

485 """ 

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

487 for the event they have clicked register for. 

488 """ 

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

490 program = event.program 

491 user = g.current_user 

492 

493 isAdded = checkUserRsvp(user, event) 

494 isEligible = isEligibleForProgram(program, user) 

495 listOfRequirements = unattendedRequiredEvents(program, user) 

496 

497 personAdded = False 

498 if isEligible: 

499 personAdded = addPersonToEvent(user, event) 

500 if personAdded and listOfRequirements: 

501 reqListToString = ', '.join(listOfRequirements) 

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

503 elif personAdded: 

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

505 else: 

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

507 else: 

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

509 

510 

511 if 'from' in request.form: 

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

513 return '' 

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

515 

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

517def RemoveRSVP(): 

518 """ 

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

520 """ 

521 eventData = request.form 

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

523 

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

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

526 currentRsvpParticipant.delete_instance() 

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

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

529 if 'from' in eventData: 

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

531 return '' 

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

533 

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

535def serviceTranscript(username): 

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

537 if user is None: 

538 abort(404) 

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

540 abort(403) 

541 

542 slCourses = getSlCourseTranscript(username) 

543 totalHours = getTotalHours(username) 

544 allEventTranscript = getProgramTranscript(username) 

545 startDate = getStartYear(username) 

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

547 allEventTranscript = allEventTranscript, 

548 slCourses = slCourses.objects(), 

549 totalHours = totalHours, 

550 startDate = startDate, 

551 userData = user) 

552 

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

554def updateTranscript(username, program_id): 

555 # Check user permissions 

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

557 if user is None: 

558 abort(404) 

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

560 abort(403) 

561 

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

563 data = request.json 

564 

565 # Retrieve removeFromTranscript value from the request data 

566 removeFromTranscript = data.get('removeFromTranscript') 

567 

568 # Update the ProgramBan object matching the program_id and username 

569 try: 

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

571 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

572 bannedProgramForUser.save() 

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

574 except ProgramBan.DoesNotExist: 

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

576 

577 

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

579def searchUser(query): 

580 

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

582 

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

584 try: 

585 query = query.strip() 

586 search = query.upper() 

587 splitSearch = search.split() 

588 searchResults = searchUsers(query,category) 

589 return searchResults 

590 except Exception as e: 

591 print(e) 

592 return "Error in searching for user", 500 

593 

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

595def contributors(): 

596 return render_template("/contributors.html") 

597 

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

599def getDietInfo(): 

600 dietaryInfo = request.form 

601 user = dietaryInfo["user"] 

602 dietInfo = dietaryInfo["dietInfo"] 

603 

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

605 updateDietInfo(user, dietInfo) 

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

607 if len(dietInfo) > 0: 

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

609 else: 

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

611 

612 

613 return " " 

614 

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

616def indicateMinorInterest(username): 

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

618 data = request.get_json() 

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

620 

621 toggleMinorInterest(username, isAdding) 

622 

623 else: 

624 abort(403) 

625 

626 return "" 

627 

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

629def updateMinorDeclaration(username): 

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

631 declareMinorInterest(username) 

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

633 else: 

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

635 abort(403) 

636 

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

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

639