Coverage for app/controllers/admin/routes.py: 24%

393 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-06-27 14:39 +0000

1from flask import request, render_template, url_for, g, redirect 

2from flask import flash, abort, jsonify, session, send_file, render_template, request 

3from peewee import DoesNotExist, fn, IntegrityError 

4from playhouse.shortcuts import model_to_dict 

5import json 

6from datetime import datetime 

7import os 

8 

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 

28 

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 

38from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getImportedCourses, getInstructorCourses, editImportedCourses 

39from app.logic.spreadsheet import createSpreadsheet 

40 

41from app.controllers.admin import admin_bp 

42 

43 

44@admin_bp.route('/admin/reports') 

45def reports(): 

46 return render_template("/admin/reports.html") 

47 

48@admin_bp.route('/download') 

49def download_file(): 

50 academic_year = request.args.get('academic_year', '2023-2024') # Default academic year if not provided 

51 working_filepath = createSpreadsheet(academic_year) 

52 absolute_filepath = os.path.abspath(working_filepath) 

53 return send_file(absolute_filepath, as_attachment=True) 

54 

55@admin_bp.route('/switch_user', methods=['POST']) 

56def switchUser(): 

57 if app.env == "production": 

58 print(f"An attempt was made to switch to another user by {g.current_user.username}!") 

59 abort(403) 

60 

61 print(f"Switching user from {g.current_user} to",request.form['newuser']) 

62 session['current_user'] = model_to_dict(User.get_by_id(request.form['newuser'])) 

63 

64 return redirect(request.referrer) 

65 

66 

67@admin_bp.route('/eventTemplates') 

68def templateSelect(): 

69 if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff: 

70 allprograms = getAllowedPrograms(g.current_user) 

71 visibleTemplates = getAllowedTemplates(g.current_user) 

72 return render_template("/events/template_selector.html", 

73 programs=allprograms, 

74 celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored), 

75 templates=visibleTemplates) 

76 else: 

77 abort(403) 

78 

79 

80@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST']) 

81def createEvent(templateid, programid): 

82 if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)): 

83 abort(403) 

84 

85 # Validate given URL 

86 program = None 

87 try: 

88 template = EventTemplate.get_by_id(templateid) 

89 if programid: 

90 program = Program.get_by_id(programid) 

91 except DoesNotExist as e: 

92 print("Invalid template or program id:", e) 

93 flash("There was an error with your selection. Please try again or contact Systems Support.", "danger") 

94 return redirect(url_for("admin.program_picker")) 

95 

96 # Get the data from the form or from the template 

97 eventData = template.templateData 

98 

99 eventData['program'] = program 

100 

101 if request.method == "GET": 

102 eventData['contactName'] = "CELTS Admin" 

103 eventData['contactEmail'] = app.config['celts_admin_contact'] 

104 if program: 

105 eventData['location'] = program.defaultLocation 

106 if program.contactName: 

107 eventData['contactName'] = program.contactName 

108 if program.contactEmail: 

109 eventData['contactEmail'] = program.contactEmail 

110 

111 # Try to save the form 

112 if request.method == "POST": 

113 eventData.update(request.form.copy()) 

114 try: 

115 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request)) 

116 

117 except Exception as e: 

118 print("Error saving event:", e) 

119 savedEvents = False 

120 validationErrorMessage = "Unknown Error Saving Event. Please try again" 

121 

122 if savedEvents: 

123 rsvpcohorts = request.form.getlist("cohorts[]") 

124 for year in rsvpcohorts: 

125 rsvpForBonnerCohort(int(year), savedEvents[0].id) 

126 

127 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize 

128 flash(f"{noun} successfully created!", 'success') 

129 

130 if program: 

131 if len(savedEvents) > 1: 

132 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')}.") 

133 else: 

134 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')}.") 

135 else: 

136 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')}.") 

137 

138 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id)) 

139 else: 

140 flash(validationErrorMessage, 'warning') 

141 

142 # make sure our data is the same regardless of GET or POST 

143 preprocessEventData(eventData) 

144 isProgramManager = g.current_user.isProgramManagerFor(programid) 

145 

146 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0) 

147 

148 requirements, bonnerCohorts = [], [] 

