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
« 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
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.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 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 )
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
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]
136 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
137 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
139 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
140 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
142 allBackgroundHistory = getUserBGCheckHistory(volunteer)
143 backgroundTypes = list(BackgroundCheckType.select())
145 eligibilityTable = []
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)
166 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
168 managersProgramDict = getManagerProgramDict(g.current_user)
169 managersList = [id[1] for id in managersProgramDict.items()]
170 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
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)
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)
200 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
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 )
210 elif request.method == 'POST':
211 if g.current_user.username != username:
212 abort(403)
214 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
215 if not rowsUpdated:
216 EmergencyContact.create(user = username, **request.form)
218 createAdminLog(f"{g.current_user.fullName} updated {contactInfo.user.fullName}'s emergency contact information.")
220 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.")
222 flash('Emergency contact information saved successfully!', 'success')
224 if request.args.get('action') == 'exit':
225 return redirect (f"/profile/{username}")
226 else:
227 return redirect (f"/profile/{username}/insuranceInfo")
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)
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 )
246 # Save the form data
247 elif request.method == 'POST':
248 if g.current_user.username != username:
249 abort(403)
251 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
252 if rowsUpdated:
253 InsuranceInfo.create(user = username, **request.form)
255 createAdminLog(f"{g.current_user.fullName} updated { userInsuranceInfo.user.fullName}'s insurance information.")
257 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.")
259 flash('Insurance information saved successfully!', 'success')
261 if request.args.get('action') == 'exit':
262 return redirect (f"/profile/{username}")
263 else:
264 return redirect (f"/profile/{username}/emergencyContact")
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)
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()}
280 return render_template ('/main/travelForm.html',
281 userData = userData
282 )
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
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"
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
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"
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
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")
373 except Exception as e:
374 print(e)
375 return "Error Updating Interest", 500
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
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
405 isAdded = checkUserRsvp(user, event)
406 isEligible = isEligibleForProgram(program, user)
407 listOfRequirements = unattendedRequiredEvents(program, user)
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")
423 if 'from' in request.form:
424 if request.form['from'] == 'ajax':
425 return ''
426 return redirect(url_for("admin.eventDisplay", eventId=event.id))
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'])
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))
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)
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)
465@main_bp.route('/searchUser/<query>', methods = ['GET'])
466def searchUser(query):
468 category= request.args.get("category")
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
481@main_bp.route('/contributors',methods = ['GET'])
482def contributors():
483 return render_template("/contributors.html")
485@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
486def getDietInfo():
487 dietaryInfo = request.form
488 user = dietaryInfo["user"]
489 dietInfo = dietaryInfo["dietInfo"]
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.")
500 return " "
502@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
503def indicateMinorInterest(username):
504 toggleMinorInterest(username)
506 return ""