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

310 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-07-17 14:16 +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 

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 

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

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@main_bp.route('/goToEventsList/<programID>', methods=['GET']) 

67def goToEventsList(programID): 

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

69 

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

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

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

73def events(selectedTerm, activeTab, programID): 

74 currentTerm = g.current_term 

75 if selectedTerm: 

76 currentTerm = selectedTerm 

77 currentTime = datetime.datetime.now() 

78 

79 listOfTerms = Term.select() 

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 = Term.get_by_id(currentTerm) 

84 

85 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

86 studentLedEvents = getStudentLedEvents(term) 

87 countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime) 

88 trainingEvents = getTrainingEvents(term, g.current_user) 

89 bonnerEvents = getBonnerEvents(term) 

90 otherEvents = getOtherEvents(term) 

91 

92 managersProgramDict = getManagerProgramDict(g.current_user) 

93 

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

95 selectedTerm = term, 

96 studentLedEvents = studentLedEvents, 

97 trainingEvents = trainingEvents, 

98 bonnerEvents = bonnerEvents, 

99 otherEvents = otherEvents, 

100 listOfTerms = listOfTerms, 

101 rsvpedEventsID = rsvpedEventsID, 

102 currentEventRsvpAmount = currentEventRsvpAmount, 

103 currentTime = currentTime, 

104 user = g.current_user, 

105 activeTab = activeTab, 

106 programID = int(programID), 

107 managersProgramDict = managersProgramDict, 

108 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents 

109 ) 

110 

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

112def viewUsersProfile(username): 

113 """ 

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

115 """ 

116 try: 

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

118 except Exception as e: 

119 if g.current_user.isAdmin: 

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

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

122 else: 

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

124 

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

126 upcomingEvents = getUpcomingEventsForUser(volunteer) 

127 participatedEvents = getParticipatedEventsForUser(volunteer) 

128 programs = Program.select() 

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

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

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

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

133 

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

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

136 

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

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

139 

140 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

141 backgroundTypes = list(BackgroundCheckType.select()) 

142 

143 eligibilityTable = [] 

144 

145 for program in programs: 

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

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

148 .where(ProgramBan.user == volunteer, 

149 ProgramBan.program == program, 

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

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

152 try: 

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

154 except KeyError: 

155 allTrainingsComplete = False 

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

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

158 "completedTraining": allTrainingsComplete, 

159 "trainingList": userParticipatedTrainingEvents, 

160 "isNotBanned": (not banNotes), 

161 "banNote": noteForDict}) 

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

163 

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

165 

166 managersProgramDict = getManagerProgramDict(g.current_user) 

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

168 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

169 

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

171 programs = programs, 

172 programsInterested = programsInterested, 

173 upcomingEvents = upcomingEvents, 

174 participatedEvents = participatedEvents, 

175 rsvpedEvents = rsvpedEvents, 

176 permissionPrograms = permissionPrograms, 

177 eligibilityTable = eligibilityTable, 

178 volunteer = volunteer, 

179 backgroundTypes = backgroundTypes, 

180 allBackgroundHistory = allBackgroundHistory, 

181 currentDateTime = datetime.datetime.now(), 

182 profileNotes = profileNotes, 

183 bonnerRequirements = bonnerRequirements, 

184 managersList = managersList, 

185 participatedInLabor = getCeltsLaborHistory(volunteer), 

186 totalSustainedEngagements = totalSustainedEngagements, 

187 ) 

188 abort(403) 

189###############################EMERGENCY CONTACT###################################### 

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

191def emergencyContactInfo(username): 

192 """ 

193 This loads the Emergency Contact Page 

194 """ 

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

196 abort(403) 

197 

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

199 

200 if request.method == 'GET': 

201 readOnly = g.current_user.username != username 

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

203 username=username, 

204 contactInfo=contactInfo, 

205 readOnly=readOnly 

206 ) 

207 

208 elif request.method == 'POST': 

209 if g.current_user.username != username: 

210 abort(403) 

211 

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

213 

214 if rowsUpdated: 

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

216 

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

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

219 

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

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

222 else: 

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

224###############################INSURANCE INFO###################################### 

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

226def insuranceInfo(username): 

227 """ 

228 This loads the Insurance Information Page 

229 """ 

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

231 abort(403) 

232 

233 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user_id == username) 

234 

235 if request.method == 'GET': 

236 readOnly = g.current_user.username != username 

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

238 username=username, 

239 userInsuranceInfo=userInsuranceInfo, 

240 readOnly=readOnly 

241 ) 

242 

243 # Save the form data 

244 elif request.method == 'POST': 

245 if g.current_user.username != username: 