149 if eventData['program'] is not None and eventData['program'].isBonnerScholars: 

150 requirements = getCertRequirements(Certification.BONNER) 

151 bonnerCohorts = getBonnerCohorts(limit=5) 

152 return render_template(f"/admin/{template.templateFile}", 

153 template = template, 

154 eventData = eventData, 

155 futureTerms = futureTerms, 

156 requirements = requirements, 

157 bonnerCohorts = bonnerCohorts, 

158 isProgramManager = isProgramManager) 

159 

160 

161@admin_bp.route('/event/<eventId>/rsvp', methods=['GET']) 

162def rsvpLogDisplay(eventId): 

163 event = Event.get_by_id(eventId) 

164 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)): 

165 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc()) 

166 return render_template("/events/rsvpLog.html", 

167 event = event, 

168 allLogs = allLogs) 

169 else: 

170 abort(403) 

171 

172@admin_bp.route('/event/<eventId>/renew', methods=['POST']) 

173def renewEvent(eventId): 

174 try: 

175 formData = request.form 

176 try: 

177 assert formData['timeStart'] < formData['timeEnd'] 

178 except AssertionError: 

179 flash("End time must be after start time", 'warning') 

180 return redirect(url_for('admin.eventDisplay', eventId = eventId)) 

181 

182 try: 

183 if formData.get('dateEnd'): 

184 assert formData['dateStart'] < formData['dateEnd'] 

185 except AssertionError: 

186 flash("End date must be after start date", 'warning') 

187 return redirect(url_for('admin.eventDisplay', eventId = eventId)) 

188 

189 

190 priorEvent = model_to_dict(Event.get_by_id(eventId)) 

191 newEventDict = priorEvent.copy() 

192 newEventDict.pop('id') 

193 newEventDict.update({ 

194 'program': int(priorEvent['program']['id']), 

195 'term': int(priorEvent['term']['id']), 

196 'timeStart': formData['timeStart'], 

197 'timeEnd': formData['timeEnd'], 

198 'location': formData['location'], 

199 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}', 

200 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}', 

201 'isRecurring': bool(priorEvent['recurringId']) 

202 }) 

203 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True) 

204 if message: 

205 flash(message, "danger") 

206 return redirect(url_for('admin.eventDisplay', eventId = eventId)) 

207 

208 copyRsvpToNewEvent(priorEvent, newEvent[0]) 

209 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.") 

210 flash("Event successfully renewed.", "success") 

211 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id)) 

212 

213 

214 except Exception as e: 

215 print("Error while trying to renew event:", e) 

216 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger') 

217 return redirect(url_for('admin.eventDisplay', eventId = eventId)) 

218 

219 

220 

221@admin_bp.route('/event/<eventId>/view', methods=['GET']) 

222@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST']) 

223def eventDisplay(eventId): 

224 pageViewsCount = EventView.select().where(EventView.event == eventId).count() 

225 if request.method == 'GET' and request.path == f'/event/{eventId}/view': 

226 viewer = g.current_user 

227 event = Event.get_by_id(eventId) 

228 addEventView(viewer,event) 

229 # Validate given URL 

230 try: 

231 event = Event.get_by_id(eventId) 

232 except DoesNotExist as e: 

233 print(f"Unknown event: {eventId}") 

234 abort(404) 

235 

236 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event)) 

237 if 'edit' in request.url_rule.rule and notPermitted: 

238 abort(403) 

239 

240 eventData = model_to_dict(event, recurse=False) 

241 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event) 

242 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) 

243 

244 image = None 

245 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"] 

246 for attachment in associatedAttachments: 

247 for extension in picurestype: 

248 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True): 

249 image = filepaths[attachment.fileName][0] 

250 if image: 

251 break 

252 

253 

254 if request.method == "POST": # Attempt to save form 

255 eventData = request.form.copy() 

256 try: 

257 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request)) 

258 

259 except Exception as e: 

260 print("Error saving event:", e) 

261 savedEvents = False 

262 validationErrorMessage = "Unknown Error Saving Event. Please try again" 

263 

264 

265 if savedEvents: 

266 rsvpcohorts = request.form.getlist("cohorts[]") 

