Coverage for app/controllers/admin/routes.py: 24%
396 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-24 12:19 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-24 12:19 +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)
58@admin_bp.route('/switch_user', methods=['POST'])
59def switchUser():
60 if app.env == "production":
61 print(f"An attempt was made to switch to another user by {g.current_user.username}!")
62 abort(403)
64 print(f"Switching user from {g.current_user} to",request.form['newuser'])
65 session['current_user'] = model_to_dict(User.get_by_id(request.form['newuser']))
67 return redirect(request.referrer)
70@admin_bp.route('/eventTemplates')
71def templateSelect():
72 if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff:
73 allprograms = getAllowedPrograms(g.current_user)
74 visibleTemplates = getAllowedTemplates(g.current_user)
75 return render_template("/events/template_selector.html",
76 programs=allprograms,
77 celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored),
78 templates=visibleTemplates)
79 else:
80 abort(403)
83@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST'])
84def createEvent(templateid, programid):
85 if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)):
86 abort(403)
88 # Validate given URL
89 program = None
90 try:
91 template = EventTemplate.get_by_id(templateid)
92 if programid:
93 program = Program.get_by_id(programid)
94 except DoesNotExist as e:
95 print("Invalid template or program id:", e)
96 flash("There was an error with your selection. Please try again or contact Systems Support.", "danger")
97 return redirect(url_for("admin.program_picker"))
99 # Get the data from the form or from the template
100 eventData = template.templateData
102 eventData['program'] = program
104 if request.method == "GET":
105 eventData['contactName'] = "CELTS Admin"
106 eventData['contactEmail'] = app.config['celts_admin_contact']
107 if program:
108 eventData['location'] = program.defaultLocation
109 if program.contactName:
110 eventData['contactName'] = program.contactName
111 if program.contactEmail:
112 eventData['contactEmail'] = program.contactEmail
114 # Try to save the form
115 if request.method == "POST":
116 eventData.update(request.form.copy())
117 try:
118 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
120 except Exception as e:
121 print("Error saving event:", e)
122 savedEvents = False
123 validationErrorMessage = "Unknown Error Saving Event. Please try again"
125 if savedEvents:
126 rsvpcohorts = request.form.getlist("cohorts[]")
127 for year in rsvpcohorts:
128 rsvpForBonnerCohort(int(year), savedEvents[0].id)
129 addBonnerCohortToRsvpLog(int(year), savedEvents[0].id)
132 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
133 flash(f"{noun} successfully created!", 'success')
135 if program:
136 if len(savedEvents) > 1:
137 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')}.")
138 else:
139 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')}.")
140 else:
141 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')}.")
143 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
144 else:
145 flash(validationErrorMessage, 'warning')
147 # make sure our data is the same regardless of GET or POST
148 preprocessEventData(eventData)
149 isProgramManager = g.current_user.isProgramManagerFor(programid)
151 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
153 requirements, bonnerCohorts = [], []
154 if eventData['program'] is not None and eventData['program'].isBonnerScholars:
155 requirements = getCertRequirements(Certification.BONNER)
156 bonnerCohorts = getBonnerCohorts(limit=5)
157 return render_template(f"/admin/{template.templateFile}",
158 template = template,
159 eventData = eventData,
160 futureTerms = futureTerms,
161 requirements = requirements,
162 bonnerCohorts = bonnerCohorts,
163 isProgramManager = isProgramManager)
166@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
167def rsvpLogDisplay(eventId):
168 event = Event.get_by_id(eventId)
169 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
170 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
171 return render_template("/events/rsvpLog.html",
172 event = event,
173 allLogs = allLogs)
174 else:
175 abort(403)
177@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
178def renewEvent(eventId):
179 try:
180 formData = request.form
181 try:
182 assert formData['timeStart'] < formData['timeEnd']
183 except AssertionError:
184 flash("End time must be after start time", 'warning')
185 return redirect(url_for('admin.eventDisplay', eventId = eventId))
187 try:
188 if formData.get('dateEnd'):
189 assert formData['dateStart'] < formData['dateEnd']
190 except AssertionError:
191 flash("End date must be after start date", 'warning')
192 return redirect(url_for('admin.eventDisplay', eventId = eventId))
195 priorEvent = model_to_dict(Event.get_by_id(eventId))
196 newEventDict = priorEvent.copy()
197 newEventDict.pop('id')
198 newEventDict.update({
199 'program': int(priorEvent['program']['id']),
200 'term': int(priorEvent['term']['id']),
201 'timeStart': formData['timeStart'],
202 'timeEnd': formData['timeEnd'],
203 'location': formData['location'],
204 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
205 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
206 'isRecurring': bool(priorEvent['recurringId'])
207 })
208 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
209 if message:
210 flash(message, "danger")
211 return redirect(url_for('admin.eventDisplay', eventId = eventId))
213 copyRsvpToNewEvent(priorEvent, newEvent[0])
214 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
215 flash("Event successfully renewed.", "success")
216 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
219 except Exception as e:
220 print("Error while trying to renew event:", e)
221 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
222 return redirect(url_for('admin.eventDisplay', eventId = eventId))
226@admin_bp.route('/event/<eventId>/view', methods=['GET'])
227@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
228def eventDisplay(eventId):
229 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
230 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
231 viewer = g.current_user
232 event = Event.get_by_id(eventId)
233 addEventView(viewer,event)
234 # Validate given URL
235 try:
236 event = Event.get_by_id(eventId)
237 except DoesNotExist as e:
238 print(f"Unknown event: {eventId}")
239 abort(404)
241 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
242 if 'edit' in request.url_rule.rule and notPermitted:
243 abort(403)
245 eventData = model_to_dict(event, recurse=False)
246 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
247 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
249 image = None
250 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
251 for attachment in associatedAttachments:
252 for extension in picurestype:
253 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
254 image = filepaths[attachment.fileName][0]
255 if image:
256 break
259 if request.method == "POST": # Attempt to save form
260 eventData = request.form.copy()
261 try:
262 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
264 except Exception as e:
265 print("Error saving event:", e)
266 savedEvents = False
267 validationErrorMessage = "Unknown Error Saving Event. Please try again"
270 if savedEvents:
271 rsvpcohorts = request.form.getlist("cohorts[]")
272 for year in rsvpcohorts:
273 rsvpForBonnerCohort(int(year), event.id)
274 addBonnerCohortToRsvpLog(int(year), event.id)
276 flash("Event successfully updated!", "success")
277 return redirect(url_for("admin.eventDisplay", eventId = event.id))
278 else:
279 flash(validationErrorMessage, 'warning')
281 # make sure our data is the same regardless of GET and POST
282 preprocessEventData(eventData)
283 eventData['program'] = event.program
284 futureTerms = selectSurroundingTerms(g.current_term)
285 userHasRSVPed = checkUserRsvp(g.current_user, event)
286 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
287 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
288 requirements, bonnerCohorts = [], []
290 if eventData['program'] and eventData['program'].isBonnerScholars:
291 requirements = getCertRequirements(Certification.BONNER)
292 bonnerCohorts = getBonnerCohorts(limit=5)
294 rule = request.url_rule
296 # Event Edit
297 if 'edit' in rule.rule:
298 return render_template("admin/createEvent.html",
299 eventData = eventData,
300 futureTerms=futureTerms,
301 event = event,
302 requirements = requirements,
303 bonnerCohorts = bonnerCohorts,
304 userHasRSVPed = userHasRSVPed,
305 isProgramManager = isProgramManager,
306 filepaths = filepaths)
307 # Event View
308 else:
309 # get text representations of dates for html
310 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
311 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
312 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
313 eventCountdown = getCountdownToEvent(event)
316 # Identify the next event in a recurring series
317 if event.recurringId:
318 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId)
319 .where((Event.isCanceled == False) | (Event.id == event.id))
320 .order_by(Event.startDate))
321 eventIndex = eventSeriesList.index(event)
322 if len(eventSeriesList) != (eventIndex + 1):
323 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1]
325 currentEventRsvpAmount = getEventRsvpCount(event.id)
327 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
329 return render_template("eventView.html",
330 eventData=eventData,
331 event=event,
332 userHasRSVPed=userHasRSVPed,
333 programTrainings=userParticipatedTrainingEvents,
334 currentEventRsvpAmount=currentEventRsvpAmount,
335 isProgramManager=isProgramManager,
336 filepaths=filepaths,
337 image=image,
338 pageViewsCount=pageViewsCount,
339 eventCountdown=eventCountdown
340 )
344@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
345def cancelRoute(eventId):
346 if g.current_user.isAdmin:
347 try:
348 cancelEvent(eventId)
349 return redirect(request.referrer)
351 except Exception as e:
352 print('Error while canceling event:', e)
353 return "", 500
355 else:
356 abort(403)
358@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
359def deleteRoute(eventId):
360 try:
361 deleteEvent(eventId)
362 flash("Event successfully deleted.", "success")
363 return redirect(url_for("main.events", selectedTerm=g.current_term))
365 except Exception as e:
366 print('Error while canceling event:', e)
367 return "", 500
369@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
370def deleteEventAndAllFollowingRoute(eventId):
371 try:
372 deleteEventAndAllFollowing(eventId)
373 flash("Events successfully deleted.", "success")
374 return redirect(url_for("main.events", selectedTerm=g.current_term))
376 except Exception as e:
377 print('Error while canceling event:', e)
378 return "", 500
380@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
381def deleteAllRecurringEventsRoute(eventId):
382 try:
383 deleteAllRecurringEvents(eventId)
384 flash("Events successfully deleted.", "success")
385 return redirect(url_for("main.events", selectedTerm=g.current_term))
387 except Exception as e:
388 print('Error while canceling event:', e)
389 return "", 500
391@admin_bp.route('/makeRecurringEvents', methods=['POST'])
392def addRecurringEvents():
393 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
394 return json.dumps(recurringEvents, default=str)
397@admin_bp.route('/userProfile', methods=['POST'])
398def userProfile():
399 volunteerName= request.form.copy()
400 if volunteerName['searchStudentsInput']:
401 username = volunteerName['searchStudentsInput'].strip("()")
402 user=username.split('(')[-1]
403 return redirect(url_for('main.viewUsersProfile', username=user))
404 else:
405 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
406 return redirect(url_for('admin.studentSearchPage'))
408@admin_bp.route('/search_student', methods=['GET'])
409def studentSearchPage():
410 if g.current_user.isAdmin:
411 return render_template("/admin/searchStudentPage.html")
412 abort(403)
414@admin_bp.route('/addParticipants', methods = ['GET'])
415def addParticipants():
416 '''Renders the page, will be removed once merged with full page'''
418 return render_template('addParticipants.html',
419 title="Add Participants")
421@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
422def activityLogs():
423 if g.current_user.isCeltsAdmin:
424 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
425 return render_template("/admin/activityLogs.html",
426 allLogs = allLogs)
427 else:
428 abort(403)
430@admin_bp.route("/deleteEventFile", methods=["POST"])
431def deleteEventFile():
432 fileData= request.form
433 eventfile=FileHandler(eventId=fileData["databaseId"])
434 eventfile.deleteFile(fileData["fileId"])
435 return ""
437@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
438def addCourseFile():
439 fileData = request.files['addCourseParticipants']
440 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
441 fileData.save(filePath)
442 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
443 os.remove(filePath)
444 return redirect(url_for("admin.manageServiceLearningCourses"))
446@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
447@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
448def manageServiceLearningCourses(term=None):
450 """
451 The SLC management page for admins
452 """
453 if not g.current_user.isCeltsAdmin:
454 abort(403)
456 if request.method == 'POST' and "submitParticipant" in request.form:
457 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
458 flash('Courses and participants saved successfully!', 'success')
459 return redirect(url_for('admin.manageServiceLearningCourses'))
461 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
463 setRedirectTarget(request.full_path)
464 # retrieve and store the courseID of the imported course from a session variable if it exists.
465 # This allows us to export the courseID in the html and use it.
466 courseID = session.get("alterCourseId")
468 if courseID:
469 # delete courseID from the session if it was retrieved, for storage purposes.
470 session.pop("alterCourseId")
471 return render_template('/admin/manageServiceLearningFaculty.html',
472 courseInstructors = getInstructorCourses(),
473 unapprovedCourses = unapprovedCourses(manageTerm),
474 approvedCourses = approvedCourses(manageTerm),
475 importedCourses = getImportedCourses(manageTerm),
476 terms = selectSurroundingTerms(g.current_term),
477 term = manageTerm,
478 cpPreview = session.get('cpPreview', {}),
479 cpPreviewErrors = session.get('cpErrors', []),
480 courseID = courseID
481 )
483 return render_template('/admin/manageServiceLearningFaculty.html',
484 courseInstructors = getInstructorCourses(),
485 unapprovedCourses = unapprovedCourses(manageTerm),
486 approvedCourses = approvedCourses(manageTerm),
487 importedCourses = getImportedCourses(manageTerm),
488 terms = selectSurroundingTerms(g.current_term),
489 term = manageTerm,
490 cpPreview= session.get('cpPreview',{}),
491 cpPreviewErrors = session.get('cpErrors',[])
492 )
494@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
495def getSidebarInformation() -> str:
496 """
497 Get the count of unapproved courses and students interested in the minor for the current term
498 to display in the admin sidebar. It must be returned as a string to be received by the
499 ajax request.
500 """
501 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
502 interestedStudentsCount: int = len(getMinorInterest())
503 return {"unapprovedCoursesCount": unapprovedCoursesCount,
504 "interestedStudentsCount": interestedStudentsCount}
506@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
507def removeFromSession():
508 try:
509 session.pop('cpPreview')
510 except KeyError:
511 pass
513 return ""
515@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
516def alterImportedCourse(courseID):
517 """
518 This route handles a GET and a POST request for the purpose of imported courses.
519 The GET request provides preexisting information of an imported course in a modal.
520 The POST request updates a specific imported course (course name, course abbreviation,
521 hours earned on completion, list of instructors) in the database with new information
522 coming from the imported courses modal.
523 """
524 if request.method == 'GET':
525 try:
526 targetCourse = Course.get_by_id(courseID)
527 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
529 try:
530 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
531 except IndexError: # If a course has no participant, IndexError will be raised
532 serviceHours = 20
534 courseData = model_to_dict(targetCourse, recurse=False)
535 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
536 courseData['hoursEarned'] = serviceHours
538 return jsonify(courseData)
540 except DoesNotExist:
541 flash("Course not found")
542 return jsonify({"error": "Course not found"}), 404
544 if request.method == 'POST':
545 # Update course information in the database
546 courseData = request.form.copy()
547 editImportedCourses(courseData)
548 session['alterCourseId'] = courseID
550 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
553@admin_bp.route("/manageBonner")
554def manageBonner():
555 if not g.current_user.isCeltsAdmin:
556 abort(403)
558 return render_template("/admin/bonnerManagement.html",
559 cohorts=getBonnerCohorts(),
560 events=getBonnerEvents(g.current_term),
561 requirements=getCertRequirements(certification=Certification.BONNER))
563@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
564def updatecohort(year, method, username):
565 if not g.current_user.isCeltsAdmin:
566 abort(403)
568 try:
569 user = User.get_by_id(username)
570 except:
571 abort(500)
573 if method == "add":
574 try:
575 BonnerCohort.create(year=year, user=user)
576 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
577 except IntegrityError as e:
578 # if they already exist, ignore the error
579 flash(f'Error: {user.fullName} already added.', "danger")
580 pass
582 elif method == "remove":
583 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
584 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
585 else:
586 flash(f"Error: {user.fullName} can't be added.", "danger")
587 abort(500)
589 return ""
591@admin_bp.route("/bonnerxls")
592def bonnerxls():
593 if not g.current_user.isCeltsAdmin:
594 abort(403)
596 newfile = makeBonnerXls()
597 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
599@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
600def saveRequirements(certid):
601 if not g.current_user.isCeltsAdmin:
602 abort(403)
604 newRequirements = updateCertRequirements(certid, request.get_json())
606 return jsonify([requirement.id for requirement in newRequirements])
609@admin_bp.route("/displayEventFile", methods=["POST"])
610def displayEventFile():
611 fileData = request.form
612 eventfile = FileHandler(eventId=fileData["id"])
613 isChecked = fileData.get('checked') == 'true'
614 eventfile.changeDisplay(fileData['id'], isChecked)
615 return ""