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

316 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-07-22 20:57 +0000

1import json 

2import datetime 

3from peewee import JOIN 

4from http import cookies 

5from playhouse.shortcuts import model_to_dict 

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

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 

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, 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 currentTime = datetime.datetime.now() 

80 

81 listOfTerms = Term.select() 

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.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 bonnerEvents = getBonnerEvents(term) 

92 otherEvents = getOtherEvents(term) 

93 

94 managersProgramDict = getManagerProgramDict(g.current_user) 

95 

96 # Fetch toggle state from session 

97 toggle_state = session.get('toggleState', 'unchecked') 

98 

99 return render_template("/events/event_list.html", 

100 selectedTerm = term, 

101 studentLedEvents = studentLedEvents, 

102 trainingEvents = trainingEvents, 

103 bonnerEvents = bonnerEvents, 

104 otherEvents = otherEvents, 

105 listOfTerms = listOfTerms, 

106 rsvpedEventsID = rsvpedEventsID, 

107 currentEventRsvpAmount = currentEventRsvpAmount, 

108 currentTime = currentTime, 

109 user = g.current_user, 

110 activeTab = activeTab, 

111 programID = int(programID), 

112 managersProgramDict = managersProgramDict, 

113 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents, 

114 toggle_state = toggle_state 

115 ) 

116 

117@main_bp.route('/updateToggleState', methods=['POST']) 

118def update_toggle_state(): 

119 toggle_state = request.form.get('toggleState') 

120 

121 # Update session with toggle state 

122 session['toggleState'] = toggle_state 

123 

124 return "", 200 

125 

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

127def viewUsersProfile(username): 

128 """ 

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

130 """ 

131 try: 

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

133 except Exception as e: 

134 if g.current_user.isAdmin: 

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

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

137 else: 

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

139 

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

141 upcomingEvents = getUpcomingEventsForUser(volunteer) 

142 participatedEvents = getParticipatedEventsForUser(volunteer) 

143 programs = Program.select() 

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

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

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

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

148 

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

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

151 

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

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

154 

155 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

156 backgroundTypes = list(BackgroundCheckType.select()) 

157 

158 eligibilityTable = [] 

159 

160 for program in programs: 

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

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

163 .where(ProgramBan.user == volunteer, 

164 ProgramBan.program == program, 

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

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

167 try: 

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

169 except KeyError: 

170 allTrainingsComplete = False 

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

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

173 "completedTraining": allTrainingsComplete, 

174 "trainingList": userParticipatedTrainingEvents, 

175 "isNotBanned": (not banNotes), 

176 "banNote": noteForDict}) 

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

178 

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

180 

181 managersProgramDict = getManagerProgramDict(g.current_user) 

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

183 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

184 

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

186 programs = programs, 

187 programsInterested = programsInterested, 

188 upcomingEvents = upcomingEvents, 

189 participatedEvents = participatedEvents, 

190 rsvpedEvents = rsvpedEvents, 

191 permissionPrograms = permissionPrograms, 

192 eligibilityTable = eligibilityTable, 

193 volunteer = volunteer, 

194 backgroundTypes = backgroundTypes, 

195 allBackgroundHistory = allBackgroundHistory, 

196 currentDateTime = datetime.datetime.now(), 

197 profileNotes = profileNotes, 

198 bonnerRequirements = bonnerRequirements, 

199 managersList = managersList, 

200 participatedInLabor = getCeltsLaborHistory(volunteer), 

201 totalSustainedEngagements = totalSustainedEngagements, 

202 ) 

203 abort(403) 

204 

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

206def emergencyContactInfo(username): 

207 """ 

208 This loads the Emergency Contact Page 

209 """ 

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

211 abort(403) 

212 

213 

214 if request.method == 'GET': 

215 readOnly = g.current_user.username != username 

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

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

218 username=username, 

219 contactInfo=contactInfo, 

220 readOnly=readOnly 

221 ) 

222 

223 elif request.method == 'POST': 

224 if g.current_user.username != username: 

225 abort(403) 

226 

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

228 if not rowsUpdated: 

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

230 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.") 

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

232 

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

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

235 else: 

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

237 

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

239def insuranceInfo(username): 

240 """ 

241 This loads the Insurance Information Page 

242 """ 

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

244 abort(403) 

245 

246 if request.method == 'GET': 

247 readOnly = g.current_user.username != username 

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

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

250 username=username, 

251 userInsuranceInfo=userInsuranceInfo, 

252 readOnly=readOnly 

253 ) 

254 

255 # Save the form data 

256 elif request.method == 'POST': 

