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