Coverage for app/controllers/main/routes.py: 25%
333 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-09-18 19:56 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-09-18 19:56 +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, jsonify, g, abort, flash, redirect, url_for, make_response, session, request
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
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
42@main_bp.route('/logout', methods=['GET'])
43def redirectToLogout():
44 return redirect(logout())
46@main_bp.route('/', methods=['GET'])
47def landingPage():
49 managerProgramDict = getManagerProgramDict(g.current_user)
50 # 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]
60 return render_template("/main/landingPage.html",
61 managerProgramDict=managerProgramDict,
62 term=g.current_term,
63 programsWithEventsList = futureEvents)
68@main_bp.route('/goToEventsList/<programID>', methods=['GET'])
69def goToEventsList(programID):
70 return {"activeTab": getActiveEventTab(programID)}
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()
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]
85 term: Term = Term.get_by_id(currentTerm)
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)
94 managersProgramDict = getManagerProgramDict(g.current_user)
96 # Fetch toggle state from session
97 toggleState = request.args.get('toggleState', 'unchecked')
99 # compile all student led events into one list
100 studentEvents = []
101 for studentEvent in studentLedEvents.values():
102 studentEvents += studentEvent # add all contents of studentEvent to the studentEvents list
104 # Get the count of all term events for each category to display in the event list page.
105 studentLedEventsCount: int = len(studentEvents)
106 trainingEventsCount: int = len(trainingEvents)
107 bonnerEventsCount: int = len(bonnerEvents)
108 otherEventsCount: int = len(otherEvents)
110 # gets only upcoming events to display in indicators
111 if (toggleState == 'unchecked'):
112 studentLedEventsCount: int = sum(list(countUpcomingStudentLedEvents.values()))
113 for event in trainingEvents:
114 if event.isPastEnd:
115 trainingEventsCount -= 1
116 for event in bonnerEvents:
117 if event.isPastEnd:
118 bonnerEventsCount -= 1
119 for event in otherEvents:
120 if event.isPastEnd:
121 otherEventsCount -= 1
123 # Handle ajax request for Event category header number notifiers and toggle
124 if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
125 return jsonify({
126 "studentLedEventsCount": studentLedEventsCount,
127 "trainingEventsCount": trainingEventsCount,
128 "bonnerEventsCount": bonnerEventsCount,
129 "otherEventsCount": otherEventsCount,
130 "toggleStatus": toggleState
131 })
133 return render_template("/events/eventList.html",
134 selectedTerm = term,
135 studentLedEvents = studentLedEvents,
136 trainingEvents = trainingEvents,
137 bonnerEvents = bonnerEvents,
138 otherEvents = otherEvents,
139 listOfTerms = listOfTerms,
140 rsvpedEventsID = rsvpedEventsID,
141 currentEventRsvpAmount = currentEventRsvpAmount,
142 currentTime = currentTime,
143 user = g.current_user,
144 activeTab = activeTab,
145 programID = int(programID),
146 managersProgramDict = managersProgramDict,
147 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents,
148 toggleState = toggleState,
149 )
151@main_bp.route('/profile/<username>', methods=['GET'])
152def viewUsersProfile(username):
153 """
154 This function displays the information of a volunteer to the user
155 """
156 try:
157 volunteer = User.get(User.username == username)
158 except Exception as e:
159 if g.current_user.isAdmin:
160 flash(f"{username} does not exist! ", category='danger')
161 return redirect(url_for('admin.studentSearchPage'))
162 else:
163 abort(403) # Error 403 if non admin/student-staff user trys to access via url
165 if (g.current_user == volunteer) or g.current_user.isAdmin:
166 upcomingEvents = getUpcomingEventsForUser(volunteer)
167 participatedEvents = getParticipatedEventsForUser(volunteer)
168 programs = Program.select()
169 if not g.current_user.isBonnerScholar and not g.current_user.isAdmin:
170 programs = programs.where(Program.isBonnerScholars == False)
171 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == volunteer)
172 programsInterested = [interest.program for interest in interests]
174 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
175 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
177 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
178 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
180 allBackgroundHistory = getUserBGCheckHistory(volunteer)
181 backgroundTypes = list(BackgroundCheckType.select())
183 eligibilityTable = []
185 for program in programs:
186 banNotes = list(ProgramBan.select(ProgramBan, Note)
187 .join(Note, on=(ProgramBan.banNote == Note.id))
188 .where(ProgramBan.user == volunteer,
189 ProgramBan.program == program,
190 ProgramBan.endDate > datetime.datetime.now()).execute())
191 userParticipatedTrainingEvents = getParticipationStatusForTrainings(program, [volunteer], g.current_term)
192 try:
193 allTrainingsComplete = False not in [attended for event, attended in userParticipatedTrainingEvents[username]] # Did volunteer attend all events
194 except KeyError:
195 allTrainingsComplete = False
196 noteForDict = banNotes[-1].banNote.noteContent if banNotes else ""
197 eligibilityTable.append({"program": program,
198 "completedTraining": allTrainingsComplete,
199 "trainingList": userParticipatedTrainingEvents,
200 "isNotBanned": (not banNotes),
201 "banNote": noteForDict})
202 profileNotes = ProfileNote.select().where(ProfileNote.user == volunteer)
204 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
206 managersProgramDict = getManagerProgramDict(g.current_user)
207 managersList = [id[1] for id in managersProgramDict.items()]
208 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
210 return render_template ("/main/userProfile.html",
211 programs = programs,
212 programsInterested = programsInterested,
213 upcomingEvents = upcomingEvents,
214 participatedEvents = participatedEvents,
215 rsvpedEvents = rsvpedEvents,
216 permissionPrograms = permissionPrograms,
217 eligibilityTable = eligibilityTable,
218 volunteer = volunteer,
219 backgroundTypes = backgroundTypes,
220 allBackgroundHistory = allBackgroundHistory,
221 currentDateTime = datetime.datetime.now(),
222 profileNotes = profileNotes,
223 bonnerRequirements = bonnerRequirements,
224 managersList = managersList,
225 participatedInLabor = getCeltsLaborHistory(volunteer),
226 totalSustainedEngagements = totalSustainedEngagements,
227 )
228 abort(403)
230@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
231def emergencyContactInfo(username):
232 """
233 This loads the Emergency Contact Page
234 """
235 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
236 abort(403)
238 user = User.get(User.username == username)
240 if request.method == 'GET':
241 readOnly = g.current_user.username != username
242 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
243 return render_template ("/main/emergencyContactInfo.html",
244 username=username,
245 contactInfo=contactInfo,
246 readOnly=readOnly
247 )
249 elif request.method == 'POST':
250 if g.current_user.username != username:
251 abort(403)
253 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
254 if not rowsUpdated:
255 EmergencyContact.create(user = username, **request.form)
256 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s emergency contact information.")
257 flash('Emergency contact information saved successfully!', 'success')
259 if request.args.get('action') == 'exit':
260 return redirect (f"/profile/{username}")
261 else:
262 return redirect (f"/profile/{username}/insuranceInfo")
264@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST'])
265def insuranceInfo(username):
266 """
267 This loads the Insurance Information Page
268 """
269 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
270 abort(403)
272 user = User.get(User.username == username)
274 if request.method == 'GET':
275 readOnly = g.current_user.username != username
276 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username)
277 return render_template ("/main/insuranceInfo.html",
278 username=username,
279 userInsuranceInfo=userInsuranceInfo,
280 readOnly=readOnly
281 )
283 # Save the form data
284 elif request.method == 'POST':
285 if g.current_user.username != username:
286 abort(403)
288 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
289 if not rowsUpdated:
290 InsuranceInfo.create(user = username, **request.form)
291 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.")
292 flash('Insurance information saved successfully!', 'success')
294 if request.args.get('action') == 'exit':
295 return redirect (f"/profile/{username}")
296 else:
297 return redirect (f"/profile/{username}/emergencyContact")
299@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
300def travelForm(username):
301 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
302 abort(403)
304 user = (User.select(User, EmergencyContact, InsuranceInfo)
305 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
306 .join(InsuranceInfo, JOIN.LEFT_OUTER)
307 .where(User.username == username).limit(1))
308 if not list(user):
309 abort(404)
310 userData = list(user.dicts())[0]
311 userData = {key: value if value else '' for (key, value) in userData.items()}
313 return render_template ('/main/travelForm.html',
314 userData = userData
315 )
318@main_bp.route('/profile/addNote', methods=['POST'])
319def addNote():
320 """
321 This function adds a note to the user's profile.
322 """
323 postData = request.form
324 try:
325 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
326 flash("Successfully added profile note", "success")
327 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
328 except Exception as e:
329 print("Error adding note", e)
330 flash("Failed to add profile note", "danger")
331 return "Failed to add profile note", 500
333@main_bp.route('/<username>/deleteNote', methods=['POST'])
334def deleteNote(username):
335 """
336 This function deletes a note from the user's profile.
337 """
338 try:
339 deleteProfileNote(request.form["id"])
340 flash("Successfully deleted profile note", "success")
341 except Exception as e:
342 print("Error deleting note", e)
343 flash("Failed to delete profile note", "danger")
344 return "success"
346# ===========================Ban===============================================
347@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
348def ban(program_id, username):
349 """
350 This function updates the ban status of a username either when they are banned from a program.
351 program_id: the primary id of the program the student is being banned from
352 username: unique value of a user to correctly identify them
353 """
354 postData = request.form
355 banNote = postData["note"] # This contains the note left about the change
356 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
357 try:
358 banUser(program_id, username, banNote, banEndDate, g.current_user)
359 programInfo = Program.get(int(program_id))
360 flash("Successfully banned the volunteer", "success")
361 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
362 return "Successfully banned the volunteer."
363 except Exception as e:
364 print("Error while updating ban", e)
365 flash("Failed to ban the volunteer", "danger")
366 return "Failed to ban the volunteer", 500
368# ===========================Unban===============================================
369@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
370def unban(program_id, username):
371 """
372 This function updates the ban status of a username either when they are unbanned from a program.
373 program_id: the primary id of the program the student is being unbanned from
374 username: unique value of a user to correctly identify them
375 """
376 postData = request.form
377 unbanNote = postData["note"] # This contains the note left about the change
378 try:
379 unbanUser(program_id, username, unbanNote, g.current_user)
380 programInfo = Program.get(int(program_id))
381 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
382 flash("Successfully unbanned the volunteer", "success")
383 return "Successfully unbanned the volunteer"
385 except Exception as e:
386 print("Error while updating Unban", e)
387 flash("Failed to unban the volunteer", "danger")
388 return "Failed to unban the volunteer", 500
391@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
392def addInterest(program_id, username):
393 """
394 This function adds a program to the list of programs a user interested in
395 program_id: the primary id of the program the student is adding interest of
396 username: unique value of a user to correctly identify them
397 """
398 try:
399 success = addUserInterest(program_id, username)
400 if success:
401 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
402 return ""
403 else:
404 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
406 except Exception as e:
407 print(e)
408 return "Error Updating Interest", 500
410@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
411def removeInterest(program_id, username):
412 """
413 This function removes a program to the list of programs a user interested in
414 program_id: the primary id of the program the student is adding interest of
415 username: unique value of a user to correctly identify them
416 """
417 try:
418 removed = removeUserInterest(program_id, username)
419 if removed:
420 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
421 return ""
422 else:
423 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
424 except Exception as e:
425 print(e)
426 return "Error Updating Interest", 500
428@main_bp.route('/rsvpForEvent', methods = ['POST'])
429def volunteerRegister():
430 """
431 This function selects the user ID and event ID and registers the user
432 for the event they have clicked register for.
433 """
434 event = Event.get_by_id(request.form['id'])
435 program = event.program
436 user = g.current_user
438 isAdded = checkUserRsvp(user, event)
439 isEligible = isEligibleForProgram(program, user)
440 listOfRequirements = unattendedRequiredEvents(program, user)
442 personAdded = False
443 if isEligible:
444 personAdded = addPersonToEvent(user, event)
445 if personAdded and listOfRequirements:
446 reqListToString = ', '.join(listOfRequirements)
447 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
448 elif personAdded:
449 flash("Successfully registered for event!","success")
450 else:
451 flash(f"RSVP Failed due to an unknown error.", "danger")
452 else:
453 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
456 if 'from' in request.form:
457 if request.form['from'] == 'ajax':
458 return ''
459 return redirect(url_for("admin.eventDisplay", eventId=event.id))
461@main_bp.route('/rsvpRemove', methods = ['POST'])
462def RemoveRSVP():
463 """
464 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
465 """
466 eventData = request.form
467 event = Event.get_by_id(eventData['id'])
469 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
470 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
471 currentRsvpParticipant.delete_instance()
472 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
473 flash("Successfully unregistered for event!", "success")
474 if 'from' in eventData:
475 if eventData['from'] == 'ajax':
476 return ''
477 return redirect(url_for("admin.eventDisplay", eventId=event.id))
479@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
480def serviceTranscript(username):
481 user = User.get_or_none(User.username == username)
482 if user is None:
483 abort(404)
484 if user != g.current_user and not g.current_user.isAdmin:
485 abort(403)
487 slCourses = getSlCourseTranscript(username)
488 totalHours = getTotalHours(username)
489 allEventTranscript = getProgramTranscript(username)
490 startDate = getStartYear(username)
491 return render_template('main/serviceTranscript.html',
492 allEventTranscript = allEventTranscript,
493 slCourses = slCourses.objects(),
494 totalHours = totalHours,
495 startDate = startDate,
496 userData = user)
498@main_bp.route('/searchUser/<query>', methods = ['GET'])
499def searchUser(query):
501 category= request.args.get("category")
503 '''Accepts user input and queries the database returning results that matches user search'''
504 try:
505 query = query.strip()
506 search = query.upper()
507 splitSearch = search.split()
508 searchResults = searchUsers(query,category)
509 return searchResults
510 except Exception as e:
511 print(e)
512 return "Error in searching for user", 500
514@main_bp.route('/contributors',methods = ['GET'])
515def contributors():
516 return render_template("/contributors.html")
518@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
519def getDietInfo():
520 dietaryInfo = request.form
521 user = dietaryInfo["user"]
522 dietInfo = dietaryInfo["dietInfo"]
524 if (g.current_user.username == user) or g.current_user.isAdmin:
525 updateDietInfo(user, dietInfo)
526 userInfo = User.get(User.username == user)
527 if len(dietInfo) > 0:
528 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
529 else:
530 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
533 return " "
535@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
536def indicateMinorInterest(username):
537 if g.current_user.isCeltsAdmin or g.current_user.username == username:
538 toggleMinorInterest(username)
540 else:
541 abort(403)
543 return ""