Coverage for app/controllers/main/routes.py: 27%
316 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-23 14:59 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-23 14:59 +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, make_response, session
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 # Fetch toggle state from session
97 toggle_state = session.get('toggleState', 'unchecked')
99 return render_template("/events/event_list.html",
100 selectedTerm = term,
101 studentLedEvents = studentLedEvents,
102 trainingEvents = trainingEvents,
103 bonnerEvents = bonnerEvents,
104 otherEvents = otherEvents,
105 listOfTerms = listOfTerms,
106 rsvpedEventsID = rsvpedEventsID,
107 currentEventRsvpAmount = currentEventRsvpAmount,
108 currentTime = currentTime,
109 user = g.current_user,
110 activeTab = activeTab,
111 programID = int(programID),
112 managersProgramDict = managersProgramDict,
113 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents,
114 toggle_state = toggle_state
115 )
117@main_bp.route('/updateToggleState', methods=['POST'])
118def update_toggle_state():
119 toggle_state = request.form.get('toggleState')
121 # Update session with toggle state
122 session['toggleState'] = toggle_state
124 return "", 200
126@main_bp.route('/profile/<username>', methods=['GET'])
127def viewUsersProfile(username):
128 """
129 This function displays the information of a volunteer to the user
130 """
131 try:
132 volunteer = User.get(User.username == username)
133 except Exception as e:
134 if g.current_user.isAdmin:
135 flash(f"{username} does not exist! ", category='danger')
136 return redirect(url_for('admin.studentSearchPage'))
137 else:
138 abort(403) # Error 403 if non admin/student-staff user trys to access via url
140 if (g.current_user == volunteer) or g.current_user.isAdmin:
141 upcomingEvents = getUpcomingEventsForUser(volunteer)
142 participatedEvents = getParticipatedEventsForUser(volunteer)
143 programs = Program.select()
144 if not g.current_user.isBonnerScholar and not g.current_user.isAdmin:
145 programs = programs.where(Program.isBonnerScholars == False)
146 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == volunteer)
147 programsInterested = [interest.program for interest in interests]
149 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
150 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
152 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
153 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
155 allBackgroundHistory = getUserBGCheckHistory(volunteer)
156 backgroundTypes = list(BackgroundCheckType.select())
158 eligibilityTable = []
160 for program in programs:
161 banNotes = list(ProgramBan.select(ProgramBan, Note)
162 .join(Note, on=(ProgramBan.banNote == Note.id))
163 .where(ProgramBan.user == volunteer,
164 ProgramBan.program == program,
165 ProgramBan.endDate > datetime.datetime.now()).execute())
166 userParticipatedTrainingEvents = getParticipationStatusForTrainings(program, [volunteer], g.current_term)
167 try:
168 allTrainingsComplete = False not in [attended for event, attended in userParticipatedTrainingEvents[username]] # Did volunteer attend all events
169 except KeyError:
170 allTrainingsComplete = False
171 noteForDict = banNotes[-1].banNote.noteContent if banNotes else ""
172 eligibilityTable.append({"program": program,
173 "completedTraining": allTrainingsComplete,
174 "trainingList": userParticipatedTrainingEvents,
175 "isNotBanned": (not banNotes),
176 "banNote": noteForDict})
177 profileNotes = ProfileNote.select().where(ProfileNote.user == volunteer)
179 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
181 managersProgramDict = getManagerProgramDict(g.current_user)
182 managersList = [id[1] for id in managersProgramDict.items()]
183 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
185 return render_template ("/main/userProfile.html",
186 programs = programs,
187 programsInterested = programsInterested,
188 upcomingEvents = upcomingEvents,
189 participatedEvents = participatedEvents,
190 rsvpedEvents = rsvpedEvents,
191 permissionPrograms = permissionPrograms,
192 eligibilityTable = eligibilityTable,
193 volunteer = volunteer,
194 backgroundTypes = backgroundTypes,
195 allBackgroundHistory = allBackgroundHistory,
196 currentDateTime = datetime.datetime.now(),
197 profileNotes = profileNotes,
198 bonnerRequirements = bonnerRequirements,
199 managersList = managersList,
200 participatedInLabor = getCeltsLaborHistory(volunteer),
201 totalSustainedEngagements = totalSustainedEngagements,
202 )
203 abort(403)
205@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
206def emergencyContactInfo(username):
207 """
208 This loads the Emergency Contact Page
209 """
210 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
211 abort(403)
214 if request.method == 'GET':
215 readOnly = g.current_user.username != username
216 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
217 return render_template ("/main/emergencyContactInfo.html",
218 username=username,
219 contactInfo=contactInfo,
220 readOnly=readOnly
221 )
223 elif request.method == 'POST':
224 if g.current_user.username != username:
225 abort(403)
227 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
228 if not rowsUpdated:
229 EmergencyContact.create(user = username, **request.form)
230 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.")
231 flash('Emergency contact information saved successfully!', 'success')
233 if request.args.get('action') == 'exit':
234 return redirect (f"/profile/{username}")
235 else:
236 return redirect (f"/profile/{username}/insuranceInfo")
238@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST'])
239def insuranceInfo(username):
240 """
241 This loads the Insurance Information Page
242 """
243 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
244 abort(403)
246 if request.method == 'GET':
247 readOnly = g.current_user.username != username
248 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username)
249 return render_template ("/main/insuranceInfo.html",
250 username=username,
251 userInsuranceInfo=userInsuranceInfo,
252 readOnly=readOnly
253 )
255 # Save the form data
256 elif request.method == 'POST':
257 if g.current_user.username != username:
258 abort(403)
260 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
261 if not rowsUpdated:
262 InsuranceInfo.create(user = username, **request.form)
263 createActivityLog(f"{g.current_user} updated {username}'s emergency contact information.")
264 flash('Insurance information saved successfully!', 'success')
266 if request.args.get('action') == 'exit':
267 return redirect (f"/profile/{username}")
268 else:
269 return redirect (f"/profile/{username}/emergencyContact")
271@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
272def travelForm(username):
273 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
274 abort(403)
276 user = (User.select(User, EmergencyContact, InsuranceInfo)
277 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
278 .join(InsuranceInfo, JOIN.LEFT_OUTER)
279 .where(User.username == username).limit(1))
280 if not list(user):
281 abort(404)
282 userData = list(user.dicts())[0]
283 userData = {key: value if value else '' for (key, value) in userData.items()}
285 return render_template ('/main/travelForm.html',
286 userData = userData
287 )
290@main_bp.route('/profile/addNote', methods=['POST'])
291def addNote():
292 """
293 This function adds a note to the user's profile.
294 """
295 postData = request.form
296 try:
297 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
298 flash("Successfully added profile note", "success")
299 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
300 except Exception as e:
301 print("Error adding note", e)
302 flash("Failed to add profile note", "danger")
303 return "Failed to add profile note", 500
305@main_bp.route('/<username>/deleteNote', methods=['POST'])
306def deleteNote(username):
307 """
308 This function deletes a note from the user's profile.
309 """
310 try:
311 deleteProfileNote(request.form["id"])
312 flash("Successfully deleted profile note", "success")
313 except Exception as e:
314 print("Error deleting note", e)
315 flash("Failed to delete profile note", "danger")
316 return "success"
318# ===========================Ban===============================================
319@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
320def ban(program_id, username):
321 """
322 This function updates the ban status of a username either when they are banned from a program.
323 program_id: the primary id of the program the student is being banned from
324 username: unique value of a user to correctly identify them
325 """
326 postData = request.form
327 banNote = postData["note"] # This contains the note left about the change
328 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
329 try:
330 banUser(program_id, username, banNote, banEndDate, g.current_user)
331 programInfo = Program.get(int(program_id))
332 flash("Successfully banned the volunteer", "success")
333 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
334 return "Successfully banned the volunteer."
335 except Exception as e:
336 print("Error while updating ban", e)
337 flash("Failed to ban the volunteer", "danger")
338 return "Failed to ban the volunteer", 500
340# ===========================Unban===============================================
341@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
342def unban(program_id, username):
343 """
344 This function updates the ban status of a username either when they are unbanned from a program.
345 program_id: the primary id of the program the student is being unbanned from
346 username: unique value of a user to correctly identify them
347 """
348 postData = request.form
349 unbanNote = postData["note"] # This contains the note left about the change
350 try:
351 unbanUser(program_id, username, unbanNote, g.current_user)
352 programInfo = Program.get(int(program_id))
353 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
354 flash("Successfully unbanned the volunteer", "success")
355 return "Successfully unbanned the volunteer"
357 except Exception as e:
358 print("Error while updating Unban", e)
359 flash("Failed to unban the volunteer", "danger")
360 return "Failed to unban the volunteer", 500
363@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
364def addInterest(program_id, username):
365 """
366 This function adds a program to the list of programs a user interested in
367 program_id: the primary id of the program the student is adding interest of
368 username: unique value of a user to correctly identify them
369 """
370 try:
371 success = addUserInterest(program_id, username)
372 if success:
373 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
374 return ""
375 else:
376 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
382@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
383def removeInterest(program_id, username):
384 """
385 This function removes a program to the list of programs a user interested in
386 program_id: the primary id of the program the student is adding interest of
387 username: unique value of a user to correctly identify them
388 """
389 try:
390 removed = removeUserInterest(program_id, username)
391 if removed:
392 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
393 return ""
394 else:
395 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
396 except Exception as e:
397 print(e)
398 return "Error Updating Interest", 500
400@main_bp.route('/rsvpForEvent', methods = ['POST'])
401def volunteerRegister():
402 """
403 This function selects the user ID and event ID and registers the user
404 for the event they have clicked register for.
405 """
406 event = Event.get_by_id(request.form['id'])
407 program = event.program
408 user = g.current_user
410 isAdded = checkUserRsvp(user, event)
411 isEligible = isEligibleForProgram(program, user)
412 listOfRequirements = unattendedRequiredEvents(program, user)
414 personAdded = False
415 if isEligible:
416 personAdded = addPersonToEvent(user, event)
417 if personAdded and listOfRequirements:
418 reqListToString = ', '.join(listOfRequirements)
419 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
420 elif personAdded:
421 flash("Successfully registered for event!","success")
422 else:
423 flash(f"RSVP Failed due to an unknown error.", "danger")
424 else:
425 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
428 if 'from' in request.form:
429 if request.form['from'] == 'ajax':
430 return ''
431 return redirect(url_for("admin.eventDisplay", eventId=event.id))
433@main_bp.route('/rsvpRemove', methods = ['POST'])
434def RemoveRSVP():
435 """
436 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
437 """
438 eventData = request.form
439 event = Event.get_by_id(eventData['id'])
441 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
442 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
443 currentRsvpParticipant.delete_instance()
444 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
445 flash("Successfully unregistered for event!", "success")
446 if 'from' in eventData:
447 if eventData['from'] == 'ajax':
448 return ''
449 return redirect(url_for("admin.eventDisplay", eventId=event.id))
451@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
452def serviceTranscript(username):
453 user = User.get_or_none(User.username == username)
454 if user is None:
455 abort(404)
456 if user != g.current_user and not g.current_user.isAdmin:
457 abort(403)
459 slCourses = getSlCourseTranscript(username)
460 totalHours = getTotalHours(username)
461 allEventTranscript = getProgramTranscript(username)
462 startDate = getStartYear(username)
463 return render_template('main/serviceTranscript.html',
464 allEventTranscript = allEventTranscript,
465 slCourses = slCourses.objects(),
466 totalHours = totalHours,
467 startDate = startDate,
468 userData = user)
470@main_bp.route('/searchUser/<query>', methods = ['GET'])
471def searchUser(query):
473 category= request.args.get("category")
475 '''Accepts user input and queries the database returning results that matches user search'''
476 try:
477 query = query.strip()
478 search = query.upper()
479 splitSearch = search.split()
480 searchResults = searchUsers(query,category)
481 return searchResults
482 except Exception as e:
483 print(e)
484 return "Error in searching for user", 500
486@main_bp.route('/contributors',methods = ['GET'])
487def contributors():
488 return render_template("/contributors.html")
490@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
491def getDietInfo():
492 dietaryInfo = request.form
493 user = dietaryInfo["user"]
494 dietInfo = dietaryInfo["dietInfo"]
496 if (g.current_user.username == user) or g.current_user.isAdmin:
497 updateDietInfo(user, dietInfo)
498 userInfo = User.get(User.username == user)
499 if len(dietInfo) > 0:
500 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
501 else:
502 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
505 return " "
507@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
508def indicateMinorInterest(username):
509 if g.current_user.isCeltsAdmin or g.current_user.username == username:
510 toggleMinorInterest(username)
512 else:
513 abort(403)
515 return ""