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

392 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-12-31 18:06 +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@main_bp.route('/<username>/deleteNote', methods=['POST']) 

397def deleteNote(username): 

398 """ 

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

400 """ 

401 try: 

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

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

404 except Exception as e: 

405 print("Error deleting note", e) 

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

407 return "success" 

408 

409# ===========================Ban=============================================== 

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

411def ban(program_id, username): 

412 """ 

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

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

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

416 """ 

417 postData = request.form 

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

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

420 

421 try: 

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

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

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

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

426 return "Successfully banned the volunteer." 

427 except Exception as e: 

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

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

430 return "Failed to ban the volunteer", 500 

431 

432# ===========================Unban=============================================== 

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

434def unban(program_id, username): 

435 """ 

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

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

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

439 """ 

440 postData = request.form 

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

442 try: 

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

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

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

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

447 return "Successfully unbanned the volunteer" 

448 

449 except Exception as e: 

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

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

452 return "Failed to unban the volunteer", 500 

453 

454 

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

456def addInterest(program_id, username): 

457 """ 

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

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

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

461 """ 

462 try: 

463 success = addUserInterest(program_id, username) 

464 if success: 

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

466 return "" 

467 else: 

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

469 

470 except Exception as e: 

471 print(e) 

472 return "Error Updating Interest", 500 

473 

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

475def removeInterest(program_id, username): 

476 """ 

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

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

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

480 """ 

481 try: 

482 removed = removeUserInterest(program_id, username) 

483 if removed: 

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

485 return "" 

486 else: 

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

488 except Exception as e: 

489 print(e) 

490 return "Error Updating Interest", 500 

491 

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

493def volunteerRegister(): 

494 """ 

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

496 for the event they have clicked register for. 

497 """ 

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

499 program = event.program 

500 user = g.current_user 

501 

502 isAdded = checkUserRsvp(user, event) 

503 isEligible = isEligibleForProgram(program, user) 

504 listOfRequirements = unattendedRequiredEvents(program, user) 

505 

506 personAdded = False 

507 if isEligible: 

508 personAdded = addPersonToEvent(user, event) 

509 if personAdded and listOfRequirements: 

510 reqListToString = ', '.join(listOfRequirements) 

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

512 elif personAdded: 

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

514 else: 

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

516 else: 

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

518 

519 

520 if 'from' in request.form: 

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

522 return '' 

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

524 

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

526def RemoveRSVP(): 

527 """ 

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

529 """ 

530 eventData = request.form 

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

532 

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

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

535 currentRsvpParticipant.delete_instance() 

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

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

538 if 'from' in eventData: 

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

540 return '' 

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

542 

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

544def serviceTranscript(username): 

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

546 if user is None: 

547 abort(404) 

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

549 abort(403) 

550 

551 slCourses = getSlCourseTranscript(username) 

552 totalHours = getTotalHours(username) 

553 allEventTranscript = getProgramTranscript(username) 

554 startDate = getStartYear(username) 

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

556 allEventTranscript = allEventTranscript, 

557 slCourses = slCourses.objects(), 

558 totalHours = totalHours, 

559 startDate = startDate, 

560 userData = user) 

561 

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

563def updateTranscript(username, program_id): 

564 # Check user permissions 

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

566 if user is None: 

567 abort(404) 

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

569 abort(403) 

570 

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

572 data = request.json 

573 

574 # Retrieve removeFromTranscript value from the request data 

575 removeFromTranscript = data.get('removeFromTranscript') 

576 

577 # Update the ProgramBan object matching the program_id and username 

578 try: 

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

580 bannedProgramForUser.removeFromTranscript = removeFromTranscript 

581 bannedProgramForUser.save() 

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

583 except ProgramBan.DoesNotExist: 

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

585 

586 

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

588def searchUser(query): 

589 

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

591 

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

593 try: 

594 query = query.strip() 

595 search = query.upper() 

596 splitSearch = search.split() 

597 searchResults = searchUsers(query,category) 

598 return searchResults 

599 except Exception as e: 

600 print(e) 

601 return "Error in searching for user", 500 

602 

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

604def contributors(): 

605 return render_template("/contributors.html") 

606 

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

608def getDietInfo(): 

609 dietaryInfo = request.form 

610 user = dietaryInfo["user"] 

611 dietInfo = dietaryInfo["dietInfo"] 

612 

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

614 updateDietInfo(user, dietInfo) 

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

616 if len(dietInfo) > 0: 

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

618 else: 

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

620 

621 

622 return " " 

623 

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

625def indicateMinorInterest(username): 

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

627 data = request.get_json() 

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

629 

630 toggleMinorInterest(username, isAdding) 

631 

632 else: 

633 abort(403) 

634 

635 return "" 

636 

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

638def updateMinorDeclaration(username): 

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

640 declareMinorInterest(username) 

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

642 else: 

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

644 abort(403) 

645 

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

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

648