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

310 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-06-20 17:50 +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 # 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 return render_template("/events/event_list.html", 

97 selectedTerm = term, 

98 studentLedEvents = studentLedEvents, 

99 trainingEvents = trainingEvents, 

100 bonnerEvents = bonnerEvents, 

101 otherEvents = otherEvents, 

102 listOfTerms = listOfTerms, 

103 rsvpedEventsID = rsvpedEventsID, 

104 currentEventRsvpAmount = currentEventRsvpAmount, 

105 currentTime = currentTime, 

106 user = g.current_user, 

107 activeTab = activeTab, 

108 programID = int(programID), 

109 managersProgramDict = managersProgramDict, 

110 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents 

111 ) 

112 

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

114def viewUsersProfile(username): 

115 """ 

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

117 """ 

118 try: 

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

120 except Exception as e: 

121 if g.current_user.isAdmin: 

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

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

124 else: 

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

126 

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

128 upcomingEvents = getUpcomingEventsForUser(volunteer) 

129 participatedEvents = getParticipatedEventsForUser(volunteer) 

130 programs = Program.select() 

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

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

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

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

135 

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

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

138 

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

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

141 

142 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

143 backgroundTypes = list(BackgroundCheckType.select()) 

144 

145 eligibilityTable = [] 

146 

147 for program in programs: 

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

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

150 .where(ProgramBan.user == volunteer, 

151 ProgramBan.program == program, 

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

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

154 try: 

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

156 except KeyError: 

157 allTrainingsComplete = False 

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

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

160 "completedTraining": allTrainingsComplete, 

161 "trainingList": userParticipatedTrainingEvents, 

162 "isNotBanned": (not banNotes), 

163 "banNote": noteForDict}) 

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

165 

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

167 

168 managersProgramDict = getManagerProgramDict(g.current_user) 

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

170 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

171 

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

173 programs = programs, 

174 programsInterested = programsInterested, 

175 upcomingEvents = upcomingEvents, 

176 participatedEvents = participatedEvents, 

177 rsvpedEvents = rsvpedEvents, 

178 permissionPrograms = permissionPrograms, 

179 eligibilityTable = eligibilityTable, 

180 volunteer = volunteer, 

181 backgroundTypes = backgroundTypes, 

182 allBackgroundHistory = allBackgroundHistory, 

183 currentDateTime = datetime.datetime.now(), 

184 profileNotes = profileNotes, 

185 bonnerRequirements = bonnerRequirements, 

186 managersList = managersList, 

187 participatedInLabor = getCeltsLaborHistory(volunteer), 

188 totalSustainedEngagements = totalSustainedEngagements, 

189 ) 

190 abort(403) 

191 

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

193def emergencyContactInfo(username): 

194 """ 

195 This loads the Emergency Contact Page 

196 """ 

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

198 abort(403) 

199 

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

201 

202 if request.method == 'GET': 

203 readOnly = g.current_user.username != username 

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

205 username=username, 

206 contactInfo=contactInfo, 

207 readOnly=readOnly 

208 ) 

209 

210 elif request.method == 'POST': 

211 if g.current_user.username != username: 

212 abort(403) 

213 

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

215 if not rowsUpdated: 

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

217 

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

219 

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

221 

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

223 

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

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

226 else: 

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

228 

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

230def insuranceInfo(username): 

231 """ 

232 This loads the Insurance Information Page 

233 """ 

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

235 abort(403) 

236 

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

238 if request.method == 'GET': 

239 readOnly = False and g.current_user.username != username 

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

241 username=username, 

242 userInsuranceInfo=userInsuranceInfo, 

243 readOnly=readOnly 

244 ) 

245 

246 # Save the form data 

247 elif request.method == 'POST': 

248 if g.current_user.username != username: 

249 abort(403) 

250 

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

252 if rowsUpdated: 

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

254 

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

256 

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

258 

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

260 

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

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

263 else: 

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

265 

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

267def travelForm(username): 

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

269 abort(403) 

270 

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

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

273 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

275 if not list(user): 

276 abort(404) 

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

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

279 

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

281 userData = userData 

282 ) 

283 

284 

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

