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

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 

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, addBonnerCohortToRsvpLog 

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

39 

40from app.controllers.admin import admin_bp 

41from app.logic.spreadsheet import createSpreadsheet 

42 

43 

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) 

49 

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) 

55 

56 

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) 

62 

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'])) 

65 

66 return redirect(request.referrer) 

67 

68 

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) 

80 

81 

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) 

86 

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")) 

97 

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

99 eventData = template.templateData 

100 

101 eventData['program'] = program 

102 

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 

112 

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)) 

118 

119 except Exception as e: 

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

121 savedEvents = False 

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

123 

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) 

129 

130 

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

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

133 

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

141 

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

143 else: 

144 flash(validationErrorMessage, 'warning') 

145 

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

147 preprocessEventData(eventData) 

148 isProgramManager = g.current_user.isProgramManagerFor(programid) 

149 

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

151 

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) 

163 

164 

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) 

175 

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)) 

185 

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)) 

192 

193 

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)) 

211 

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)) 

216 

217 

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)) 

222 

223 

224 

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) 

239 

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) 

243 

244 eventData = model_to_dict(event, recurse=False) 

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

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

247 

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 

256 

257 

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

259 eventData = request.form.copy() 

260 try: 

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

262 

263 except Exception as e: 

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

265 savedEvents = False 

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

267 

268 

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) 

274 

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

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

277 else: 

278 flash(validationErrorMessage, 'warning') 

279 

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 = [], [] 

288 

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

290 requirements = getCertRequirements(Certification.BONNER) 

291 bonnerCohorts = getBonnerCohorts(limit=5) 

292 

293 rule = request.url_rule 

294 

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) 

313 

314 

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] 

323 

324 currentEventRsvpAmount = getEventRsvpCount(event.id) 

325 

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

327 

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 ) 

340 

341 

342 

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) 

349 

350 except Exception as e: 

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

352 return "", 500 

353 

354 else: 

355 abort(403) 

356 

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)) 

363 

364 except Exception as e: 

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

366 return "", 500 

367 

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)) 

374 

375 except Exception as e: 

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

377 return "", 500 

378 

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)) 

385 

386 except Exception as e: 

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

388 return "", 500 

389 

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

391def addRecurringEvents(): 

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

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

394 

395 

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')) 

406 

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) 

412 

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

414def addParticipants(): 

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

416 

417 return render_template('addParticipants.html', 

418 title="Add Participants") 

419 

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) 

428 

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 "" 

435 

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")) 

444 

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

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

447def manageServiceLearningCourses(term=None): 

448 

449 """ 

450 The SLC management page for admins 

451 """ 

452 if not g.current_user.isCeltsAdmin: 

453 abort(403) 

454 

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')) 

459 

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

461 

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") 

466 

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 ) 

481 

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 ) 

492 

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} 

504 

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

506def removeFromSession(): 

507 try: 

508 session.pop('cpPreview') 

509 except KeyError: 

510 pass 

511 

512 return "" 

513 

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) 

527 

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 

532 

533 courseData = model_to_dict(targetCourse, recurse=False) 

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

535 courseData['hoursEarned'] = serviceHours 

536 

537 return jsonify(courseData) 

538 

539 except DoesNotExist: 

540 flash("Course not found") 

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

542 

543 if request.method == 'POST': 

544 # Update course information in the database 

545 courseData = request.form.copy() 

546 editImportedCourses(courseData) 

547 session['alterCourseId'] = courseID 

548 

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

550 

551 

552@admin_bp.route("/manageBonner") 

553def manageBonner(): 

554 if not g.current_user.isCeltsAdmin: 

555 abort(403) 

556 

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

558 cohorts=getBonnerCohorts(), 

559 events=getBonnerEvents(g.current_term), 

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

561 

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) 

566 

567 try: 

568 user = User.get_by_id(username) 

569 except: 

570 abort(500) 

571 

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 

580 

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) 

587 

588 return "" 

589 

590@admin_bp.route("/bonnerxls") 

591def bonnerxls(): 

592 if not g.current_user.isCeltsAdmin: 

593 abort(403) 

594 

595 newfile = makeBonnerXls() 

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

597 

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

599def saveRequirements(certid): 

600 if not g.current_user.isCeltsAdmin: 

601 abort(403) 

602 

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

604 

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

606 

607 

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 ""