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

307 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-06-18 19:54 +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) and (Event.isCanceled == False) and (Event.isPast == False)) 

55 .distinct() 

56 .execute()) # Ensure only unique programs are included 

57 

58 return render_template("/main/landingPage.html", 

59 managerProgramDict=managerProgramDict, 

60 term=g.current_term, 

61 programsWithEventsList=programsWithEventsList) 

62 

63 

64@main_bp.route('/goToEventsList/<programID>', methods=['GET']) 

65def goToEventsList(programID): 

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

67 

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

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

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

71def events(selectedTerm, activeTab, programID): 

72 currentTerm = g.current_term 

73 if selectedTerm: 

74 currentTerm = selectedTerm 

75 currentTime = datetime.datetime.now() 

76 

77 listOfTerms = Term.select() 

78 participantRSVP = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == g.current_user) 

79 rsvpedEventsID = [event.event.id for event in participantRSVP] 

80 

81 term = Term.get_by_id(currentTerm) 

82 

83 currentEventRsvpAmount = getEventRsvpCountsForTerm(term) 

84 studentLedEvents = getStudentLedEvents(term) 

85 countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime) 

86 trainingEvents = getTrainingEvents(term, g.current_user) 

87 bonnerEvents = getBonnerEvents(term) 

88 otherEvents = getOtherEvents(term) 

89 

90 managersProgramDict = getManagerProgramDict(g.current_user) 

91 

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

93 selectedTerm = term, 

94 studentLedEvents = studentLedEvents, 

95 trainingEvents = trainingEvents, 

96 bonnerEvents = bonnerEvents, 

97 otherEvents = otherEvents, 

98 listOfTerms = listOfTerms, 

99 rsvpedEventsID = rsvpedEventsID, 

100 currentEventRsvpAmount = currentEventRsvpAmount, 

101 currentTime = currentTime, 

102 user = g.current_user, 

103 activeTab = activeTab, 

104 programID = int(programID), 

105 managersProgramDict = managersProgramDict, 

106 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents 

107 ) 

108 

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

110def viewUsersProfile(username): 

111 """ 

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

113 """ 

114 try: 

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

116 except Exception as e: 

117 if g.current_user.isAdmin: 

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

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

120 else: 

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

122 

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

124 upcomingEvents = getUpcomingEventsForUser(volunteer) 

125 participatedEvents = getParticipatedEventsForUser(volunteer) 

126 programs = Program.select() 

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

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

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

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

131 

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

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

134 

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

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

137 

138 allBackgroundHistory = getUserBGCheckHistory(volunteer) 

139 backgroundTypes = list(BackgroundCheckType.select()) 

140 

141 eligibilityTable = [] 

142 for program in programs: 

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

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

145 .where(ProgramBan.user == volunteer, 

146 ProgramBan.program == program, 

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

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

149 try: 

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

151 except KeyError: 

152 allTrainingsComplete = False 

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

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

155 "completedTraining": allTrainingsComplete, 

156 "trainingList": userParticipatedTrainingEvents, 

157 "isNotBanned": (not banNotes), 

158 "banNote": noteForDict}) 

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

160 

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

162 

163 managersProgramDict = getManagerProgramDict(g.current_user) 

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

165 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) 

166 

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

168 programs = programs, 

169 programsInterested = programsInterested, 

170 upcomingEvents = upcomingEvents, 

171 participatedEvents = participatedEvents, 

172 rsvpedEvents = rsvpedEvents, 

173 permissionPrograms = permissionPrograms, 

174 eligibilityTable = eligibilityTable, 

175 volunteer = volunteer, 

176 backgroundTypes = backgroundTypes, 

177 allBackgroundHistory = allBackgroundHistory, 

178 currentDateTime = datetime.datetime.now(), 

179 profileNotes = profileNotes, 

180 bonnerRequirements = bonnerRequirements, 

181 managersList = managersList, 

182 participatedInLabor = getCeltsLaborHistory(volunteer), 

183 totalSustainedEngagements = totalSustainedEngagements, 

184 ) 

185 abort(403) 

186 

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

188def emergencyContactInfo(username): 

189 """ 

190 This loads the Emergency Contact Page 

191 """ 

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

193 abort(403) 

194 

195 

196 if request.method == 'GET': 

197 readOnly = g.current_user.username != username 

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

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

200 username=username, 

201 contactInfo=contactInfo, 

202 readOnly=readOnly 

203 ) 

204 

205 elif request.method == 'POST': 

206 if g.current_user.username != username: 

207 abort(403) 

208 

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

210 if not rowsUpdated: 

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

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

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

214 

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

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

217 else: 

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

219 

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

221def insuranceInfo(username): 

222 """ 

223 This loads the Insurance Information Page 

224 """ 

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

226 abort(403) 

227 

228 if request.method == 'GET': 

