Coverage for app/controllers/main/routes.py: 23%
384 statements
« prev ^ index » next coverage.py v7.10.2, created at 2026-03-10 19:32 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2026-03-10 19:32 +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
391@main_bp.route('/<username>/deleteNote', methods=['POST'])
392def deleteNote(username):
393 """
394 This function deletes a note from the user's profile.
395 """
396 try:
397 deleteProfileNote(request.form["id"])
398 flash("Successfully deleted profile note", "success")
399 except Exception as e:
400 print("Error deleting note", e)
401 flash("Failed to delete profile note", "danger")
402 return "success"
404# ===========================Ban===============================================
405@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
406def ban(program_id, username):
407 """
408 This function updates the ban status of a username either when they are banned from a program.
409 program_id: the primary id of the program the student is being banned from
410 username: unique value of a user to correctly identify them
411 """
412 postData = request.form
413 banNote = postData["note"] # This contains the note left about the change
414 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
416 try:
417 banUser(program_id, username, banNote, banEndDate, g.current_user)
418 programInfo = Program.get(int(program_id))
419 flash("Successfully banned the volunteer", "success")
420 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
421 return "Successfully banned the volunteer."
422 except Exception as e:
423 print("Error while updating ban", e)
424 flash("Failed to ban the volunteer", "danger")
425 return "Failed to ban the volunteer", 500
427# ===========================Unban===============================================
428@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
429def unban(program_id, username):
430 """
431 This function updates the ban status of a username either when they are unbanned from a program.
432 program_id: the primary id of the program the student is being unbanned from
433 username: unique value of a user to correctly identify them
434 """
435 postData = request.form
436 unbanNote = postData["note"] # This contains the note left about the change
437 try:
438 unbanUser(program_id, username, unbanNote, g.current_user)
439 programInfo = Program.get(int(program_id))
440 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
441 flash("Successfully unbanned the volunteer", "success")
442 return "Successfully unbanned the volunteer"
444 except Exception as e:
445 print("Error while updating Unban", e)
446 flash("Failed to unban the volunteer", "danger")
447 return "Failed to unban the volunteer", 500
450@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
451def addInterest(program_id, username):
452 """
453 This function adds a program to the list of programs a user interested in
454 program_id: the primary id of the program the student is adding interest of
455 username: unique value of a user to correctly identify them
456 """
457 try:
458 success = addUserInterest(program_id, username)
459 if success:
460 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
461 return ""
462 else:
463 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
465 except Exception as e:
466 print(e)
467 return "Error Updating Interest", 500
469@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
470def removeInterest(program_id, username):
471 """
472 This function removes a program to the list of programs a user interested in
473 program_id: the primary id of the program the student is adding interest of
474 username: unique value of a user to correctly identify them
475 """
476 try:
477 removed = removeUserInterest(program_id, username)
478 if removed:
479 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
480 return ""
481 else:
482 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
483 except Exception as e:
484 print(e)
485 return "Error Updating Interest", 500
487@main_bp.route('/rsvpForEvent', methods = ['POST'])
488def volunteerRegister():
489 """
490 This function selects the user ID and event ID and registers the user
491 for the event they have clicked register for.
492 """
493 event = Event.get_by_id(request.form['id'])
494 program = event.program
495 user = g.current_user
497 isEligible = isEligibleForProgram(program, user)
499 personAdded = False
500 if isEligible:
501 personAdded = addPersonToEvent(user, event)
502 if personAdded:
503 flash("Successfully registered for event!","success")
504 else:
505 flash(f"RSVP Failed due to an unknown error.", "danger")
506 else:
507 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
509 if 'from' in request.form:
510 if request.form['from'] == 'ajax':
511 return ''
512 return redirect(url_for("admin.eventDisplay", eventId=event.id))
514@main_bp.route('/rsvpRemove', methods = ['POST'])
515def RemoveRSVP():
516 """
517 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
518 """
519 eventData = request.form
520 event = Event.get_by_id(eventData['id'])
522 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
523 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
524 currentRsvpParticipant.delete_instance()
525 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
526 flash("Successfully unregistered for event!", "success")
527 if 'from' in eventData:
528 if eventData['from'] == 'ajax':
529 return ''
530 return redirect(url_for("admin.eventDisplay", eventId=event.id))
532@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
533def serviceTranscript(username):
534 user = User.get_or_none(User.username == username)
535 if user is None:
536 abort(404)
537 if user != g.current_user and not g.current_user.isAdmin:
538 abort(403)
540 slCourses = getSlCourseTranscript(username)
541 totalHours = getTotalHours(username)
542 allEventTranscript = getProgramTranscript(username)
543 startDate = getStartYear(username)
544 return render_template('main/serviceTranscript.html',
545 allEventTranscript = allEventTranscript,
546 slCourses = slCourses.objects(),
547 totalHours = totalHours,
548 startDate = startDate,
549 userData = user)
551@main_bp.route('/profile/<username>/updateTranscript/<program_id>', methods=['POST'])
552def updateTranscript(username, program_id):
553 # Check user permissions
554 user = User.get_or_none(User.username == username)
555 if user is None:
556 abort(404)
557 if user != g.current_user and not g.current_user.isAdmin:
558 abort(403)
560 # Get the data sent from the client-side JavaScript
561 data = request.json
563 # Retrieve removeFromTranscript value from the request data
564 removeFromTranscript = data.get('removeFromTranscript')
566 # Update the ProgramBan object matching the program_id and username
567 try:
568 bannedProgramForUser = ProgramBan.get((ProgramBan.program == program_id) & (ProgramBan.user == user) & (ProgramBan.unbanNote.is_null()))
569 bannedProgramForUser.removeFromTranscript = removeFromTranscript
570 bannedProgramForUser.save()
571 return jsonify({'status': 'success'})
572 except ProgramBan.DoesNotExist:
573 return jsonify({'status': 'error', 'message': 'ProgramBan not found'})
576@main_bp.route('/searchUser/<query>', methods = ['GET'])
577def searchUser(query):
579 category= request.args.get("category")
581 '''Accepts user input and queries the database returning results that matches user search'''
582 try:
583 query = query.strip()
584 search = query.upper()
585 splitSearch = search.split()
586 searchResults = searchUsers(query,category)
587 return searchResults
588 except Exception as e:
589 print(e)
590 return "Error in searching for user", 500
592@main_bp.route('/contributors',methods = ['GET'])
593def contributors():
594 return render_template("/contributors.html")
596@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
597def getDietInfo():
598 dietaryInfo = request.form
599 user = dietaryInfo["user"]
600 dietInfo = dietaryInfo["dietInfo"]
602 if (g.current_user.username == user) or g.current_user.isAdmin:
603 updateDietInfo(user, dietInfo)
604 userInfo = User.get(User.username == user)
605 if len(dietInfo) > 0:
606 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
607 else:
608 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
611 return " "
613@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
614def indicateMinorInterest(username):
615 if g.current_user.isCeltsAdmin or g.current_user.username == username:
616 data = request.get_json()
617 isAdding = data.get("isAdding", False)
619 toggleMinorInterest(username, isAdding)
621 else:
622 abort(403)
624 return ""
626@main_bp.route('/profile/<username>/updateMinorDeclaration', methods=["POST"])
627def updateMinorDeclaration(username):
628 if g.current_user.isCeltsAdmin or g.current_user.username == username:
629 declareMinorInterest(username)
630 flash("Candidate minor successfully updated", "success")
631 else:
632 flash("Error updating candidate minor status", "danger")
633 abort(403)
635 tab = request.args.get("tab", "interested")
636 return redirect(url_for('admin.manageMinor', tab=tab))