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

418 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-07-31 16:31 +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/undo', methods=['GET']) 

359def undoEvent(): 

360 try: 

361 events = session['lastDeletedEvent'] 

362 for eventId in events: 

363 Event.update({Event.deletionDate: None, Event.deletedBy: None}).where(Event.id == eventId).execute() 

364 event = Event.get_or_none(Event.id == eventId) 

365 recurringEvents = list(Event.select().where((Event.recurringId==event.recurringId) & (Event.deletionDate == None)).order_by(Event.id)) 

366 if event.recurringId is not None: 

367 nameCounter = 1 

368 for recurringEvent in recurringEvents: 

369 newEventNameList = recurringEvent.name.split() 

370 newEventNameList[-1] = f"{nameCounter}" 

371 newEventNameList = " ".join(newEventNameList) 

372 Event.update({Event.name: newEventNameList}).where(Event.id==recurringEvent.id).execute() 

373 nameCounter += 1 

374 flash("Deletion successfully undone.", "success") 

375 return redirect('/eventsList/' + str(g.current_term)) 

376 except Exception as e: 

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

378 return "", 500 

379 

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

381def deleteRoute(eventId): 

382 try: 

383 deleteEvent(eventId) 

384 session['lastDeletedEvent'] = [eventId] 

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

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

387 

388 except Exception as e: 

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

390 return "", 500 

391 

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

393def deleteEventAndAllFollowingRoute(eventId): 

394 try: 

395 session["lastDeletedEvent"] = deleteEventAndAllFollowing(eventId) 

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

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

398 

399 except Exception as e: 

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

401 return "", 500 

402 

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

404def deleteAllRecurringEventsRoute(eventId): 

405 try: 

406 session["lastDeletedEvent"] = deleteAllRecurringEvents(eventId) 

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

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

409 

410 except Exception as e: 

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

412 return "", 500 

413 

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

415def addRecurringEvents(): 

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

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

418 

419 

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

421def userProfile(): 

422 volunteerName= request.form.copy() 

423 if volunteerName['searchStudentsInput']: 

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

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

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

427 else: 

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

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

430 

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

432def studentSearchPage(): 

433 if g.current_user.isAdmin: 

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

435 abort(403) 

436 

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

438def addParticipants(): 

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

440 

441 return render_template('addParticipants.html', 

442 title="Add Participants") 

443 

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

445def activityLogs(): 

446 if g.current_user.isCeltsAdmin: 

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

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

449 allLogs = allLogs) 

450 else: 

451 abort(403) 

452 

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

454def deleteEventFile(): 

455 fileData= request.form 

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

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

458 return "" 

459 

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

461def addCourseFile(): 

462 fileData = request.files['addCourseParticipants'] 

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

464 fileData.save(filePath) 

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

466 os.remove(filePath) 

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

468 

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

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

471def manageServiceLearningCourses(term=None): 

472 

473 """ 

474 The SLC management page for admins 

475 """ 

476 if not g.current_user.isCeltsAdmin: 

477 abort(403) 

478 

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

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

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

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

483 

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

485 

486 setRedirectTarget(request.full_path) 

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

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

489 courseID = session.get("alterCourseId") 

490 

491 if courseID: 

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

493 session.pop("alterCourseId") 

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

495 courseInstructors = getInstructorCourses(), 

496 unapprovedCourses = unapprovedCourses(manageTerm), 

497 approvedCourses = approvedCourses(manageTerm), 

498 importedCourses = getImportedCourses(manageTerm), 

499 terms = selectSurroundingTerms(g.current_term), 

500 term = manageTerm, 

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

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

503 courseID = courseID 

504 ) 

505 

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

507 courseInstructors = getInstructorCourses(), 

508 unapprovedCourses = unapprovedCourses(manageTerm), 

509 approvedCourses = approvedCourses(manageTerm), 

510 importedCourses = getImportedCourses(manageTerm), 

511 terms = selectSurroundingTerms(g.current_term), 

512 term = manageTerm, 

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

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

515 ) 

516 

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

518def getSidebarInformation() -> str: 

519 """ 

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

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

522 ajax request. 

523 """ 

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

525 interestedStudentsCount: int = len(getMinorInterest()) 

526 return {"unapprovedCoursesCount": unapprovedCoursesCount, 

527 "interestedStudentsCount": interestedStudentsCount} 

528 

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

530def removeFromSession(): 

531 try: 

532 session.pop('cpPreview') 

533 except KeyError: 

534 pass 

535 

536 return "" 

537 

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

539def alterImportedCourse(courseID): 

540 """ 

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

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

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

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

545 coming from the imported courses modal.  

546 """ 

547 if request.method == 'GET': 

548 try: 

549 targetCourse = Course.get_by_id(courseID) 

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

551 

552 try: 

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

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

555 serviceHours = 20 

556 

557 courseData = model_to_dict(targetCourse, recurse=False) 

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

559 courseData['hoursEarned'] = serviceHours 

560 

561 return jsonify(courseData) 

562 

563 except DoesNotExist: 

564 flash("Course not found") 

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

566 

567 if request.method == 'POST': 

568 # Update course information in the database 

569 courseData = request.form.copy() 

570 editImportedCourses(courseData) 

571 session['alterCourseId'] = courseID 

572 

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

574 

575 

576@admin_bp.route("/manageBonner") 

577def manageBonner(): 

578 if not g.current_user.isCeltsAdmin: 

579 abort(403) 

580 

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

582 cohorts=getBonnerCohorts(), 

583 events=getBonnerEvents(g.current_term), 

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

585 

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

587def updatecohort(year, method, username): 

588 if not g.current_user.isCeltsAdmin: 

589 abort(403) 

590 

591 try: 

592 user = User.get_by_id(username) 

593 except: 

594 abort(500) 

595 

596 if method == "add": 

597 try: 

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

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

600 except IntegrityError as e: 

601 # if they already exist, ignore the error 

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

603 pass 

604 

605 elif method == "remove": 

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

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

608 else: 

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

610 abort(500) 

611 

612 return "" 

613 

614@admin_bp.route("/bonnerxls") 

615def bonnerxls(): 

616 if not g.current_user.isCeltsAdmin: 

617 abort(403) 

618 

619 newfile = makeBonnerXls() 

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

621 

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

623def saveRequirements(certid): 

624 if not g.current_user.isCeltsAdmin: 

625 abort(403) 

626 

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

628 

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

630 

631 

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

633def displayEventFile(): 

634 fileData = request.form 

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

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

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

638 return ""