267 for year in rsvpcohorts: 

268 rsvpForBonnerCohort(int(year), event.id) 

269 

270 flash("Event successfully updated!", "success") 

271 return redirect(url_for("admin.eventDisplay", eventId = event.id)) 

272 else: 

273 flash(validationErrorMessage, 'warning') 

274 

275 # make sure our data is the same regardless of GET and POST 

276 preprocessEventData(eventData) 

277 eventData['program'] = event.program 

278 futureTerms = selectSurroundingTerms(g.current_term) 

279 userHasRSVPed = checkUserRsvp(g.current_user, event) 

280 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) 

281 isProgramManager = g.current_user.isProgramManagerFor(eventData['program']) 

282 requirements, bonnerCohorts = [], [] 

283 

284 if eventData['program'] and eventData['program'].isBonnerScholars: 

285 requirements = getCertRequirements(Certification.BONNER) 

286 bonnerCohorts = getBonnerCohorts(limit=5) 

287 

288 rule = request.url_rule 

289 

290 # Event Edit 

291 if 'edit' in rule.rule: 

292 return render_template("admin/createEvent.html", 

293 eventData = eventData, 

294 futureTerms=futureTerms, 

295 event = event, 

296 requirements = requirements, 

297 bonnerCohorts = bonnerCohorts, 

298 userHasRSVPed = userHasRSVPed, 

299 isProgramManager = isProgramManager, 

300 filepaths = filepaths) 

301 # Event View 

302 else: 

303 # get text representations of dates for html 

304 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p") 

305 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p") 

306 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y") 

307 eventCountdown = getCountdownToEvent(event) 

308 

309 

310 # Identify the next event in a recurring series 

311 if event.recurringId: 

312 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId) 

313 .where((Event.isCanceled == False) | (Event.id == event.id)) 

314 .order_by(Event.startDate)) 

315 eventIndex = eventSeriesList.index(event) 

316 if len(eventSeriesList) != (eventIndex + 1): 

317 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1] 

318 

319 currentEventRsvpAmount = getEventRsvpCount(event.id) 

320 

321 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term) 

322 

323 return render_template("eventView.html", 

324 eventData=eventData, 

325 event=event, 

326 userHasRSVPed=userHasRSVPed, 

327 programTrainings=userParticipatedTrainingEvents, 

328 currentEventRsvpAmount=currentEventRsvpAmount, 

329 isProgramManager=isProgramManager, 

330 filepaths=filepaths, 

331 image=image, 

332 pageViewsCount=pageViewsCount, 

333 eventCountdown=eventCountdown) 

334 

335 

336 

337@admin_bp.route('/event/<eventId>/cancel', methods=['POST']) 

338def cancelRoute(eventId): 

339 if g.current_user.isAdmin: 

340 try: 

341 cancelEvent(eventId) 

342 return redirect(request.referrer) 

343 

344 except Exception as e: 

345 print('Error while canceling event:', e) 

346 return "", 500 

347 

348 else: 

349 abort(403) 

350 

351@admin_bp.route('/event/<eventId>/delete', methods=['POST']) 

352def deleteRoute(eventId): 

353 try: 

354 deleteEvent(eventId) 

355 flash("Event successfully deleted.", "success") 

356 return redirect(url_for("main.events", selectedTerm=g.current_term)) 

357 

358 except Exception as e: 

359 print('Error while canceling event:', e) 

360 return "", 500 

361 

362@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST']) 

363def deleteEventAndAllFollowingRoute(eventId): 

364 try: 

365 deleteEventAndAllFollowing(eventId) 

366 flash("Events successfully deleted.", "success") 

367 return redirect(url_for("main.events", selectedTerm=g.current_term)) 

368 

369 except Exception as e: 

370 print('Error while canceling event:', e) 

371 return "", 500 

372 

373@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST']) 

374def deleteAllRecurringEventsRoute(eventId): 

375 try: 

376 deleteAllRecurringEvents(eventId) 

377 flash("Events successfully deleted.", "success") 

378 return redirect(url_for("main.events", selectedTerm=g.current_term)) 

379 

380 except Exception as e: 

381 print('Error while canceling event:', e) 

382 return "", 500 

383 

