Coverage for app/controllers/main/routes.py: 26%
308 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-01-29 16:34 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-01-29 16:34 +0000
1from flask import request, render_template, g, abort, flash, redirect, url_for
2from peewee import JOIN
3from playhouse.shortcuts import model_to_dict
4import datetime
5import json
6from http import cookies
8from app import app
9from app.models.program import Program
10from app.models.event import Event
11from app.models.backgroundCheck import BackgroundCheck
12from app.models.backgroundCheckType import BackgroundCheckType
13from app.models.user import User
14from app.models.eventParticipant import EventParticipant
15from app.models.interest import Interest
16from app.models.programBan import ProgramBan
17from app.models.term import Term
18from app.models.eventRsvp import EventRsvp
19from app.models.note import Note
20from app.models.profileNote import ProfileNote
21from app.models.programManager import ProgramManager
22from app.models.courseInstructor import CourseInstructor
23from app.models.certification import Certification
24from app.models.emergencyContact import EmergencyContact
25from app.models.insuranceInfo import InsuranceInfo
26from app.models.celtsLabor import CeltsLabor
28from app.controllers.main import main_bp
29from app.logic.loginManager import logout
30from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo
31from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent
32from app.logic.events import *
33from app.logic.searchUsers import searchUsers
34from app.logic.transcript import *
35from app.logic.landingPage import getManagerProgramDict, getActiveEventTab
36from app.logic.utils import selectSurroundingTerms
37from app.logic.certification import getCertRequirementsWithCompletion
38from app.logic.createLogs import createRsvpLog, createAdminLog
39from app.logic.celtsLabor import getCeltsLaborHistory
41@main_bp.route('/logout', methods=['GET'])
42def redirectToLogout():
43 return redirect(logout())
45@main_bp.route('/', methods=['GET'])
46def landingPage():
48 managerProgramDict = getManagerProgramDict(g.current_user)
50 # Optimize the query to fetch programs with non-canceled, non-past events in the current term
51 programsWithEventsList = list(Program.select(Program, Event)
52 .join(Event)
53 .where((Event.term == g.current_term) and (Event.isCanceled == False) and (Event.isPast == False))
54 .distinct()
55 .execute()) # Ensure only unique programs are included
57 return render_template("/main/landingPage.html",
58 managerProgramDict=managerProgramDict,
59 term=g.current_term,
60 programsWithEventsList=programsWithEventsList)
63@main_bp.route('/goToEventsList/<programID>', methods=['GET'])
64def goToEventsList(programID):
65 return {"activeTab": getActiveEventTab(programID)}
67@main_bp.route('/eventsList/<selectedTerm>', methods=['GET'], defaults={'activeTab': "studentLedEvents", 'programID': 0})
68@main_bp.route('/eventsList/<selectedTerm>/<activeTab>', methods=['GET'], defaults={'programID': 0})
69@main_bp.route('/eventsList/<selectedTerm>/<activeTab>/<programID>', methods=['GET'])
70def events(selectedTerm, activeTab, programID):
71 currentTerm = g.current_term
72 if selectedTerm:
73 currentTerm = selectedTerm
74 currentTime = datetime.datetime.now()
76 listOfTerms = Term.select()
77 participantRSVP = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == g.current_user)
78 rsvpedEventsID = [event.event.id for event in participantRSVP]
80 term = Term.get_by_id(currentTerm)
82 currentEventRsvpAmount = getEventRsvpCountsForTerm(term)
83 studentLedEvents = getStudentLedEvents(term)
84 countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime)
85 trainingEvents = getTrainingEvents(term, g.current_user)
86 bonnerEvents = getBonnerEvents(term)
87 otherEvents = getOtherEvents(term)
89 managersProgramDict = getManagerProgramDict(g.current_user)
91 return render_template("/events/event_list.html",
92 selectedTerm = term,
93 studentLedEvents = studentLedEvents,
94 trainingEvents = trainingEvents,
95 bonnerEvents = bonnerEvents,
96 otherEvents = otherEvents,
97 listOfTerms = listOfTerms,
98 rsvpedEventsID = rsvpedEventsID,
99 currentEventRsvpAmount = currentEventRsvpAmount,
100 currentTime = currentTime,
101 user = g.current_user,
102 activeTab = activeTab,
103 programID = int(programID),
104 managersProgramDict = managersProgramDict,
105 countUpcomingStudentLedEvents = countUpcomingStudentLedEvents
106 )
108@main_bp.route('/profile/<username>', methods=['GET'])
109def viewUsersProfile(username):
110 """
111 This function displays the information of a volunteer to the user
112 """
113 try:
114 volunteer = User.get(User.username == username)
115 except Exception as e:
116 if g.current_user.isAdmin:
117 flash(f"{username} does not exist! ", category='danger')
118 return redirect(url_for('admin.studentSearchPage'))
119 else:
120 abort(403) # Error 403 if non admin/student-staff user trys to access via url
122 if (g.current_user == volunteer) or g.current_user.isAdmin:
123 upcomingEvents = getUpcomingEventsForUser(volunteer)
124 participatedEvents = getParticipatedEventsForUser(volunteer)
125 programs = Program.select()
126 if not g.current_user.isBonnerScholar and not g.current_user.isAdmin:
127 programs = programs.where(Program.isBonnerScholars == False)
128 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == volunteer)
129 programsInterested = [interest.program for interest in interests]
131 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
132 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
134 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
135 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
137 allBackgroundHistory = getUserBGCheckHistory(volunteer)
138 backgroundTypes = list(BackgroundCheckType.select())
140 eligibilityTable = []
141 for program in programs:
142 banNotes = list(ProgramBan.select(ProgramBan, Note)
143 .join(Note, on=(ProgramBan.banNote == Note.id))
144 .where(ProgramBan.user == volunteer,
145 ProgramBan.program == program,
146 ProgramBan.endDate > datetime.datetime.now()).execute())
147 userParticipatedTrainingEvents = getParticipationStatusForTrainings(program, [volunteer], g.current_term)
148 try:
149 allTrainingsComplete = False not in [attended for event, attended in userParticipatedTrainingEvents[username]] # Did volunteer attend all events
150 except KeyError:
151 allTrainingsComplete = False
152 noteForDict = banNotes[-1].banNote.noteContent if banNotes else ""
153 eligibilityTable.append({"program": program,
154 "completedTraining": allTrainingsComplete,
155 "trainingList": userParticipatedTrainingEvents,
156 "isNotBanned": (not banNotes),
157 "banNote": noteForDict})
158 profileNotes = ProfileNote.select().where(ProfileNote.user == volunteer)
160 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
162 managersProgramDict = getManagerProgramDict(g.current_user)
163 managersList = [id[1] for id in managersProgramDict.items()]
164 participatedInLabor = getCeltsLaborHistory(volunteer)
166 return render_template ("/main/userProfile.html",
167 programs = programs,
168 programsInterested = programsInterested,
169 upcomingEvents = upcomingEvents,
170 participatedEvents = participatedEvents,
171 rsvpedEvents = rsvpedEvents,
172 permissionPrograms = permissionPrograms,
173 eligibilityTable = eligibilityTable,
174 volunteer = volunteer,
175 backgroundTypes = backgroundTypes,
176 allBackgroundHistory = allBackgroundHistory,
177 currentDateTime = datetime.datetime.now(),
178 profileNotes = profileNotes,
179 bonnerRequirements = bonnerRequirements,
180 managersList = managersList,
181 participatedInLabor = participatedInLabor,
182 )
183 abort(403)
185@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
186def emergencyContactInfo(username):
187 """
188 This loads the Emergency Contact Page
189 """
190 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
191 abort(403)
194 if request.method == 'GET':
195 readOnly = g.current_user.username != username
196 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
197 return render_template ("/main/emergencyContactInfo.html",
198 username=username,
199 contactInfo=contactInfo,
200 readOnly=readOnly
201 )
203 elif request.method == 'POST':
204 if g.current_user.username != username:
205 abort(403)
207 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
208 if not rowsUpdated:
209 EmergencyContact.create(user = username, **request.form)
210 createAdminLog(f"{g.current_user} updated {username}'s emergency contact information.")
211 flash('Emergency contact information saved successfully!', 'success')
213 if request.args.get('action') == 'exit':
214 return redirect (f"/profile/{username}")
215 else:
216 return redirect (f"/profile/{username}/insuranceInfo")
218@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST'])
219def insuranceInfo(username):
220 """
221 This loads the Insurance Information Page
222 """
223 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
224 abort(403)
226 if request.method == 'GET':
227 readOnly = g.current_user.username != username
228 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username)
229 return render_template ("/main/insuranceInfo.html",
230 username=username,
231 userInsuranceInfo=userInsuranceInfo,
232 readOnly=readOnly
233 )
235 # Save the form data
236 elif request.method == 'POST':
237 if g.current_user.username != username:
238 abort(403)
240 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
241 if not rowsUpdated:
242 InsuranceInfo.create(user = username, **request.form)
243 createAdminLog(f"{g.current_user} updated {username}'s emergency contact information.")
244 flash('Insurance information saved successfully!', 'success')
246 if request.args.get('action') == 'exit':
247 return redirect (f"/profile/{username}")
248 else:
249 return redirect (f"/profile/{username}/emergencyContact")
251@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
252def travelForm(username):
253 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
254 abort(403)
256 user = (User.select(User, EmergencyContact, InsuranceInfo)
257 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
258 .join(InsuranceInfo, JOIN.LEFT_OUTER)
259 .where(User.username == username).limit(1))
260 if not list(user):
261 abort(404)
262 userData = list(user.dicts())[0]
263 userData = {key: value if value else '' for (key, value) in userData.items()}
265 return render_template ('/main/travelForm.html',
266 userData = userData
267 )
270@main_bp.route('/profile/addNote', methods=['POST'])
271def addNote():
272 """
273 This function adds a note to the user's profile.
274 """
275 postData = request.form
276 try:
277 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
278 flash("Successfully added profile note", "success")
279 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
280 except Exception as e:
281 print("Error adding note", e)
282 flash("Failed to add profile note", "danger")
283 return "Failed to add profile note", 500
285@main_bp.route('/<username>/deleteNote', methods=['POST'])
286def deleteNote(username):
287 """
288 This function deletes a note from the user's profile.
289 """
290 try:
291 deleteProfileNote(request.form["id"])
292 flash("Successfully deleted profile note", "success")
293 except Exception as e:
294 print("Error deleting note", e)
295 flash("Failed to delete profile note", "danger")
296 return "success"
298# ===========================Ban===============================================
299@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
300def ban(program_id, username):
301 """
302 This function updates the ban status of a username either when they are banned from a program.
303 program_id: the primary id of the program the student is being banned from
304 username: unique value of a user to correctly identify them
305 """
306 postData = request.form
307 banNote = postData["note"] # This contains the note left about the change
308 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
309 try:
310 banUser(program_id, username, banNote, banEndDate, g.current_user)
311 programInfo = Program.get(int(program_id))
312 flash("Successfully banned the volunteer", "success")
313 createAdminLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
314 return "Successfully banned the volunteer."
315 except Exception as e:
316 print("Error while updating ban", e)
317 flash("Failed to ban the volunteer", "danger")
318 return "Failed to ban the volunteer", 500
320# ===========================Unban===============================================
321@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
322def unban(program_id, username):
323 """
324 This function updates the ban status of a username either when they are unbanned from a program.
325 program_id: the primary id of the program the student is being unbanned from
326 username: unique value of a user to correctly identify them
327 """
328 postData = request.form
329 unbanNote = postData["note"] # This contains the note left about the change
330 try:
331 unbanUser(program_id, username, unbanNote, g.current_user)
332 programInfo = Program.get(int(program_id))
333 createAdminLog(f'Unbanned {username} from {programInfo.programName}.')
334 flash("Successfully unbanned the volunteer", "success")
335 return "Successfully unbanned the volunteer"
337 except Exception as e:
338 print("Error while updating Unban", e)
339 flash("Failed to unban the volunteer", "danger")
340 return "Failed to unban the volunteer", 500
343@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
344def addInterest(program_id, username):
345 """
346 This function adds a program to the list of programs a user interested in
347 program_id: the primary id of the program the student is adding interest of
348 username: unique value of a user to correctly identify them
349 """
350 try:
351 success = addUserInterest(program_id, username)
352 if success:
353 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
354 return ""
355 else:
356 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
358 except Exception as e:
359 print(e)
360 return "Error Updating Interest", 500
362@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
363def removeInterest(program_id, username):
364 """
365 This function removes a program to the list of programs a user interested in
366 program_id: the primary id of the program the student is adding interest of
367 username: unique value of a user to correctly identify them
368 """
369 try:
370 removed = removeUserInterest(program_id, username)
371 if removed:
372 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
373 return ""
374 else:
375 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
376 except Exception as e:
377 print(e)
378 return "Error Updating Interest", 500
380@main_bp.route('/rsvpForEvent', methods = ['POST'])
381def volunteerRegister():
382 """
383 This function selects the user ID and event ID and registers the user
384 for the event they have clicked register for.
385 """
386 event = Event.get_by_id(request.form['id'])
387 program = event.program
388 user = g.current_user
390 isAdded = checkUserRsvp(user, event)
391 isEligible = isEligibleForProgram(program, user)
392 listOfRequirements = unattendedRequiredEvents(program, user)
394 personAdded = False
395 if isEligible:
396 personAdded = addPersonToEvent(user, event)
397 if personAdded and listOfRequirements:
398 reqListToString = ', '.join(listOfRequirements)
399 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
400 elif personAdded:
401 flash("Successfully registered for event!","success")
402 else:
403 flash(f"RSVP Failed due to an unknown error.", "danger")
404 else:
405 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
408 if 'from' in request.form:
409 if request.form['from'] == 'ajax':
410 return ''
411 return redirect(url_for("admin.eventDisplay", eventId=event.id))
413@main_bp.route('/rsvpRemove', methods = ['POST'])
414def RemoveRSVP():
415 """
416 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
417 """
418 eventData = request.form
419 event = Event.get_by_id(eventData['id'])
421 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
422 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
423 currentRsvpParticipant.delete_instance()
424 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
425 flash("Successfully unregistered for event!", "success")
426 if 'from' in eventData:
427 if eventData['from'] == 'ajax':
428 return ''
429 return redirect(url_for("admin.eventDisplay", eventId=event.id))
431@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
432def serviceTranscript(username):
433 user = User.get_or_none(User.username == username)
434 if user is None:
435 abort(404)
436 if user != g.current_user and not g.current_user.isAdmin:
437 abort(403)
439 slCourses = getSlCourseTranscript(username)
440 totalHours = getTotalHours(username)
441 allEventTranscript = getProgramTranscript(username)
442 startDate = getStartYear(username)
443 return render_template('main/serviceTranscript.html',
444 allEventTranscript = allEventTranscript,
445 slCourses = slCourses.objects(),
446 totalHours = totalHours,
447 startDate = startDate,
448 userData = user)
450@main_bp.route('/searchUser/<query>', methods = ['GET'])
451def searchUser(query):
453 category= request.args.get("category")
455 '''Accepts user input and queries the database returning results that matches user search'''
456 try:
457 query = query.strip()
458 search = query.upper()
459 splitSearch = search.split()
460 searchResults = searchUsers(query,category)
461 return searchResults
462 except Exception as e:
463 print(e)
464 return "Error in searching for user", 500
466@main_bp.route('/contributors',methods = ['GET'])
467def contributors():
468 return render_template("/contributors.html")
470@main_bp.route('/proposalReview/', methods = ['GET', 'POST'])
471def reviewProposal():
472 """
473 this function gets the submitted course id and returns the its data to the review proposal modal
474 """
475 courseID=request.form
476 course=Course.get_by_id(courseID["course_id"])
477 instructors_data=course.courseInstructors
478 return render_template('/main/reviewproposal.html',
479 course=course,
480 instructors_data=instructors_data)
482@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
483def getDietInfo():
484 dietaryInfo = request.form
485 user = dietaryInfo["user"]
486 dietInfo = dietaryInfo["dietInfo"]
488 if (g.current_user.username == user) or g.current_user.isAdmin:
489 updateDietInfo(user, dietInfo)
490 userInfo = User.get(User.username == user)
491 if len(dietInfo) > 0:
492 createAdminLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
493 else:
494 createAdminLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
497 return " "