257 if g.current_user.username != username: 

258 abort(403) 

259 

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

261 if not rowsUpdated: 

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

263 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.") 

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

265 

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

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

268 else: 

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

270 

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

272def travelForm(username): 

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

274 abort(403) 

275 

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

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

278 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

280 if not list(user): 

281 abort(404) 

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

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

284 

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

286 userData = userData 

287 ) 

288 

289 

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

291def addNote(): 

292 """ 

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

294 """ 

295 postData = request.form 

296 try: 

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

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

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

300 except Exception as e: 

301 print("Error adding note", e) 

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

303 return "Failed to add profile note", 500 

304 

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

306def deleteNote(username): 

307 """ 

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

309 """ 

310 try: 

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

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

313 except Exception as e: 

314 print("Error deleting note", e) 

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

316 return "success" 

317 

318# ===========================Ban=============================================== 

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

320def ban(program_id, username): 

321 """ 

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

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

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

325 """ 

326 postData = request.form 

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

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

329 try: 

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

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

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

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

334 return "Successfully banned the volunteer." 

335 except Exception as e: 

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

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

338 return "Failed to ban the volunteer", 500 

339 

340# ===========================Unban=============================================== 

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

342def unban(program_id, username): 

343 """ 

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

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

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

347 """ 

348 postData = request.form 

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

350 try: 

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

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

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

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

355 return "Successfully unbanned the volunteer" 

356 

357 except Exception as e: 

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

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

360 return "Failed to unban the volunteer", 500 

361 

362 

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

364def addInterest(program_id, username): 

365 """ 

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

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

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

369 """ 

370 try: 

371 success = addUserInterest(program_id, username) 

372 if success: 

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

374 return "" 

375 else: 

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

377 

378 except Exception as e: 

379 print(e) 

380 return "Error Updating Interest", 500 

381 

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

383def removeInterest(program_id, username): 

384 """ 

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

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

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

388 """ 

389 try: 

390 removed = removeUserInterest(program_id, username) 

391 if removed: 

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

393 return "" 

394 else: 

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

396 except Exception as e: 

397 print(e) 

398 return "Error Updating Interest", 500 

399 

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

401def volunteerRegister(): 

402 """ 

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

404 for the event they have clicked register for. 

405 """ 

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

407 program = event.program 

408 user = g.current_user 

409 

410 isAdded = checkUserRsvp(user, event) 

411 isEligible = isEligibleForProgram(program, user) 

412 listOfRequirements = unattendedRequiredEvents(program, user) 

413 

414 personAdded = False 

415 if isEligible: 

416 personAdded = addPersonToEvent(user, event) 

417 if personAdded and listOfRequirements: 

418 reqListToString = ', '.join(listOfRequirements) 

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

420 elif personAdded: 

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

422 else: 

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

424 else: 

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

426 

427 

428 if 'from' in request.form: 

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

430 return '' 

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

432 

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

434def RemoveRSVP(): 

435 """ 

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

437 """ 

438 eventData = request.form 

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

440 

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

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

443 currentRsvpParticipant.delete_instance() 

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

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

446 if 'from' in eventData: 

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

448 return '' 

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

450 

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

452def serviceTranscript(username): 

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

454 if user is None: 

455 abort(404) 

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

457 abort(403) 

458 

459 slCourses = getSlCourseTranscript(username) 

460 totalHours = getTotalHours(username) 

461 allEventTranscript = getProgramTranscript(username) 

462 startDate = getStartYear(username) 

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

464 allEventTranscript = allEventTranscript, 

465 slCourses = slCourses.objects(), 

466 totalHours = totalHours, 

467 startDate = startDate, 

468 userData = user) 

469 

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

471def searchUser(query): 

472 

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

474 

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

476 try: 

477 query = query.strip() 

478 search = query.upper() 

479 splitSearch = search.split() 

480 searchResults = searchUsers(query,category) 

481 return searchResults 

482 except Exception as e: 

483 print(e) 

484 return "Error in searching for user", 500 

485 

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

487def contributors(): 

488 return render_template("/contributors.html") 

489 

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

491def getDietInfo(): 

492 dietaryInfo = request.form 

493 user = dietaryInfo["user"] 

494 dietInfo = dietaryInfo["dietInfo"] 

495 

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

497 updateDietInfo(user, dietInfo) 

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

499 if len(dietInfo) > 0: 

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

501 else: 

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

503 

504 

505 return " " 

506 

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

508def indicateMinorInterest(username): 

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

510 toggleMinorInterest(username) 

511 

512 else: 

513 abort(403) 

514 

515 return ""