Coverage for app/controllers/admin/routes.py: 24%
396 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-22 20:57 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-22 20:57 +0000
1from flask import request, render_template, url_for, g, redirect
2from flask import flash, abort, jsonify, session, send_file
3from peewee import DoesNotExist, fn, IntegrityError
4from playhouse.shortcuts import model_to_dict
5import json
6from datetime import datetime
7import os
9from app import app
10from app.models.program import Program
11from app.models.event import Event
12from app.models.eventRsvp import EventRsvp
13from app.models.eventParticipant import EventParticipant
14from app.models.user import User
15from app.models.course import Course
16from app.models.courseInstructor import CourseInstructor
17from app.models.courseParticipant import CourseParticipant
18from app.models.eventTemplate import EventTemplate
19from app.models.activityLog import ActivityLog
20from app.models.eventRsvpLog import EventRsvpLog
21from app.models.attachmentUpload import AttachmentUpload
22from app.models.bonnerCohort import BonnerCohort
23from app.models.certification import Certification
24from app.models.user import User
25from app.models.term import Term
26from app.models.eventViews import EventView
27from app.models.courseStatus import CourseStatus
29from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates
30from app.logic.createLogs import createActivityLog
31from app.logic.certification import getCertRequirements, updateCertRequirements
32from app.logic.utils import selectSurroundingTerms, getFilesFromRequest, getRedirectTarget, setRedirectTarget
33from app.logic.events import cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent
34from app.logic.participants import getParticipationStatusForTrainings, checkUserRsvp
35from app.logic.minor import getMinorInterest
36from app.logic.fileHandler import FileHandler
37from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort, addBonnerCohortToRsvpLog
38from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getImportedCourses, getInstructorCourses, editImportedCourses
40from app.controllers.admin import admin_bp
41from app.logic.spreadsheet import createSpreadsheet
44@admin_bp.route('/admin/reports')
45def reports():
46 academicYears = Term.select(Term.academicYear).distinct().order_by(Term.academicYear.desc())
47 academicYears = list(map(lambda t: t.academicYear, academicYears))
48 return render_template("/admin/reports.html", academicYears=academicYears)
50@admin_bp.route('/admin/reports/download', methods=['POST'])
51def downloadFile():
52 academicYear = request.form.get('academicYear')
53 filepath = os.path.abspath(createSpreadsheet(academicYear))
54 return send_file(filepath, as_attachment=True)
57@admin_bp.route('/switch_user', methods=['POST'])
58def switchUser():
59 if app.env == "production":
60 print(f"An attempt was made to switch to another user by {g.current_user.username}!")
61 abort(403)
63 print(f"Switching user from {g.current_user} to",request.form['newuser'])
64 session['current_user'] = model_to_dict(User.get_by_id(request.form['newuser']))
66 return redirect(request.referrer)
69@admin_bp.route('/eventTemplates')
70def templateSelect():
71 if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff:
72 allprograms = getAllowedPrograms(g.current_user)
73 visibleTemplates = getAllowedTemplates(g.current_user)
74 return render_template("/events/template_selector.html",
75 programs=allprograms,
76 celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored),
77 templates=visibleTemplates)
78 else:
79 abort(403)
82@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST'])
83def createEvent(templateid, programid):
84 if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)):
85 abort(403)
87 # Validate given URL
88 program = None
89 try:
90 template = EventTemplate.get_by_id(templateid)
91 if programid:
92 program = Program.get_by_id(programid)
93 except DoesNotExist as e:
94 print("Invalid template or program id:", e)
95 flash("There was an error with your selection. Please try again or contact Systems Support.", "danger")
96 return redirect(url_for("admin.program_picker"))
98 # Get the data from the form or from the template
99 eventData = template.templateData
101 eventData['program'] = program
103 if request.method == "GET":
104 eventData['contactName'] = "CELTS Admin"
105 eventData['contactEmail'] = app.config['celts_admin_contact']
106 if program:
107 eventData['location'] = program.defaultLocation
108 if program.contactName:
109 eventData['contactName'] = program.contactName
110 if program.contactEmail:
111 eventData['contactEmail'] = program.contactEmail
113 # Try to save the form
114 if request.method == "POST":
115 eventData.update(request.form.copy())
116 try:
117 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
119 except Exception as e:
120 print("Error saving event:", e)
121 savedEvents = False
122 validationErrorMessage = "Unknown Error Saving Event. Please try again"
124 if savedEvents:
125 rsvpcohorts = request.form.getlist("cohorts[]")
126 for year in rsvpcohorts:
127 rsvpForBonnerCohort(int(year), savedEvents[0].id)
128 addBonnerCohortToRsvpLog(int(year), savedEvents[0].id)
131 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
132 flash(f"{noun} successfully created!", 'success')
134 if program:
135 if len(savedEvents) > 1:
136 createActivityLog(f"Created a recurring event, <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a>, for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}. The last event in the series will be on {datetime.strftime(savedEvents[-1].startDate, '%m/%d/%Y')}.")
137 else:
138 createActivityLog(f"Created <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a> for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.")
139 else:
140 createActivityLog(f"Created a non-program event, <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a>, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.")
142 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
143 else:
144 flash(validationErrorMessage, 'warning')
146 # make sure our data is the same regardless of GET or POST
147 preprocessEventData(eventData)
148 isProgramManager = g.current_user.isProgramManagerFor(programid)
150 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
152 requirements, bonnerCohorts = [], []
153 if eventData['program'] is not None and eventData['program'].isBonnerScholars:
154 requirements = getCertRequirements(Certification.BONNER)
155 bonnerCohorts = getBonnerCohorts(limit=5)
156 return render_template(f"/admin/{template.templateFile}",
157 template = template,
158 eventData = eventData,
159 futureTerms = futureTerms,
160 requirements = requirements,
161 bonnerCohorts = bonnerCohorts,
162 isProgramManager = isProgramManager)
165@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
166def rsvpLogDisplay(eventId):
167 event = Event.get_by_id(eventId)
168 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
169 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
170 return render_template("/events/rsvpLog.html",
171 event = event,
172 allLogs = allLogs)
173 else:
174 abort(403)
176@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
177def renewEvent(eventId):
178 try:
179 formData = request.form
180 try:
181 assert formData['timeStart'] < formData['timeEnd']
182 except AssertionError:
183 flash("End time must be after start time", 'warning')
184 return redirect(url_for('admin.eventDisplay', eventId = eventId))
186 try:
187 if formData.get('dateEnd'):
188 assert formData['dateStart'] < formData['dateEnd']
189 except AssertionError:
190 flash("End date must be after start date", 'warning')
191 return redirect(url_for('admin.eventDisplay', eventId = eventId))
194 priorEvent = model_to_dict(Event.get_by_id(eventId))
195 newEventDict = priorEvent.copy()
196 newEventDict.pop('id')
197 newEventDict.update({
198 'program': int(priorEvent['program']['id']),
199 'term': int(priorEvent['term']['id']),
200 'timeStart': formData['timeStart'],
201 'timeEnd': formData['timeEnd'],
202 'location': formData['location'],
203 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
204 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
205 'isRecurring': bool(priorEvent['recurringId'])
206 })
207 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
208 if message:
209 flash(message, "danger")
210 return redirect(url_for('admin.eventDisplay', eventId = eventId))
212 copyRsvpToNewEvent(priorEvent, newEvent[0])
213 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
214 flash("Event successfully renewed.", "success")
215 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
218 except Exception as e:
219 print("Error while trying to renew event:", e)
220 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
221 return redirect(url_for('admin.eventDisplay', eventId = eventId))
225@admin_bp.route('/event/<eventId>/view', methods=['GET'])
226@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
227def eventDisplay(eventId):
228 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
229 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
230 viewer = g.current_user
231 event = Event.get_by_id(eventId)
232 addEventView(viewer,event)
233 # Validate given URL
234 try:
235 event = Event.get_by_id(eventId)
236 except DoesNotExist as e:
237 print(f"Unknown event: {eventId}")
238 abort(404)
240 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
241 if 'edit' in request.url_rule.rule and notPermitted:
242 abort(403)
244 eventData = model_to_dict(event, recurse=False)
245 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
246 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
248 image = None
249 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
250 for attachment in associatedAttachments:
251 for extension in picurestype:
252 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
253 image = filepaths[attachment.fileName][0]
254 if image:
255 break
258 if request.method == "POST": # Attempt to save form
259 eventData = request.form.copy()
260 try:
261 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
263 except Exception as e:
264 print("Error saving event:", e)
265 savedEvents = False
266 validationErrorMessage = "Unknown Error Saving Event. Please try again"
269 if savedEvents:
270 rsvpcohorts = request.form.getlist("cohorts[]")
271 for year in rsvpcohorts:
272 rsvpForBonnerCohort(int(year), event.id)
273 addBonnerCohortToRsvpLog(int(year), event.id)
275 flash("Event successfully updated!", "success")
276 return redirect(url_for("admin.eventDisplay", eventId = event.id))
277 else:
278 flash(validationErrorMessage, 'warning')
280 # make sure our data is the same regardless of GET and POST
281 preprocessEventData(eventData)
282 eventData['program'] = event.program
283 futureTerms = selectSurroundingTerms(g.current_term)
284 userHasRSVPed = checkUserRsvp(g.current_user, event)
285 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
286 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
287 requirements, bonnerCohorts = [], []
289 if eventData['program'] and eventData['program'].isBonnerScholars:
290 requirements = getCertRequirements(Certification.BONNER)
291 bonnerCohorts = getBonnerCohorts(limit=5)
293 rule = request.url_rule
295 # Event Edit
296 if 'edit' in rule.rule:
297 return render_template("admin/createEvent.html",
298 eventData = eventData,
299 futureTerms=futureTerms,
300 event = event,
301 requirements = requirements,
302 bonnerCohorts = bonnerCohorts,
303 userHasRSVPed = userHasRSVPed,
304 isProgramManager = isProgramManager,
305 filepaths = filepaths)
306 # Event View
307 else:
308 # get text representations of dates for html
309 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
310 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
311 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
312 eventCountdown = getCountdownToEvent(event)
315 # Identify the next event in a recurring series
316 if event.recurringId:
317 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId)
318 .where((Event.isCanceled == False) | (Event.id == event.id))
319 .order_by(Event.startDate))
320 eventIndex = eventSeriesList.index(event)
321 if len(eventSeriesList) != (eventIndex + 1):
322 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1]
324 currentEventRsvpAmount = getEventRsvpCount(event.id)
326 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
328 return render_template("eventView.html",
329 eventData=eventData,
330 event=event,
331 userHasRSVPed=userHasRSVPed,
332 programTrainings=userParticipatedTrainingEvents,
333 currentEventRsvpAmount=currentEventRsvpAmount,
334 isProgramManager=isProgramManager,
335 filepaths=filepaths,
336 image=image,
337 pageViewsCount=pageViewsCount,
338 eventCountdown=eventCountdown
339 )
343@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
344def cancelRoute(eventId):
345 if g.current_user.isAdmin:
346 try:
347 cancelEvent(eventId)
348 return redirect(request.referrer)
350 except Exception as e:
351 print('Error while canceling event:', e)
352 return "", 500
354 else:
355 abort(403)
357@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
358def deleteRoute(eventId):
359 try:
360 deleteEvent(eventId)
361 flash("Event successfully deleted.", "success")
362 return redirect(url_for("main.events", selectedTerm=g.current_term))
364 except Exception as e:
365 print('Error while canceling event:', e)
366 return "", 500
368@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
369def deleteEventAndAllFollowingRoute(eventId):
370 try:
371 deleteEventAndAllFollowing(eventId)
372 flash("Events successfully deleted.", "success")
373 return redirect(url_for("main.events", selectedTerm=g.current_term))
375 except Exception as e:
376 print('Error while canceling event:', e)
377 return "", 500
379@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
380def deleteAllRecurringEventsRoute(eventId):
381 try:
382 deleteAllRecurringEvents(eventId)
383 flash("Events successfully deleted.", "success")
384 return redirect(url_for("main.events", selectedTerm=g.current_term))
386 except Exception as e:
387 print('Error while canceling event:', e)
388 return "", 500
390@admin_bp.route('/makeRecurringEvents', methods=['POST'])
391def addRecurringEvents():
392 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
393 return json.dumps(recurringEvents, default=str)
396@admin_bp.route('/userProfile', methods=['POST'])
397def userProfile():
398 volunteerName= request.form.copy()
399 if volunteerName['searchStudentsInput']:
400 username = volunteerName['searchStudentsInput'].strip("()")
401 user=username.split('(')[-1]
402 return redirect(url_for('main.viewUsersProfile', username=user))
403 else:
404 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
405 return redirect(url_for('admin.studentSearchPage'))
407@admin_bp.route('/search_student', methods=['GET'])
408def studentSearchPage():
409 if g.current_user.isAdmin:
410 return render_template("/admin/searchStudentPage.html")
411 abort(403)
413@admin_bp.route('/addParticipants', methods = ['GET'])
414def addParticipants():
415 '''Renders the page, will be removed once merged with full page'''
417 return render_template('addParticipants.html',
418 title="Add Participants")
420@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
421def activityLogs():
422 if g.current_user.isCeltsAdmin:
423 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
424 return render_template("/admin/activityLogs.html",
425 allLogs = allLogs)
426 else:
427 abort(403)
429@admin_bp.route("/deleteEventFile", methods=["POST"])
430def deleteEventFile():
431 fileData= request.form
432 eventfile=FileHandler(eventId=fileData["databaseId"])
433 eventfile.deleteFile(fileData["fileId"])
434 return ""
436@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
437def addCourseFile():
438 fileData = request.files['addCourseParticipants']
439 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
440 fileData.save(filePath)
441 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
442 os.remove(filePath)
443 return redirect(url_for("admin.manageServiceLearningCourses"))
445@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
446@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
447def manageServiceLearningCourses(term=None):
449 """
450 The SLC management page for admins
451 """
452 if not g.current_user.isCeltsAdmin:
453 abort(403)
455 if request.method == 'POST' and "submitParticipant" in request.form:
456 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
457 flash('Courses and participants saved successfully!', 'success')
458 return redirect(url_for('admin.manageServiceLearningCourses'))
460 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
462 setRedirectTarget(request.full_path)
463 # retrieve and store the courseID of the imported course from a session variable if it exists.
464 # This allows us to export the courseID in the html and use it.
465 courseID = session.get("alterCourseId")
467 if courseID:
468 # delete courseID from the session if it was retrieved, for storage purposes.
469 session.pop("alterCourseId")
470 return render_template('/admin/manageServiceLearningFaculty.html',
471 courseInstructors = getInstructorCourses(),
472 unapprovedCourses = unapprovedCourses(manageTerm),
473 approvedCourses = approvedCourses(manageTerm),
474 importedCourses = getImportedCourses(manageTerm),
475 terms = selectSurroundingTerms(g.current_term),
476 term = manageTerm,
477 cpPreview = session.get('cpPreview', {}),
478 cpPreviewErrors = session.get('cpErrors', []),
479 courseID = courseID
480 )
482 return render_template('/admin/manageServiceLearningFaculty.html',
483 courseInstructors = getInstructorCourses(),
484 unapprovedCourses = unapprovedCourses(manageTerm),
485 approvedCourses = approvedCourses(manageTerm),
486 importedCourses = getImportedCourses(manageTerm),
487 terms = selectSurroundingTerms(g.current_term),
488 term = manageTerm,
489 cpPreview= session.get('cpPreview',{}),
490 cpPreviewErrors = session.get('cpErrors',[])
491 )
493@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
494def getSidebarInformation() -> str:
495 """
496 Get the count of unapproved courses and students interested in the minor for the current term
497 to display in the admin sidebar. It must be returned as a string to be received by the
498 ajax request.
499 """
500 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
501 interestedStudentsCount: int = len(getMinorInterest())
502 return {"unapprovedCoursesCount": unapprovedCoursesCount,
503 "interestedStudentsCount": interestedStudentsCount}
505@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
506def removeFromSession():
507 try:
508 session.pop('cpPreview')
509 except KeyError:
510 pass
512 return ""
514@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
515def alterImportedCourse(courseID):
516 """
517 This route handles a GET and a POST request for the purpose of imported courses.
518 The GET request provides preexisting information of an imported course in a modal.
519 The POST request updates a specific imported course (course name, course abbreviation,
520 hours earned on completion, list of instructors) in the database with new information
521 coming from the imported courses modal.
522 """
523 if request.method == 'GET':
524 try:
525 targetCourse = Course.get_by_id(courseID)
526 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
528 try:
529 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
530 except IndexError: # If a course has no participant, IndexError will be raised
531 serviceHours = 20
533 courseData = model_to_dict(targetCourse, recurse=False)
534 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
535 courseData['hoursEarned'] = serviceHours
537 return jsonify(courseData)
539 except DoesNotExist:
540 flash("Course not found")
541 return jsonify({"error": "Course not found"}), 404
543 if request.method == 'POST':
544 # Update course information in the database
545 courseData = request.form.copy()
546 editImportedCourses(courseData)
547 session['alterCourseId'] = courseID
549 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
552@admin_bp.route("/manageBonner")
553def manageBonner():
554 if not g.current_user.isCeltsAdmin:
555 abort(403)
557 return render_template("/admin/bonnerManagement.html",
558 cohorts=getBonnerCohorts(),
559 events=getBonnerEvents(g.current_term),
560 requirements=getCertRequirements(certification=Certification.BONNER))
562@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
563def updatecohort(year, method, username):
564 if not g.current_user.isCeltsAdmin:
565 abort(403)
567 try:
568 user = User.get_by_id(username)
569 except:
570 abort(500)
572 if method == "add":
573 try:
574 BonnerCohort.create(year=year, user=user)
575 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
576 except IntegrityError as e:
577 # if they already exist, ignore the error
578 flash(f'Error: {user.fullName} already added.', "danger")
579 pass
581 elif method == "remove":
582 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
583 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
584 else:
585 flash(f"Error: {user.fullName} can't be added.", "danger")
586 abort(500)
588 return ""
590@admin_bp.route("/bonnerxls")
591def bonnerxls():
592 if not g.current_user.isCeltsAdmin:
593 abort(403)
595 newfile = makeBonnerXls()
596 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
598@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
599def saveRequirements(certid):
600 if not g.current_user.isCeltsAdmin:
601 abort(403)
603 newRequirements = updateCertRequirements(certid, request.get_json())
605 return jsonify([requirement.id for requirement in newRequirements])
608@admin_bp.route("/displayEventFile", methods=["POST"])
609def displayEventFile():
610 fileData = request.form
611 eventfile = FileHandler(eventId=fileData["id"])
612 isChecked = fileData.get('checked') == 'true'
613 eventfile.changeDisplay(fileData['id'], isChecked)
614 return ""