286def addNote(): 

287 """ 

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

289 """ 

290 postData = request.form 

291 try: 

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

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

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

295 except Exception as e: 

296 print("Error adding note", e) 

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

298 return "Failed to add profile note", 500 

299 

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

301def deleteNote(username): 

302 """ 

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

304 """ 

305 try: 

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

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

308 except Exception as e: 

309 print("Error deleting note", e) 

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

311 return "success" 

312 

313# ===========================Ban=============================================== 

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

315def ban(program_id, username): 

316 """ 

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

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

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

320 """ 

321 postData = request.form 

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

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

324 try: 

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

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

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

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

329 return "Successfully banned the volunteer." 

330 except Exception as e: 

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

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

333 return "Failed to ban the volunteer", 500 

334 

335# ===========================Unban=============================================== 

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

337def unban(program_id, username): 

338 """ 

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

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

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

342 """ 

343 postData = request.form 

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

345 try: 

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

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

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

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

350 return "Successfully unbanned the volunteer" 

351 

352 except Exception as e: 

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

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

355 return "Failed to unban the volunteer", 500 

356 

357 

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

359def addInterest(program_id, username): 

360 """ 

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

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

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

364 """ 

365 try: 

366 success = addUserInterest(program_id, username) 

367 if success: 

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

369 return "" 

370 else: 

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

372 

373 except Exception as e: 

374 print(e) 

375 return "Error Updating Interest", 500 

376 

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

378def removeInterest(program_id, username): 

379 """ 

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

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

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

383 """ 

384 try: 

385 removed = removeUserInterest(program_id, username) 

386 if removed: 

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

388 return "" 

389 else: 

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

391 except Exception as e: 

392 print(e) 

393 return "Error Updating Interest", 500 

394 

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

396def volunteerRegister(): 

397 """ 

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

399 for the event they have clicked register for. 

400 """ 

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

402 program = event.program 

403 user = g.current_user 

404 

405 isAdded = checkUserRsvp(user, event) 

406 isEligible = isEligibleForProgram(program, user) 

407 listOfRequirements = unattendedRequiredEvents(program, user) 

408 

409 personAdded = False 

410 if isEligible: 

411 personAdded = addPersonToEvent(user, event) 

412 if personAdded and listOfRequirements: 

413 reqListToString = ', '.join(listOfRequirements) 

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

415 elif personAdded: 

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

417 else: 

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

419 else: 

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

421 

422 

423 if 'from' in request.form: 

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

425 return '' 

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

427 

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

429def RemoveRSVP(): 

430 """ 

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

432 """ 

433 eventData = request.form 

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

435 

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

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

438 currentRsvpParticipant.delete_instance() 

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

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

441 if 'from' in eventData: 

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

443 return '' 

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

445 

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

447def serviceTranscript(username): 

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

449 if user is None: 

450 abort(404) 

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

452 abort(403) 

453 

454 slCourses = getSlCourseTranscript(username) 

455 totalHours = getTotalHours(username) 

456 allEventTranscript = getProgramTranscript(username) 

457 startDate = getStartYear(username) 

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

459 allEventTranscript = allEventTranscript, 

460 slCourses = slCourses.objects(), 

461 totalHours = totalHours, 

462 startDate = startDate, 

463 userData = user) 

464 

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

466def searchUser(query): 

467 

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

469 

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

471 try: 

472 query = query.strip() 

473 search = query.upper() 

474 splitSearch = search.split() 

475 searchResults = searchUsers(query,category) 

476 return searchResults 

477 except Exception as e: 

478 print(e) 

479 return "Error in searching for user", 500 

480 

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

482def contributors(): 

483 return render_template("/contributors.html") 

484 

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

486def getDietInfo(): 

487 dietaryInfo = request.form 

488 user = dietaryInfo["user"] 

489 dietInfo = dietaryInfo["dietInfo"] 

490 

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

492 updateDietInfo(user, dietInfo) 

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

494 if len(dietInfo) > 0: 

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

496 else: 

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

498 

499 

500 return " " 

501 

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

503def indicateMinorInterest(username): 

504 toggleMinorInterest(username) 

505 

506 return ""