384@admin_bp.route('/makeRecurringEvents', methods=['POST']) 

385def addRecurringEvents(): 

386 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy())) 

387 return json.dumps(recurringEvents, default=str) 

388 

389 

390@admin_bp.route('/userProfile', methods=['POST']) 

391def userProfile(): 

392 volunteerName= request.form.copy() 

393 if volunteerName['searchStudentsInput']: 

394 username = volunteerName['searchStudentsInput'].strip("()") 

395 user=username.split('(')[-1] 

396 return redirect(url_for('main.viewUsersProfile', username=user)) 

397 else: 

398 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger') 

399 return redirect(url_for('admin.studentSearchPage')) 

400 

401@admin_bp.route('/search_student', methods=['GET']) 

402def studentSearchPage(): 

403 if g.current_user.isAdmin: 

404 return render_template("/admin/searchStudentPage.html") 

405 abort(403) 

406 

407@admin_bp.route('/addParticipants', methods = ['GET']) 

408def addParticipants(): 

409 '''Renders the page, will be removed once merged with full page''' 

410 

411 return render_template('addParticipants.html', 

412 title="Add Participants") 

413 

414@admin_bp.route('/activityLogs', methods = ['GET', 'POST']) 

415def activityLogs(): 

416 if g.current_user.isCeltsAdmin: 

417 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc()) 

418 return render_template("/admin/activityLogs.html", 

419 allLogs = allLogs) 

420 else: 

421 abort(403) 

422 

423@admin_bp.route("/deleteEventFile", methods=["POST"]) 

424def deleteEventFile(): 

425 fileData= request.form 

426 eventfile=FileHandler(eventId=fileData["databaseId"]) 

427 eventfile.deleteFile(fileData["fileId"]) 

428 return "" 

429 

430@admin_bp.route("/uploadCourseParticipant", methods= ["POST"]) 

431def addCourseFile(): 

432 fileData = request.files['addCourseParticipants'] 

433 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename) 

434 fileData.save(filePath) 

435 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath) 

436 os.remove(filePath) 

437 return redirect(url_for("admin.manageServiceLearningCourses")) 

438 

439@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST']) 

440@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST']) 

441def manageServiceLearningCourses(term=None): 

442 

443 """ 

444 The SLC management page for admins 

445 """ 

446 if not g.current_user.isCeltsAdmin: 

447 abort(403) 

448 

449 if request.method == 'POST' and "submitParticipant" in request.form: 

450 saveCourseParticipantsToDatabase(session.pop('cpPreview', {})) 

451 flash('Courses and participants saved successfully!', 'success') 

452 return redirect(url_for('admin.manageServiceLearningCourses')) 

453 

454 manageTerm = Term.get_or_none(Term.id == term) or g.current_term 

455 

456 setRedirectTarget(request.full_path) 

457 # retrieve and store the courseID of the imported course from a session variable if it exists.  

458 # This allows us to export the courseID in the html and use it. 

459 courseID = session.get("alterCourseId") 

460 

461 if courseID: 

462 # delete courseID from the session if it was retrieved, for storage purposes. 

463 session.pop("alterCourseId") 

464 return render_template('/admin/manageServiceLearningFaculty.html', 

465 courseInstructors = getInstructorCourses(), 

466 unapprovedCourses = unapprovedCourses(manageTerm), 

467 approvedCourses = approvedCourses(manageTerm), 

468 importedCourses = getImportedCourses(manageTerm), 

469 terms = selectSurroundingTerms(g.current_term), 

470 term = manageTerm, 

471 cpPreview = session.get('cpPreview', {}), 

472 cpPreviewErrors = session.get('cpErrors', []), 

473 courseID = courseID 

474 ) 

475 

476 return render_template('/admin/manageServiceLearningFaculty.html', 

477 courseInstructors = getInstructorCourses(), 

478 unapprovedCourses = unapprovedCourses(manageTerm), 

479 approvedCourses = approvedCourses(manageTerm), 

480 importedCourses = getImportedCourses(manageTerm), 

481 terms = selectSurroundingTerms(g.current_term), 

482 term = manageTerm, 

483 cpPreview= session.get('cpPreview',{}), 

484 cpPreviewErrors = session.get('cpErrors',[]) 

485 ) 

