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