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
« 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
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) and (Event.isCanceled == False) and (Event.isPast == False))
55 .distinct()
56 .execute()) # Ensure only unique programs are included
58 return render_template("/main/landingPage.html",
59 managerProgramDict=managerProgramDict,
60 term=g.current_term,
61 programsWithEventsList=programsWithEventsList)
64@main_bp.route('/goToEventsList/<programID>', methods=['GET'])
65def goToEventsList(programID):
66 return {"activeTab": getActiveEventTab(programID)}
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()
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]
81 term = Term.get_by_id(currentTerm)
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)
90 managersProgramDict = getManagerProgramDict(g.current_user)
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 )
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
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]
132 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
133 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
135 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
136 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
138 allBackgroundHistory = getUserBGCheckHistory(volunteer)
139 backgroundTypes = list(BackgroundCheckType.select())
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)
161 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
163 managersProgramDict = getManagerProgramDict(g.current_user)
164 managersList = [id[1] for id in managersProgramDict.items()]
165 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
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)
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)
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 )
205 elif request.method == 'POST':
206 if g.current_user.username != username:
207 abort(403)
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')
215 if request.args.get('action') == 'exit':
216 return redirect (f"/profile/{username}")
217 else:
218 return redirect (f"/profile/{username}/insuranceInfo")
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)
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 )
237 # Save the form data
238 elif request.method == 'POST':
239 if g.current_user.username != username:
240 abort(403)
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')
248 if request.args.get('action') == 'exit':
249 return redirect (f"/profile/{username}")
250 else:
251 return redirect (f"/profile/{username}/emergencyContact")
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)
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()}
267 return render_template ('/main/travelForm.html',
268 userData = userData
269 )
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
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"
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
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"
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
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")
360 except Exception as e:
361 print(e)
362 return "Error Updating Interest", 500
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
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
392 isAdded = checkUserRsvp(user, event)
393 isEligible = isEligibleForProgram(program, user)
394 listOfRequirements = unattendedRequiredEvents(program, user)
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")
410 if 'from' in request.form:
411 if request.form['from'] == 'ajax':
412 return ''
413 return redirect(url_for("admin.eventDisplay", eventId=event.id))
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'])
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))
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)
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)
452@main_bp.route('/searchUser/<query>', methods = ['GET'])
453def searchUser(query):
455 category= request.args.get("category")
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
468@main_bp.route('/contributors',methods = ['GET'])
469def contributors():
470 return render_template("/contributors.html")
472@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
473def getDietInfo():
474 dietaryInfo = request.form
475 user = dietaryInfo["user"]
476 dietInfo = dietaryInfo["dietInfo"]
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.")
487 return " "
489@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
490def indicateMinorInterest(username):
491 toggleMinorInterest(username)
493 return ""