Coverage for app/controllers/main/routes.py: 23%
389 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-12-18 20:14 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-12-18 20:14 +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)
224 bonnerRequirements = getCertRequirementsWithCompletion(certification=Certification.BONNER, username=volunteer)
225 managersProgramDict = getManagerProgramDict(g.current_user)
226 managersList = [id[1] for id in managersProgramDict.items()]
227 totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
229 return render_template ("/main/userProfile.html",
230 username=username,
231 programs = programs,
232 programsInterested = programsInterested,
233 upcomingEvents = upcomingEvents,
234 participatedEvents = participatedEvents,
235 rsvpedEvents = rsvpedEvents,
236 permissionPrograms = permissionPrograms,
237 eligibilityTable = eligibilityTable,
238 volunteer = volunteer,
239 backgroundTypes = backgroundTypes,
240 allBackgroundHistory = allBackgroundHistory,
241 currentDateTime = datetime.datetime.now(),
242 profileNotes = profileNotes,
243 bonnerRequirements = bonnerRequirements,
244 managersList = managersList,
245 participatedInLabor = getCeltsLaborHistory(volunteer),
246 totalSustainedEngagements = totalSustainedEngagements,
247 )
248 abort(403)
250@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
251def emergencyContactInfo(username):
252 """
253 This loads the Emergency Contact Page
254 """
255 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
256 abort(403)
258 user = User.get(User.username == username)
260 if request.method == 'GET':
261 readOnly = g.current_user.username != username
262 contactInfo = EmergencyContact.get_or_none(EmergencyContact.user_id == username)
263 return render_template ("/main/emergencyContactInfo.html",
264 username=username,
265 contactInfo=contactInfo,
266 readOnly=readOnly
267 )
269 elif request.method == 'POST':
270 if g.current_user.username != username:
271 abort(403)
273 rowsUpdated = EmergencyContact.update(**request.form).where(EmergencyContact.user == username).execute()
274 if not rowsUpdated:
275 EmergencyContact.create(user = username, **request.form)
277 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s emergency contact information.")
278 flash('Emergency contact information saved successfully!', 'success')
280 if request.args.get('action') == 'exit':
281 return redirect (f"/profile/{username}")
282 else:
283 return redirect (f"/profile/{username}/insuranceInfo")
285@main_bp.route('/profile/<username>/insuranceInfo', methods=['GET', 'POST'])
286def insuranceInfo(username):
287 """
288 This loads the Insurance Information Page
289 """
290 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
291 abort(403)
293 user = User.get(User.username == username)
295 if request.method == 'GET':
296 readOnly = g.current_user.username != username
297 userInsuranceInfo = InsuranceInfo.get_or_none(InsuranceInfo.user == username)
298 return render_template ("/main/insuranceInfo.html",
299 username=username,
300 userInsuranceInfo=userInsuranceInfo,
301 readOnly=readOnly
302 )
304 # Save the form data
305 elif request.method == 'POST':
306 if g.current_user.username != username:
307 abort(403)
309 InsuranceInfo.replace({**request.form, "user": username}).execute()
311 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.")
312 flash('Insurance information saved successfully!', 'success')
314 if request.args.get('action') == 'exit':
315 return redirect (f"/profile/{username}")
316 else:
317 return redirect (f"/profile/{username}/emergencyContact")
319@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
320def travelForm(username):
321 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
322 abort(403)
324 user = (User.select(User, EmergencyContact, InsuranceInfo)
325 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
326 .join(InsuranceInfo, JOIN.LEFT_OUTER)
327 .where(User.username == username).limit(1))
328 if not list(user):
329 abort(404)
330 userList = list(user.dicts())[0]
331 userList = [{key: value if value else '' for (key, value) in userList.items()}]
333 return render_template ('/main/travelForm.html',
334 userList = userList
335 )
337@main_bp.route('/event/<eventID>/travelForm', methods=['GET', 'POST'])
338def eventTravelForm(eventID):
339 try:
340 event = Event.get_by_id(eventID)
341 except DoesNotExist as e:
342 print(f"No event found for {eventID}", e)
343 abort(404)
345 if not (g.current_user.isCeltsAdmin):
346 abort(403)
348 if request.method == "POST" and request.form.getlist("username") != []:
349 usernameList = request.form.getlist("username")
350 usernameList = usernameList.copy()
351 userList = []
352 for username in usernameList:
353 user = (User.select(User, EmergencyContact, InsuranceInfo)
354 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
355 .join(InsuranceInfo, JOIN.LEFT_OUTER)
356 .where(User.username == username).limit(1))
357 if not list(username):
358 abort(404)
359 userData = list(user.dicts())[0]
360 userData = {key: value if value else '' for (key, value) in userData.items()}
361 userList.append(userData)
364 else:
365 return redirect(f"/event/{eventID}/volunteer_details")
368 return render_template ('/main/travelForm.html',
369 usernameList = usernameList,
370 userList = userList,
371 )
373@main_bp.route('/profile/addNote', methods=['POST'])
374def addNote():
375 """
376 This function adds a note to the user's profile.
377 """
378 postData = request.form
379 try:
380 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
381 flash("Successfully added profile note", "success")
382 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
383 except Exception as e:
384 print("Error adding note", e)
385 flash("Failed to add profile note", "danger")
386 return "Failed to add profile note", 500
388@main_bp.route('/<username>/deleteNote', methods=['POST'])
389def deleteNote(username):
390 """
391 This function deletes a note from the user's profile.
392 """
393 try:
394 deleteProfileNote(request.form["id"])
395 flash("Successfully deleted profile note", "success")
396 except Exception as e:
397 print("Error deleting note", e)
398 flash("Failed to delete profile note", "danger")
399 return "success"
401# ===========================Ban===============================================
402@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
403def ban(program_id, username):
404 """
405 This function updates the ban status of a username either when they are banned from a program.
406 program_id: the primary id of the program the student is being banned from
407 username: unique value of a user to correctly identify them
408 """
409 postData = request.form
410 banNote = postData["note"] # This contains the note left about the change
411 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
413 try:
414 banUser(program_id, username, banNote, banEndDate, g.current_user)
415 programInfo = Program.get(int(program_id))
416 flash("Successfully banned the volunteer", "success")
417 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
418 return "Successfully banned the volunteer."
419 except Exception as e:
420 print("Error while updating ban", e)
421 flash("Failed to ban the volunteer", "danger")
422 return "Failed to ban the volunteer", 500
424# ===========================Unban===============================================
425@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
426def unban(program_id, username):
427 """
428 This function updates the ban status of a username either when they are unbanned from a program.
429 program_id: the primary id of the program the student is being unbanned from
430 username: unique value of a user to correctly identify them
431 """
432 postData = request.form
433 unbanNote = postData["note"] # This contains the note left about the change
434 try:
435 unbanUser(program_id, username, unbanNote, g.current_user)
436 programInfo = Program.get(int(program_id))
437 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
438 flash("Successfully unbanned the volunteer", "success")
439 return "Successfully unbanned the volunteer"
441 except Exception as e:
442 print("Error while updating Unban", e)
443 flash("Failed to unban the volunteer", "danger")
444 return "Failed to unban the volunteer", 500
447@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
448def addInterest(program_id, username):
449 """
450 This function adds a program to the list of programs a user interested in
451 program_id: the primary id of the program the student is adding interest of
452 username: unique value of a user to correctly identify them
453 """
454 try:
455 success = addUserInterest(program_id, username)
456 if success:
457 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
458 return ""
459 else:
460 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
462 except Exception as e:
463 print(e)
464 return "Error Updating Interest", 500
466@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
467def removeInterest(program_id, username):
468 """
469 This function removes a program to the list of programs a user interested in
470 program_id: the primary id of the program the student is adding interest of
471 username: unique value of a user to correctly identify them
472 """
473 try:
474 removed = removeUserInterest(program_id, username)
475 if removed:
476 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
477 return ""
478 else:
479 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
480 except Exception as e:
481 print(e)
482 return "Error Updating Interest", 500
484@main_bp.route('/rsvpForEvent', methods = ['POST'])
485def volunteerRegister():
486 """
487 This function selects the user ID and event ID and registers the user
488 for the event they have clicked register for.
489 """
490 event = Event.get_by_id(request.form['id'])
491 program = event.program
492 user = g.current_user
494 isAdded = checkUserRsvp(user, event)
495 isEligible = isEligibleForProgram(program, user)
496 listOfRequirements = unattendedRequiredEvents(program, user)
498 personAdded = False
499 if isEligible:
500 personAdded = addPersonToEvent(user, event)
501 if personAdded and listOfRequirements:
502 reqListToString = ', '.join(listOfRequirements)
503 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
504 elif personAdded:
505 flash("Successfully registered for event!","success")
506 else:
507 flash(f"RSVP Failed due to an unknown error.", "danger")
508 else:
509 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
512 if 'from' in request.form:
513 if request.form['from'] == 'ajax':
514 return ''
515 return redirect(url_for("admin.eventDisplay", eventId=event.id))
517@main_bp.route('/rsvpRemove', methods = ['POST'])
518def RemoveRSVP():
519 """
520 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
521 """
522 eventData = request.form
523 event = Event.get_by_id(eventData['id'])
525 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
526 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
527 currentRsvpParticipant.delete_instance()
528 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
529 flash("Successfully unregistered for event!", "success")
530 if 'from' in eventData:
531 if eventData['from'] == 'ajax':
532 return ''
533 return redirect(url_for("admin.eventDisplay", eventId=event.id))
535@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
536def serviceTranscript(username):
537 user = User.get_or_none(User.username == username)
538 if user is None:
539 abort(404)
540 if user != g.current_user and not g.current_user.isAdmin:
541 abort(403)
543 slCourses = getSlCourseTranscript(username)
544 totalHours = getTotalHours(username)
545 allEventTranscript = getProgramTranscript(username)
546 startDate = getStartYear(username)
547 return render_template('main/serviceTranscript.html',
548 allEventTranscript = allEventTranscript,
549 slCourses = slCourses.objects(),
550 totalHours = totalHours,
551 startDate = startDate,
552 userData = user)
554@main_bp.route('/profile/<username>/updateTranscript/<program_id>', methods=['POST'])
555def updateTranscript(username, program_id):
556 # Check user permissions
557 user = User.get_or_none(User.username == username)
558 if user is None:
559 abort(404)
560 if user != g.current_user and not g.current_user.isAdmin:
561 abort(403)
563 # Get the data sent from the client-side JavaScript
564 data = request.json
566 # Retrieve removeFromTranscript value from the request data
567 removeFromTranscript = data.get('removeFromTranscript')
569 # Update the ProgramBan object matching the program_id and username
570 try:
571 bannedProgramForUser = ProgramBan.get((ProgramBan.program == program_id) & (ProgramBan.user == user) & (ProgramBan.unbanNote.is_null()))
572 bannedProgramForUser.removeFromTranscript = removeFromTranscript
573 bannedProgramForUser.save()
574 return jsonify({'status': 'success'})
575 except ProgramBan.DoesNotExist:
576 return jsonify({'status': 'error', 'message': 'ProgramBan not found'})
579@main_bp.route('/searchUser/<query>', methods = ['GET'])
580def searchUser(query):
582 category= request.args.get("category")
584 '''Accepts user input and queries the database returning results that matches user search'''
585 try:
586 query = query.strip()
587 search = query.upper()
588 splitSearch = search.split()
589 searchResults = searchUsers(query,category)
590 return searchResults
591 except Exception as e:
592 print(e)
593 return "Error in searching for user", 500
595@main_bp.route('/contributors',methods = ['GET'])
596def contributors():
597 return render_template("/contributors.html")
599@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
600def getDietInfo():
601 dietaryInfo = request.form
602 user = dietaryInfo["user"]
603 dietInfo = dietaryInfo["dietInfo"]
605 if (g.current_user.username == user) or g.current_user.isAdmin:
606 updateDietInfo(user, dietInfo)
607 userInfo = User.get(User.username == user)
608 if len(dietInfo) > 0:
609 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
610 else:
611 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
614 return " "
616@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
617def indicateMinorInterest(username):
618 if g.current_user.isCeltsAdmin or g.current_user.username == username:
619 data = request.get_json()
620 isAdding = data.get("isAdding", False)
622 toggleMinorInterest(username, isAdding)
624 else:
625 abort(403)
627 return ""
629@main_bp.route('/profile/<username>/updateMinorDeclaration', methods=["POST"])
630def updateMinorDeclaration(username):
631 if g.current_user.isCeltsAdmin or g.current_user.username == username:
632 declareMinorInterest(username)
633 flash("Candidate minor successfully updated", "success")
634 else:
635 flash("Error updating candidate minor status", "danger")
636 abort(403)
638 tab = request.args.get("tab", "interested")
639 return redirect(url_for('admin.manageMinor', tab=tab))