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

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 

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) 

63 

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

66 

67 return redirect(request.referrer) 

68 

69 

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) 

81 

82 

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) 

87 

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

98 

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

100 eventData = template.templateData 

101 

102 eventData['program'] = program 

103 

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 

113 

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

119 

120 except Exception as e: 

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

122 savedEvents = False 

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

124 

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) 

130 

131 

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

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

134 

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

142 

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

144 else: 

145 flash(validationErrorMessage, 'warning') 

146 

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

148 preprocessEventData(eventData) 

149 isProgramManager = g.current_user.isProgramManagerFor(programid) 

150 

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

152 

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) 

164 

165 

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) 

176 

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

186 

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

193 

194 

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

212 

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

217 

218 

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

223 

224 

225 

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) 

240 

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) 

244 

245 eventData = model_to_dict(event, recurse=False) 

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

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

248 

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 

257 

258 

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

260 eventData = request.form.copy() 

261 try: 

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

263 

264 except Exception as e: 

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

266 savedEvents = False 

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

268 

269 

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) 

275 

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

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

278 else: 

279 flash(validationErrorMessage, 'warning') 

280 

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

289 

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

291 requirements = getCertRequirements(Certification.BONNER) 

292 bonnerCohorts = getBonnerCohorts(limit=5) 

293 

294 rule = request.url_rule 

295 

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) 

314 

315 

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] 

324 

325 currentEventRsvpAmount = getEventRsvpCount(event.id) 

326 

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

328 

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 ) 

341 

342 

343 

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) 

350 

351 except Exception as e: 

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

353 return "", 500 

354 

355 else: 

356 abort(403) 

357 

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

364 

365 except Exception as e: 

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

367 return "", 500 

368 

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

375 

376 except Exception as e: 

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

378 return "", 500 

379 

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

386 

387 except Exception as e: 

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

389 return "", 500 

390 

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

392def addRecurringEvents(): 

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

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

395 

396 

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

407 

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) 

413 

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

415def addParticipants(): 

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

417 

418 return render_template('addParticipants.html', 

419 title="Add Participants") 

420 

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) 

429 

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

436 

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

445 

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

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

448def manageServiceLearningCourses(term=None): 

449 

450 """ 

451 The SLC management page for admins 

452 """ 

453 if not g.current_user.isCeltsAdmin: 

454 abort(403) 

455 

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

460 

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

462 

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

467 

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 ) 

482 

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 ) 

493 

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} 

505 

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

507def removeFromSession(): 

508 try: 

509 session.pop('cpPreview') 

510 except KeyError: 

511 pass 

512 

513 return "" 

514 

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) 

528 

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 

533 

534 courseData = model_to_dict(targetCourse, recurse=False) 

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

536 courseData['hoursEarned'] = serviceHours 

537 

538 return jsonify(courseData) 

539 

540 except DoesNotExist: 

541 flash("Course not found") 

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

543 

544 if request.method == 'POST': 

545 # Update course information in the database 

546 courseData = request.form.copy() 

547 editImportedCourses(courseData) 

548 session['alterCourseId'] = courseID 

549 

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

551 

552 

553@admin_bp.route("/manageBonner") 

554def manageBonner(): 

555 if not g.current_user.isCeltsAdmin: 

556 abort(403) 

557 

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

559 cohorts=getBonnerCohorts(), 

560 events=getBonnerEvents(g.current_term), 

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

562 

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) 

567 

568 try: 

569 user = User.get_by_id(username) 

570 except: 

571 abort(500) 

572 

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 

581 

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) 

588 

589 return "" 

590 

591@admin_bp.route("/bonnerxls") 

592def bonnerxls(): 

593 if not g.current_user.isCeltsAdmin: 

594 abort(403) 

595 

596 newfile = makeBonnerXls() 

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

598 

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

600def saveRequirements(certid): 

601 if not g.current_user.isCeltsAdmin: 

602 abort(403) 

603 

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

605 

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

607 

608 

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