246 abort(403) 

247 

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

249 

250 if rowsUpdated: 

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

252 

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

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

255 

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

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

258 else: 

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

260###############################TRAVEL FORM###################################### 

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

262def travelForm(username): 

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

264 abort(403) 

265 

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

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

268 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

270 if not list(user): 

271 abort(404) 

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

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

274 

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

276 userData = userData 

277 ) 

278###############################PROFILE NOTES###################################### 

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

280def addNote(): 

281 """ 

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

283 """ 

284 postData = request.form 

285 try: 

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

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

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

289 except Exception as e: 

290 print("Error adding note", e) 

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

292 return "Failed to add profile note", 500 

293 

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

295def deleteNote(username): 

296 """ 

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

298 """ 

299 try: 

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

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

302 except Exception as e: 

303 print("Error deleting note", e) 

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

305 return "success" 

306 

307# ===========================Ban=============================================== 

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

309def ban(program_id, username): 

310 """ 

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

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

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

314 """ 

315 postData = request.form 

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

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

318 try: 

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

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

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

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

323 return "Successfully banned the volunteer." 

324 except Exception as e: 

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

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

327 return "Failed to ban the volunteer", 500 

328 

329# ===========================Unban=============================================== 

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

331def unban(program_id, username): 

332 """ 

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

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

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

336 """ 

337 postData = request.form 

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

339 try: 

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

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

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

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

344 return "Successfully unbanned the volunteer" 

345 

346 except Exception as e: 

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

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

349 return "Failed to unban the volunteer", 500 

350 

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

352def addInterest(program_id, username): 

353 """ 

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

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

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

357 """ 

358 try: 

359 success = addUserInterest(program_id, username) 

360 if success: 

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

362 return "" 

363 else: 

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

365 

366 except Exception as e: 

367 print(e) 

368 return "Error Updating Interest", 500 

369 

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

371def removeInterest(program_id, username): 

372 """ 

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

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

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

376 """ 

377 try: 

378 removed = removeUserInterest(program_id, username) 

379 if removed: 

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

381 return "" 

382 else: 

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

384 except Exception as e: 

385 print(e) 

386 return "Error Updating Interest", 500 

387 

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

389def volunteerRegister(): 

390 """ 

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

392 for the event they have clicked register for. 

393 """ 

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

395 program = event.program 

396 user = g.current_user 

397 

398 isAdded = checkUserRsvp(user, event) 

399 isEligible = isEligibleForProgram(program, user) 

400 listOfRequirements = unattendedRequiredEvents(program, user) 

401 

402 personAdded = False 

403 if isEligible: 

404 personAdded = addPersonToEvent(user, event) 

405 if personAdded and listOfRequirements: 

406 reqListToString = ', '.join(listOfRequirements) 

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

408 elif personAdded: 

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

410 else: 

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

412 else: 

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

414 

415 

416 if 'from' in request.form: 

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

418 return '' 

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

420 

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

422def RemoveRSVP(): 

423 """ 

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

425 """ 

426 eventData = request.form 

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

428 

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

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

431 currentRsvpParticipant.delete_instance() 

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

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

434 if 'from' in eventData: 

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

436 return '' 

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

438 

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

440def serviceTranscript(username): 

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

442 if user is None: 

443 abort(404) 

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

445 abort(403) 

446 

447 slCourses = getSlCourseTranscript(username) 

448 totalHours = getTotalHours(username) 

449 allEventTranscript = getProgramTranscript(username) 

450 startDate = getStartYear(username) 

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

452 allEventTranscript = allEventTranscript, 

453 slCourses = slCourses.objects(), 

454 totalHours = totalHours, 

455 startDate = startDate, 

456 userData = user) 

457 

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

459def searchUser(query): 

460 

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

462 

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

464 try: 

465 query = query.strip() 

466 search = query.upper() 

467 splitSearch = search.split() 

468 searchResults = searchUsers(query,category) 

469 return searchResults 

470 except Exception as e: 

471 print(e) 

472 return "Error in searching for user", 500 

473 

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

475def contributors(): 

476 return render_template("/contributors.html") 

477 

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

479def getDietInfo(): 

480 dietaryInfo = request.form 

481 user = dietaryInfo["user"] 

482 dietInfo = dietaryInfo["dietInfo"] 

483 

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

485 updateDietInfo(user, dietInfo) 

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

487 if len(dietInfo) > 0: 

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

489 else: 

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

491 

492 

493 return " " 

494 

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

496def indicateMinorInterest(username): 

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

498 toggleMinorInterest(username) 

499 

500 else: 

501 abort(403) 

502 

503 return ""