229 readOnly = g.current_user.username != username 

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

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

232 username=username, 

233 userInsuranceInfo=userInsuranceInfo, 

234 readOnly=readOnly 

235 ) 

236 

237 # Save the form data 

238 elif request.method == 'POST': 

239 if g.current_user.username != username: 

240 abort(403) 

241 

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

243 if not rowsUpdated: 

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

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

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

247 

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

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

250 else: 

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

252 

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

254def travelForm(username): 

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

256 abort(403) 

257 

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

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

260 .join(InsuranceInfo, JOIN.LEFT_OUTER) 

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

262 if not list(user): 

263 abort(404) 

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

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

266 

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

268 userData = userData 

269 ) 

270 

271 

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

273def addNote(): 

274 """ 

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

276 """ 

277 postData = request.form 

278 try: 

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

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

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

282 except Exception as e: 

283 print("Error adding note", e) 

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

285 return "Failed to add profile note", 500 

286 

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

288def deleteNote(username): 

289 """ 

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

291 """ 

292 try: 

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

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

295 except Exception as e: 

296 print("Error deleting note", e) 

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

298 return "success" 

299 

300# ===========================Ban=============================================== 

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

302def ban(program_id, username): 

303 """ 

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

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

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

307 """ 

308 postData = request.form 

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

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

311 try: 

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

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

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

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

316 return "Successfully banned the volunteer." 

317 except Exception as e: 

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

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

320 return "Failed to ban the volunteer", 500 

321 

322# ===========================Unban=============================================== 

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

324def unban(program_id, username): 

325 """ 

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

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

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

329 """ 

330 postData = request.form 

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

332 try: 

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

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

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

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

337 return "Successfully unbanned the volunteer" 

338 

339 except Exception as e: 

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

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

342 return "Failed to unban the volunteer", 500 

343 

344 

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

346def addInterest(program_id, username): 

347 """ 

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

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

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

351 """ 

352 try: 

353 success = addUserInterest(program_id, username) 

354 if success: 

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

356 return "" 

357 else: 

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

359 

360 except Exception as e: 

361 print(e) 

362 return "Error Updating Interest", 500 

363 

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

365def removeInterest(program_id, username): 

366 """ 

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

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

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

370 """ 

371 try: 

372 removed = removeUserInterest(program_id, username) 

373 if removed: 

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

375 return "" 

376 else: 

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

378 except Exception as e: 

379 print(e) 

380 return "Error Updating Interest", 500 

381 

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

383def volunteerRegister(): 

384 """ 

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

386 for the event they have clicked register for. 

387 """ 

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

389 program = event.program 

390 user = g.current_user 

391 

392 isAdded = checkUserRsvp(user, event) 

393 isEligible = isEligibleForProgram(program, user) 

394 listOfRequirements = unattendedRequiredEvents(program, user) 

395 

396 personAdded = False 

397 if isEligible: 

398 personAdded = addPersonToEvent(user, event) 

399 if personAdded and listOfRequirements: 

400 reqListToString = ', '.join(listOfRequirements) 

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

402 elif personAdded: 

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

404 else: 

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

406 else: 

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

408 

409 

410 if 'from' in request.form: 

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

412 return '' 

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

414 

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

416def RemoveRSVP(): 

417 """ 

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

419 """ 

420 eventData = request.form 

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

422 

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

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

425 currentRsvpParticipant.delete_instance() 

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

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

428 if 'from' in eventData: 

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

430 return '' 

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

432 

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

434def serviceTranscript(username): 

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

436 if user is None: 

437 abort(404) 

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

439 abort(403) 

440 

441 slCourses = getSlCourseTranscript(username) 

442 totalHours = getTotalHours(username) 

443 allEventTranscript = getProgramTranscript(username) 

444 startDate = getStartYear(username) 

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

446 allEventTranscript = allEventTranscript, 

447 slCourses = slCourses.objects(), 

448 totalHours = totalHours, 

449 startDate = startDate, 

450 userData = user) 

451 

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

453def searchUser(query): 

454 

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

456 

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

458 try: 

459 query = query.strip() 

460 search = query.upper() 

461 splitSearch = search.split() 

462 searchResults = searchUsers(query,category) 

463 return searchResults 

464 except Exception as e: 

465 print(e) 

466 return "Error in searching for user", 500 

467 

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

469def contributors(): 

470 return render_template("/contributors.html") 

471 

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

473def getDietInfo(): 

474 dietaryInfo = request.form 

475 user = dietaryInfo["user"] 

476 dietInfo = dietaryInfo["dietInfo"] 

477 

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

479 updateDietInfo(user, dietInfo) 

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

481 if len(dietInfo) > 0: 

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

483 else: 

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

485 

486 

487 return " " 

488 

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

490def indicateMinorInterest(username): 

491 toggleMinorInterest(username) 

492 

493 return ""