Coverage for app/controllers/main/routes.py: 23%
389 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-12-18 19:28 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-12-18 19:28 +0000
1import json
2import datetime
3from peewee import JOIN, DoesNotExist
4from http import cookies
5from playhouse.shortcuts import model_to_dict
6from flask import request, render_template, jsonify, g, abort, flash, redirect, url_for, make_response, session, request
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, getUpcomingVolunteerOpportunitiesCount, getVolunteerOpportunities, getBonnerEvents, getCeltsLabor, getEngagementEvents
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, declareMinorInterest, 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': "volunteerOpportunities", 'programID': 0})
73@main_bp.route('/eventsList/<selectedTerm>/', methods=['GET'], defaults={'activeTab': "volunteerOpportunities", 'programID': 0})
74@main_bp.route('/eventsList/<selectedTerm>/<activeTab>', methods=['GET'], defaults={'programID': 0})
75@main_bp.route('/eventsList/<selectedTerm>/<activeTab>/<programID>', methods=['GET'])
76def events(selectedTerm, activeTab, programID):
78 currentTime = datetime.datetime.now()
79 listOfTerms = Term.select().order_by(Term.termOrder)
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 = g.current_term
84 if selectedTerm:
85 term = selectedTerm
87 # Make sure we have a Term object
88 term = Term.get_or_none(Term.id == term)
89 if term is None:
90 term = Term.get(Term.isCurrentTerm == True)
92 currentEventRsvpAmount = getEventRsvpCountsForTerm(term)
93 volunteerOpportunities = getVolunteerOpportunities(term)
94 countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime)
95 trainingEvents = getTrainingEvents(term, g.current_user)
96 engagementEvents = getEngagementEvents(term)
97 bonnerEvents = getBonnerEvents(term)
98 celtsLabor = getCeltsLabor(term)
100 managersProgramDict = getManagerProgramDict(g.current_user)
102 # Fetch toggle state from session
103 toggleState = request.args.get('toggleState', 'unchecked')
105 # compile all volunteer opportunitiesevents into one list
106 studentEvents = []
107 for studentEvent in volunteerOpportunities.values():
108 studentEvents += studentEvent # add all contents of studentEvent to the studentEvents list
110 # Get the count of all term events for each category to display in the event list page.
111 volunteerOpportunitiesCount: int = len(studentEvents)
112 trainingEventsCount: int = len(trainingEvents)
113 engagementEventsCount: int = len(engagementEvents)
114 bonnerEventsCount: int = len(bonnerEvents)
115 celtsLaborCount: int = len(celtsLabor)
117 # gets only upcoming events to display in indicators
118 if (toggleState == 'unchecked'):
119 for event in trainingEvents:
120 if event.isPastEnd:
121 trainingEventsCount -= 1
122 for event in engagementEvents:
123 if event.isPastEnd:
124 engagementEventsCount -= 1
125 for event in bonnerEvents:
126 if event.isPastEnd:
127 bonnerEventsCount -= 1
128 for event in celtsLabor:
129 if event.isPastEnd:
130 celtsLaborCount -= 1
132 # Handle ajax request for Event category header number notifiers and toggle
133 if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
134 return jsonify({
135 "volunteerOpportunitiesCount": volunteerOpportunitiesCount,
136 "trainingEventsCount": trainingEventsCount,
137 "engagementEventsCount": engagementEventsCount,
138 "bonnerEventsCount": bonnerEventsCount,
139 "celtsLaborCount": celtsLaborCount,
140 "toggleStatus": toggleState
141 })
142 return render_template("/events/eventList.html",
143 selectedTerm = term,
144 volunteerOpportunities = volunteerOpportunities,
145 trainingEvents = trainingEvents,
146 engagementEvents = engagementEvents,
147 bonnerEvents = bonnerEvents,
148 celtsLabor = celtsLabor,
149 listOfTerms = listOfTerms,
150 rsvpedEventsID = rsvpedEventsID,
151 currentEventRsvpAmount = currentEventRsvpAmount,
152 currentTime = currentTime,
153 user = g.current_user,
154 activeTab = activeTab,
155 programID = int(programID),
156 managersProgramDict = managersProgramDict,
157 countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities,
158 toggleState = toggleState,
159 )
161@main_bp.route('/profile/<username>', methods=['GET'])
162def viewUsersProfile(username):
163 """
164 This function displays the information of a volunteer to the user
165 """
166 try:
167 volunteer = User.get(User.username == username)
168 except Exception as e:
169 if g.current_user.isAdmin:
170 flash(f"{username} does not exist! ", category='danger')
171 return redirect(url_for('admin.studentSearchPage'))
172 else:
173 abort(403) # Error 403 if non admin/student-staff user trys to access via url
175 if (g.current_user == volunteer) or g.current_user.isAdmin:
176 upcomingEvents = getUpcomingEventsForUser(volunteer)
177 participatedEvents = getParticipatedEventsForUser(volunteer)
178 programs = Program.select()
179 if not g.current_user.isBonnerScholar and not g.current_user.isAdmin:
180 programs = programs.where(Program.isBonnerScholars == False)
181 interests = Interest.select(Interest, Program).join(Program).where(Interest.user == volunteer)
182 programsInterested = [interest.program for interest in interests]
184 rsvpedEventsList = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == volunteer)
185 rsvpedEvents = [event.event.id for event in rsvpedEventsList]
187 programManagerPrograms = ProgramManager.select(ProgramManager, Program).join(Program).where(ProgramManager.user == volunteer)
188 permissionPrograms = [entry.program.id for entry in programManagerPrograms]
190 allBackgroundHistory = getUserBGCheckHistory(volunteer)
191 backgroundTypes = list(BackgroundCheckType.select())
195 eligibilityTable = []
197 for program in programs:
198 banNotes = list(ProgramBan.select(ProgramBan, Note)
199 .join(Note, on=(ProgramBan.banNote == Note.id))
200 .where(ProgramBan.user == volunteer,
201 ProgramBan.program == program,
202 ProgramBan.endDate > datetime.datetime.now()).execute())
203 onTranscriptQuery = list(ProgramBan.select(ProgramBan)
204 .where(ProgramBan.user == volunteer,
205 ProgramBan.program == program,
206 ProgramBan.unbanNote.is_null(),
207 ProgramBan.removeFromTranscript == 0))
209 onTranscript = True if len(onTranscriptQuery) > 0 else False
210 userParticipatedTrainingEvents = getParticipationStatusForTrainings(program, [volunteer], g.current_term)
211 try:
212 allTrainingsComplete = False not in [attended for event, attended in userParticipatedTrainingEvents[username]] # Did volunteer attend all events
213 except KeyError:
214 allTrainingsComplete = False
215 noteForDict = banNotes[-1].banNote.noteContent if banNotes else ""
216 eligibilityTable.append({"program": program,
217 "completedTraining": allTrainingsComplete,
218 "trainingList": userParticipatedTrainingEvents,
219 "isNotBanned": (not banNotes),
220 "banNote": noteForDict,
221 "onTranscript": onTranscript}),
223 profileNotes = ProfileNote.select().where(ProfileNote.user == volunteer)
225 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
227 managersProgramDict = getManagerProgramDict(g.current_user)
228 managersList = [id[1] for id in managersProgramDict.items()]
229 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
231 return render_template ("/main/userProfile.html",
232 username=username,
233 programs = programs,
234 programsInterested = programsInterested,
235 upcomingEvents = upcomingEvents,
236 participatedEvents = participatedEvents,
237 rsvpedEvents = rsvpedEvents,
238 permissionPrograms = permissionPrograms,
239 eligibilityTable = eligibilityTable,
240 volunteer = volunteer,
241 backgroundTypes = backgroundTypes,
242 allBackgroundHistory = allBackgroundHistory,
243 currentDateTime = datetime.datetime.now(),
244 profileNotes = profileNotes,
245 bonnerRequirements = bonnerRequirements,
246 managersList = managersList,
247 participatedInLabor = getCeltsLaborHistory(volunteer),
248 totalSustainedEngagements = totalSustainedEngagements,
249 )
250 abort(403)
252@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
253def emergencyContactInfo(username):
254 """
255 This loads the Emergency Contact Page
256 """
257 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
258 abort(403)
260 user = User.get(User.username == username)
262 if request.method == 'GET':
263 readOnly = g.current_user.username != username
264 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
265 return render_template ("/main/emergencyContactInfo.html",
266 username=username,
267 contactInfo=contactInfo,
268 readOnly=readOnly
269 )
271 elif request.method == 'POST':
272 if g.current_user.username != username:
273 abort(403)
275 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
276 if not rowsUpdated:
277 EmergencyContact.create(user = username, **request.form)
279 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s emergency contact information.")
280 flash('Emergency contact information saved successfully!', 'success')
282 if request.args.get('action') == 'exit':
283 return redirect (f"/profile/{username}")
284 else:
285 return redirect (f"/profile/{username}/insuranceInfo")
287@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST'])
288def insuranceInfo(username):
289 """
290 This loads the Insurance Information Page
291 """
292 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
293 abort(403)
295 user = User.get(User.username == username)
297 if request.method == 'GET':
298 readOnly = g.current_user.username != username
299 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username)
300 return render_template ("/main/insuranceInfo.html",
301 username=username,
302 userInsuranceInfo=userInsuranceInfo,
303 readOnly=readOnly
304 )
306 # Save the form data
307 elif request.method == 'POST':
308 if g.current_user.username != username:
309 abort(403)
311 InsuranceInfo.replace({**request.form, "user": username}).execute()
313 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.")
314 flash('Insurance information saved successfully!', 'success')
316 if request.args.get('action') == 'exit':
317 return redirect (f"/profile/{username}")
318 else:
319 return redirect (f"/profile/{username}/emergencyContact")
321@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
322def travelForm(username):
323 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
324 abort(403)
326 user = (User.select(User, EmergencyContact, InsuranceInfo)
327 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
328 .join(InsuranceInfo, JOIN.LEFT_OUTER)
329 .where(User.username == username).limit(1))
330 if not list(user):
331 abort(404)
332 userList = list(user.dicts())[0]
333 userList = [{key: value if value else '' for (key, value) in userList.items()}]
335 return render_template ('/main/travelForm.html',
336 userList = userList
337 )
339@main_bp.route('/event/<eventID>/travelForm', methods=['GET', 'POST'])
340def eventTravelForm(eventID):
341 try:
342 event = Event.get_by_id(eventID)
343 except DoesNotExist as e:
344 print(f"No event found for {eventID}", e)
345 abort(404)
347 if not (g.current_user.isCeltsAdmin):
348 abort(403)
350 if request.method == "POST" and request.form.getlist("username") != []:
351 usernameList = request.form.getlist("username")
352 usernameList = usernameList.copy()
353 userList = []
354 for username in usernameList:
355 user = (User.select(User, EmergencyContact, InsuranceInfo)
356 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
357 .join(InsuranceInfo, JOIN.LEFT_OUTER)
358 .where(User.username == username).limit(1))
359 if not list(username):
360 abort(404)
361 userData = list(user.dicts())[0]
362 userData = {key: value if value else '' for (key, value) in userData.items()}
363 userList.append(userData)
366 else:
367 return redirect(f"/event/{eventID}/volunteer_details")
370 return render_template ('/main/travelForm.html',
371 usernameList = usernameList,
372 userList = userList,
373 )
375@main_bp.route('/profile/addNote', methods=['POST'])
376def addNote():
377 """
378 This function adds a note to the user's profile.
379 """
380 postData = request.form
381 try:
382 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
383 flash("Successfully added profile note", "success")
384 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
385 except Exception as e:
386 print("Error adding note", e)
387 flash("Failed to add profile note", "danger")
388 return "Failed to add profile note", 500
390@main_bp.route('/<username>/deleteNote', methods=['POST'])
391def deleteNote(username):
392 """
393 This function deletes a note from the user's profile.
394 """
395 try:
396 deleteProfileNote(request.form["id"])
397 flash("Successfully deleted profile note", "success")
398 except Exception as e:
399 print("Error deleting note", e)
400 flash("Failed to delete profile note", "danger")
401 return "success"
403# ===========================Ban===============================================
404@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
405def ban(program_id, username):
406 """
407 This function updates the ban status of a username either when they are banned from a program.
408 program_id: the primary id of the program the student is being banned from
409 username: unique value of a user to correctly identify them
410 """
411 postData = request.form
412 banNote = postData["note"] # This contains the note left about the change
413 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
415 try:
416 banUser(program_id, username, banNote, banEndDate, g.current_user)
417 programInfo = Program.get(int(program_id))
418 flash("Successfully banned the volunteer", "success")
419 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
420 return "Successfully banned the volunteer."
421 except Exception as e:
422 print("Error while updating ban", e)
423 flash("Failed to ban the volunteer", "danger")
424 return "Failed to ban the volunteer", 500
426# ===========================Unban===============================================
427@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
428def unban(program_id, username):
429 """
430 This function updates the ban status of a username either when they are unbanned from a program.
431 program_id: the primary id of the program the student is being unbanned from
432 username: unique value of a user to correctly identify them
433 """
434 postData = request.form
435 unbanNote = postData["note"] # This contains the note left about the change
436 try:
437 unbanUser(program_id, username, unbanNote, g.current_user)
438 programInfo = Program.get(int(program_id))
439 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
440 flash("Successfully unbanned the volunteer", "success")
441 return "Successfully unbanned the volunteer"
443 except Exception as e:
444 print("Error while updating Unban", e)
445 flash("Failed to unban the volunteer", "danger")
446 return "Failed to unban the volunteer", 500
449@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
450def addInterest(program_id, username):
451 """
452 This function adds a program to the list of programs a user interested in
453 program_id: the primary id of the program the student is adding interest of
454 username: unique value of a user to correctly identify them
455 """
456 try:
457 success = addUserInterest(program_id, username)
458 if success:
459 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
460 return ""
461 else:
462 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
464 except Exception as e:
465 print(e)
466 return "Error Updating Interest", 500
468@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
469def removeInterest(program_id, username):
470 """
471 This function removes a program to the list of programs a user interested in
472 program_id: the primary id of the program the student is adding interest of
473 username: unique value of a user to correctly identify them
474 """
475 try:
476 removed = removeUserInterest(program_id, username)
477 if removed:
478 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
479 return ""
480 else:
481 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
482 except Exception as e:
483 print(e)
484 return "Error Updating Interest", 500
486@main_bp.route('/rsvpForEvent', methods = ['POST'])
487def volunteerRegister():
488 """
489 This function selects the user ID and event ID and registers the user
490 for the event they have clicked register for.
491 """
492 event = Event.get_by_id(request.form['id'])
493 program = event.program
494 user = g.current_user
496 isAdded = checkUserRsvp(user, event)
497 isEligible = isEligibleForProgram(program, user)
498 listOfRequirements = unattendedRequiredEvents(program, user)
500 personAdded = False
501 if isEligible:
502 personAdded = addPersonToEvent(user, event)
503 if personAdded and listOfRequirements:
504 reqListToString = ', '.join(listOfRequirements)
505 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
506 elif personAdded:
507 flash("Successfully registered for event!","success")
508 else:
509 flash(f"RSVP Failed due to an unknown error.", "danger")
510 else:
511 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
514 if 'from' in request.form:
515 if request.form['from'] == 'ajax':
516 return ''
517 return redirect(url_for("admin.eventDisplay", eventId=event.id))
519@main_bp.route('/rsvpRemove', methods = ['POST'])
520def RemoveRSVP():
521 """
522 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
523 """
524 eventData = request.form
525 event = Event.get_by_id(eventData['id'])
527 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
528 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
529 currentRsvpParticipant.delete_instance()
530 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
531 flash("Successfully unregistered for event!", "success")
532 if 'from' in eventData:
533 if eventData['from'] == 'ajax':
534 return ''
535 return redirect(url_for("admin.eventDisplay", eventId=event.id))
537@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
538def serviceTranscript(username):
539 user = User.get_or_none(User.username == username)
540 if user is None:
541 abort(404)
542 if user != g.current_user and not g.current_user.isAdmin:
543 abort(403)
545 slCourses = getSlCourseTranscript(username)
546 totalHours = getTotalHours(username)
547 allEventTranscript = getProgramTranscript(username)
548 startDate = getStartYear(username)
549 return render_template('main/serviceTranscript.html',
550 allEventTranscript = allEventTranscript,
551 slCourses = slCourses.objects(),
552 totalHours = totalHours,
553 startDate = startDate,
554 userData = user)
556@main_bp.route('/profile/<username>/updateTranscript/<program_id>', methods=['POST'])
557def updateTranscript(username, program_id):
558 # Check user permissions
559 user = User.get_or_none(User.username == username)
560 if user is None:
561 abort(404)
562 if user != g.current_user and not g.current_user.isAdmin:
563 abort(403)
565 # Get the data sent from the client-side JavaScript
566 data = request.json
568 # Retrieve removeFromTranscript value from the request data
569 removeFromTranscript = data.get('removeFromTranscript')
571 # Update the ProgramBan object matching the program_id and username
572 try:
573 bannedProgramForUser = ProgramBan.get((ProgramBan.program == program_id) & (ProgramBan.user == user) & (ProgramBan.unbanNote.is_null()))
574 bannedProgramForUser.removeFromTranscript = removeFromTranscript
575 bannedProgramForUser.save()
576 return jsonify({'status': 'success'})
577 except ProgramBan.DoesNotExist:
578 return jsonify({'status': 'error', 'message': 'ProgramBan not found'})
581@main_bp.route('/searchUser/<query>', methods = ['GET'])
582def searchUser(query):
584 category= request.args.get("category")
586 '''Accepts user input and queries the database returning results that matches user search'''
587 try:
588 query = query.strip()
589 search = query.upper()
590 splitSearch = search.split()
591 searchResults = searchUsers(query,category)
592 return searchResults
593 except Exception as e:
594 print(e)
595 return "Error in searching for user", 500
597@main_bp.route('/contributors',methods = ['GET'])
598def contributors():
599 return render_template("/contributors.html")
601@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
602def getDietInfo():
603 dietaryInfo = request.form
604 user = dietaryInfo["user"]
605 dietInfo = dietaryInfo["dietInfo"]
607 if (g.current_user.username == user) or g.current_user.isAdmin:
608 updateDietInfo(user, dietInfo)
609 userInfo = User.get(User.username == user)
610 if len(dietInfo) > 0:
611 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
612 else:
613 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
616 return " "
618@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
619def indicateMinorInterest(username):
620 if g.current_user.isCeltsAdmin or g.current_user.username == username:
621 data = request.get_json()
622 isAdding = data.get("isAdding", False)
624 toggleMinorInterest(username, isAdding)
626 else:
627 abort(403)
629 return ""
631@main_bp.route('/profile/<username>/updateMinorDeclaration', methods=["POST"])
632def updateMinorDeclaration(username):
633 if g.current_user.isCeltsAdmin or g.current_user.username == username:
634 declareMinorInterest(username)
635 flash("Candidate minor successfully updated", "success")
636 else:
637 flash("Error updating candidate minor status", "danger")
638 abort(403)
640 tab = request.args.get("tab", "interested")
641 return redirect(url_for('admin.manageMinor', tab=tab))