486 

487@admin_bp.route('/admin/getSidebarInformation', methods=['GET']) 

488def getSidebarInformation() -> str: 

489 """ 

490 Get the count of unapproved courses and students interested in the minor for the current term  

491 to display in the admin sidebar. It must be returned as a string to be received by the 

492 ajax request. 

493 """ 

494 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term)) 

495 interestedStudentsCount: int = len(getMinorInterest()) 

496 return {"unapprovedCoursesCount": unapprovedCoursesCount, 

497 "interestedStudentsCount": interestedStudentsCount} 

498 

499@admin_bp.route("/deleteUploadedFile", methods= ["POST"]) 

500def removeFromSession(): 

501 try: 

502 session.pop('cpPreview') 

503 except KeyError: 

504 pass 

505 

506 return "" 

507 

508@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET']) 

509def alterImportedCourse(courseID): 

510 """ 

511 This route handles a GET and a POST request for the purpose of imported courses.  

512 The GET request provides preexisting information of an imported course in a modal.  

513 The POST request updates a specific imported course (course name, course abbreviation,  

514 hours earned on completion, list of instructors) in the database with new information  

515 coming from the imported courses modal.  

516 """ 

517 if request.method == 'GET': 

518 try: 

519 targetCourse = Course.get_by_id(courseID) 

520 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse) 

521 

522 try: 

523 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned 

524 except IndexError: # If a course has no participant, IndexError will be raised 

525 serviceHours = 20 

526 

527 courseData = model_to_dict(targetCourse, recurse=False) 

528 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors] 

529 courseData['hoursEarned'] = serviceHours 

530 

531 return jsonify(courseData) 

532 

533 except DoesNotExist: 

534 flash("Course not found") 

535 return jsonify({"error": "Course not found"}), 404 

536 

537 if request.method == 'POST': 

538 # Update course information in the database 

539 courseData = request.form.copy() 

540 editImportedCourses(courseData) 

541 session['alterCourseId'] = courseID 

542 

543 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId'])) 

544 

545 

546@admin_bp.route("/manageBonner") 

547def manageBonner(): 

548 if not g.current_user.isCeltsAdmin: 

549 abort(403) 

550 

551 return render_template("/admin/bonnerManagement.html", 

552 cohorts=getBonnerCohorts(), 

553 events=getBonnerEvents(g.current_term), 

554 requirements=getCertRequirements(certification=Certification.BONNER)) 

555 

556@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"]) 

557def updatecohort(year, method, username): 

558 if not g.current_user.isCeltsAdmin: 

559 abort(403) 

560 

561 try: 

562 user = User.get_by_id(username) 

563 except: 

564 abort(500) 

565 

566 if method == "add": 

567 try: 

568 BonnerCohort.create(year=year, user=user) 

569 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success") 

570 except IntegrityError as e: 

571 # if they already exist, ignore the error 

572 flash(f'Error: {user.fullName} already added.', "danger") 

573 pass 

574 

575 elif method == "remove": 

576 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute() 

577 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success") 

578 else: 

579 flash(f"Error: {user.fullName} can't be added.", "danger") 

580 abort(500) 

581 

582 return "" 

583 

584@admin_bp.route("/bonnerxls") 

585def bonnerxls(): 

586 if not g.current_user.isCeltsAdmin: 

587 abort(403) 

588 

589 newfile = makeBonnerXls() 

590 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True) 

591 

592@admin_bp.route("/saveRequirements/<certid>", methods=["POST"]) 

593def saveRequirements(certid): 

594 if not g.current_user.isCeltsAdmin: 

595 abort(403) 

596 

597 newRequirements = updateCertRequirements(certid, request.get_json()) 

598 

599 return jsonify([requirement.id for requirement in newRequirements]) 

600 

601 

602@admin_bp.route("/displayEventFile", methods=["POST"]) 

603def displayEventFile(): 

604 fileData = request.form 

605 eventfile = FileHandler(eventId=fileData["id"]) 

606 isChecked = fileData.get('checked') == 'true' 

607 eventfile.changeDisplay(fileData['id'], isChecked) 

608 return ""