Coverage for app/controllers/main/routes.py: 23%
389 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 16:32 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 16: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, 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 rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute()
309 if not rowsUpdated:
310 InsuranceInfo.create(user = username, **request.form)
312 createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.")
313 flash('Insurance information saved successfully!', 'success')
315 if request.args.get('action') == 'exit':
316 return redirect (f"/profile/{username}")
317 else:
318 return redirect (f"/profile/{username}/emergencyContact")
320@main_bp.route('/profile/<username>/travelForm', methods=['GET', 'POST'])
321def travelForm(username):
322 if not (g.current_user.username == username or g.current_user.isCeltsAdmin):
323 abort(403)
325 user = (User.select(User, EmergencyContact, InsuranceInfo)
326 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
327 .join(InsuranceInfo, JOIN.LEFT_OUTER)
328 .where(User.username == username).limit(1))
329 if not list(user):
330 abort(404)
331 userList = list(user.dicts())[0]
332 userList = [{key: value if value else '' for (key, value) in userList.items()}]
334 return render_template ('/main/travelForm.html',
335 userList = userList
336 )
338@main_bp.route('/event/<eventID>/travelForm', methods=['GET', 'POST'])
339def eventTravelForm(eventID):
340 try:
341 event = Event.get_by_id(eventID)
342 except DoesNotExist as e:
343 print(f"No event found for {eventID}", e)
344 abort(404)
346 if not (g.current_user.isCeltsAdmin):
347 abort(403)
349 if request.method == "POST" and request.form.getlist("username") != []:
350 usernameList = request.form.getlist("username")
351 usernameList = usernameList.copy()
352 userList = []
353 for username in usernameList:
354 user = (User.select(User, EmergencyContact, InsuranceInfo)
355 .join(EmergencyContact, JOIN.LEFT_OUTER).switch()
356 .join(InsuranceInfo, JOIN.LEFT_OUTER)
357 .where(User.username == username).limit(1))
358 if not list(username):
359 abort(404)
360 userData = list(user.dicts())[0]
361 userData = {key: value if value else '' for (key, value) in userData.items()}
362 userList.append(userData)
365 else:
366 return redirect(f"/event/{eventID}/volunteer_details")
369 return render_template ('/main/travelForm.html',
370 usernameList = usernameList,
371 userList = userList,
372 )
374@main_bp.route('/profile/addNote', methods=['POST'])
375def addNote():
376 """
377 This function adds a note to the user's profile.
378 """
379 postData = request.form
380 try:
381 note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"])
382 flash("Successfully added profile note", "success")
383 return redirect(url_for("main.viewUsersProfile", username=postData["username"]))
384 except Exception as e:
385 print("Error adding note", e)
386 flash("Failed to add profile note", "danger")
387 return "Failed to add profile note", 500
389@main_bp.route('/<username>/deleteNote', methods=['POST'])
390def deleteNote(username):
391 """
392 This function deletes a note from the user's profile.
393 """
394 try:
395 deleteProfileNote(request.form["id"])
396 flash("Successfully deleted profile note", "success")
397 except Exception as e:
398 print("Error deleting note", e)
399 flash("Failed to delete profile note", "danger")
400 return "success"
402# ===========================Ban===============================================
403@main_bp.route('/<username>/ban/<program_id>', methods=['POST'])
404def ban(program_id, username):
405 """
406 This function updates the ban status of a username either when they are banned from a program.
407 program_id: the primary id of the program the student is being banned from
408 username: unique value of a user to correctly identify them
409 """
410 postData = request.form
411 banNote = postData["note"] # This contains the note left about the change
412 banEndDate = postData["endDate"] # Contains the date the ban will no longer be effective
414 try:
415 banUser(program_id, username, banNote, banEndDate, g.current_user)
416 programInfo = Program.get(int(program_id))
417 flash("Successfully banned the volunteer", "success")
418 createActivityLog(f'Banned {username} from {programInfo.programName} until {banEndDate}.')
419 return "Successfully banned the volunteer."
420 except Exception as e:
421 print("Error while updating ban", e)
422 flash("Failed to ban the volunteer", "danger")
423 return "Failed to ban the volunteer", 500
425# ===========================Unban===============================================
426@main_bp.route('/<username>/unban/<program_id>', methods=['POST'])
427def unban(program_id, username):
428 """
429 This function updates the ban status of a username either when they are unbanned from a program.
430 program_id: the primary id of the program the student is being unbanned from
431 username: unique value of a user to correctly identify them
432 """
433 postData = request.form
434 unbanNote = postData["note"] # This contains the note left about the change
435 try:
436 unbanUser(program_id, username, unbanNote, g.current_user)
437 programInfo = Program.get(int(program_id))
438 createActivityLog(f'Unbanned {username} from {programInfo.programName}.')
439 flash("Successfully unbanned the volunteer", "success")
440 return "Successfully unbanned the volunteer"
442 except Exception as e:
443 print("Error while updating Unban", e)
444 flash("Failed to unban the volunteer", "danger")
445 return "Failed to unban the volunteer", 500
448@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
449def addInterest(program_id, username):
450 """
451 This function adds a program to the list of programs a user interested in
452 program_id: the primary id of the program the student is adding interest of
453 username: unique value of a user to correctly identify them
454 """
455 try:
456 success = addUserInterest(program_id, username)
457 if success:
458 flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
459 return ""
460 else:
461 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
463 except Exception as e:
464 print(e)
465 return "Error Updating Interest", 500
467@main_bp.route('/<username>/removeInterest/<program_id>', methods=['POST'])
468def removeInterest(program_id, username):
469 """
470 This function removes a program to the list of programs a user interested in
471 program_id: the primary id of the program the student is adding interest of
472 username: unique value of a user to correctly identify them
473 """
474 try:
475 removed = removeUserInterest(program_id, username)
476 if removed:
477 flash("Successfully removed " + Program.get_by_id(program_id).programName + " as an interest.", "success")
478 return ""
479 else:
480 flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
481 except Exception as e:
482 print(e)
483 return "Error Updating Interest", 500
485@main_bp.route('/rsvpForEvent', methods = ['POST'])
486def volunteerRegister():
487 """
488 This function selects the user ID and event ID and registers the user
489 for the event they have clicked register for.
490 """
491 event = Event.get_by_id(request.form['id'])
492 program = event.program
493 user = g.current_user
495 isAdded = checkUserRsvp(user, event)
496 isEligible = isEligibleForProgram(program, user)
497 listOfRequirements = unattendedRequiredEvents(program, user)
499 personAdded = False
500 if isEligible:
501 personAdded = addPersonToEvent(user, event)
502 if personAdded and listOfRequirements:
503 reqListToString = ', '.join(listOfRequirements)
504 flash(f"{user.firstName} {user.lastName} successfully registered. However, the following training may be required: {reqListToString}.", "success")
505 elif personAdded:
506 flash("Successfully registered for event!","success")
507 else:
508 flash(f"RSVP Failed due to an unknown error.", "danger")
509 else:
510 flash(f"Cannot RSVP. Contact CELTS administrators: {app.config['celts_admin_contact']}.", "danger")
513 if 'from' in request.form:
514 if request.form['from'] == 'ajax':
515 return ''
516 return redirect(url_for("admin.eventDisplay", eventId=event.id))
518@main_bp.route('/rsvpRemove', methods = ['POST'])
519def RemoveRSVP():
520 """
521 This function deletes the user ID and event ID from database when RemoveRSVP is clicked
522 """
523 eventData = request.form
524 event = Event.get_by_id(eventData['id'])
526 currentRsvpParticipant = EventRsvp.get(EventRsvp.user == g.current_user, EventRsvp.event == event)
527 logBody = "withdrew from the waitlist" if currentRsvpParticipant.rsvpWaitlist else "un-RSVP'd"
528 currentRsvpParticipant.delete_instance()
529 createRsvpLog(event.id, f"{g.current_user.fullName} {logBody}.")
530 flash("Successfully unregistered for event!", "success")
531 if 'from' in eventData:
532 if eventData['from'] == 'ajax':
533 return ''
534 return redirect(url_for("admin.eventDisplay", eventId=event.id))
536@main_bp.route('/profile/<username>/serviceTranscript', methods = ['GET'])
537def serviceTranscript(username):
538 user = User.get_or_none(User.username == username)
539 if user is None:
540 abort(404)
541 if user != g.current_user and not g.current_user.isAdmin:
542 abort(403)
544 slCourses = getSlCourseTranscript(username)
545 totalHours = getTotalHours(username)
546 allEventTranscript = getProgramTranscript(username)
547 startDate = getStartYear(username)
548 return render_template('main/serviceTranscript.html',
549 allEventTranscript = allEventTranscript,
550 slCourses = slCourses.objects(),
551 totalHours = totalHours,
552 startDate = startDate,
553 userData = user)
555@main_bp.route('/profile/<username>/updateTranscript/<program_id>', methods=['POST'])
556def updateTranscript(username, program_id):
557 # Check user permissions
558 user = User.get_or_none(User.username == username)
559 if user is None:
560 abort(404)
561 if user != g.current_user and not g.current_user.isAdmin:
562 abort(403)
564 # Get the data sent from the client-side JavaScript
565 data = request.json
567 # Retrieve removeFromTranscript value from the request data
568 removeFromTranscript = data.get('removeFromTranscript')
570 # Update the ProgramBan object matching the program_id and username
571 try:
572 bannedProgramForUser = ProgramBan.get((ProgramBan.program == program_id) & (ProgramBan.user == user) & (ProgramBan.unbanNote.is_null()))
573 bannedProgramForUser.removeFromTranscript = removeFromTranscript
574 bannedProgramForUser.save()
575 return jsonify({'status': 'success'})
576 except ProgramBan.DoesNotExist:
577 return jsonify({'status': 'error', 'message': 'ProgramBan not found'})
580@main_bp.route('/searchUser/<query>', methods = ['GET'])
581def searchUser(query):
583 category= request.args.get("category")
585 '''Accepts user input and queries the database returning results that matches user search'''
586 try:
587 query = query.strip()
588 search = query.upper()
589 splitSearch = search.split()
590 searchResults = searchUsers(query,category)
591 return searchResults
592 except Exception as e:
593 print(e)
594 return "Error in searching for user", 500
596@main_bp.route('/contributors',methods = ['GET'])
597def contributors():
598 return render_template("/contributors.html")
600@main_bp.route('/updateDietInformation', methods = ['GET', 'POST'])
601def getDietInfo():
602 dietaryInfo = request.form
603 user = dietaryInfo["user"]
604 dietInfo = dietaryInfo["dietInfo"]
606 if (g.current_user.username == user) or g.current_user.isAdmin:
607 updateDietInfo(user, dietInfo)
608 userInfo = User.get(User.username == user)
609 if len(dietInfo) > 0:
610 createActivityLog(f"Updated {userInfo.fullName}'s dietary restrictions to {dietInfo}.") if dietInfo.strip() else None
611 else:
612 createActivityLog(f"Deleted all {userInfo.fullName}'s dietary restrictions dietary restrictions.")
615 return " "
617@main_bp.route('/profile/<username>/indicateInterest', methods=['POST'])
618def indicateMinorInterest(username):
619 if g.current_user.isCeltsAdmin or g.current_user.username == username:
620 data = request.get_json()
621 isAdding = data.get("isAdding", False)
623 toggleMinorInterest(username, isAdding)
625 else:
626 abort(403)
628 return ""
630@main_bp.route('/profile/<username>/updateMinorDeclaration', methods=["POST"])
631def updateMinorDeclaration(username):
632 if g.current_user.isCeltsAdmin or g.current_user.username == username:
633 declareMinorInterest(username)
634 flash("Candidate minor successfully updated", "success")
635 else:
636 flash("Error updating candidate minor status", "danger")
637 abort(403)
639 tab = request.args.get("tab", "interested")
640 return redirect(url_for('admin.manageMinor', tab=tab))