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
« 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
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)
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]
60 return render_template("/main/landingPage.html",
61 managerProgramDict=managerProgramDict,
62 term=g.current_term,
63 programsWithEventsList = futureEvents)
66@main_bp.route('/goToEventsList/<programID>', methods=['GET'])
67def goToEventsList(programID):
68 return {"activeTab": getActiveEventTab(programID)}
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()
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]
83 term = Term.get_by_id(currentTerm)
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)
92 managersProgramDict = getManagerProgramDict(g.current_user)
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 )
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
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]
134 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
135 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
137 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
138 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
140 allBackgroundHistory = getUserBGCheckHistory(volunteer)
141 backgroundTypes = list(BackgroundCheckType.select())
143 eligibilityTable = []
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)
164 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
166 managersProgramDict = getManagerProgramDict(g.current_user)
167 managersList = [id[1] for id in managersProgramDict.items()]
168 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
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)
198 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
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 )
208 elif request.method == 'POST':
209 if g.current_user.username != username:
210 abort(403)
212 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
214 if rowsUpdated:
215 EmergencyContact.create(user = username, **request.form)
217 createActivityLog(f"{g.current_user.fullName} updated {contactInfo.user.fullName}'s emergency contact information.")
218 flash('Emergency contact information saved successfully!', 'success')
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)
233 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user_id == username)
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 )
243 # Save the form data
244 elif request.method == 'POST':
245 if g.current_user.username != username:
246 abort(403)
248 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
250 if rowsUpdated:
251 InsuranceInfo.create(user = username, **request.form)
253 createActivityLog(f"{g.current_user.fullName} updated { userInsuranceInfo.user.fullName}'s insurance information.")
254 flash('Insurance information saved successfully!', 'success')
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)
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()}
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
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"
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
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"
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
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")
366 except Exception as e:
367 print(e)
368 return "Error Updating Interest", 500
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
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
398 isAdded = checkUserRsvp(user, event)
399 isEligible = isEligibleForProgram(program, user)
400 listOfRequirements = unattendedRequiredEvents(program, user)
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")
416 if 'from' in request.form:
417 if request.form['from'] == 'ajax':
418 return ''
419 return redirect(url_for("admin.eventDisplay", eventId=event.id))
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'])
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))
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)
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)
458@main_bp.route('/searchUser/<query>', methods = ['GET'])
459def searchUser(query):
461 category= request.args.get("category")
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
474@main_bp.route('/contributors',methods = ['GET'])
475def contributors():
476 return render_template("/contributors.html")
478@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
479def getDietInfo():
480 dietaryInfo = request.form
481 user = dietaryInfo["user"]
482 dietInfo = dietaryInfo["dietInfo"]
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.")
493 return " "
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)
500 else:
501 abort(403)
503 return ""