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

435 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-09-13 19:44 +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, calculateNewMultipleOfferingId 

34from app.logic.events import cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent, calculateNewMultipleOfferingId 

35from app.logic.participants import getParticipationStatusForTrainings, checkUserRsvp 

36from app.logic.minor import getMinorInterest 

37from app.logic.fileHandler import FileHandler 

38from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort, addBonnerCohortToRsvpLog 

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

40 

41from app.controllers.admin import admin_bp 

42from app.logic.spreadsheet import createSpreadsheet 

43 

44 

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

46def reports(): 

47 academicYears = Term.select(Term.academicYear).distinct().order_by(Term.academicYear.desc()) 

48 academicYears = list(map(lambda t: t.academicYear, academicYears)) 

49 return render_template("/admin/reports.html", academicYears=academicYears) 

50 

51@admin_bp.route('/admin/reports/download', methods=['POST']) 

52def downloadFile(): 

53 academicYear = request.form.get('academicYear') 

54 filepath = os.path.abspath(createSpreadsheet(academicYear)) 

55 return send_file(filepath, as_attachment=True) 

56 

57 

58 

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

60def switchUser(): 

61 if app.env == "production": 

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

63 abort(403) 

64 

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

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

67 

68 return redirect(request.referrer) 

69 

70 

71@admin_bp.route('/eventTemplates') 

72def templateSelect(): 

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

74 allprograms = getAllowedPrograms(g.current_user) 

75 visibleTemplates = getAllowedTemplates(g.current_user) 

76 return render_template("/events/templateSelector.html", 

77 programs=allprograms, 

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

79 templates=visibleTemplates) 

80 else: 

81 abort(403) 

82 

83 

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

85def createEvent(templateid, programid): 

86 savedEventsList = [] 

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

88 abort(403) 

89 

90 # Validate given URL 

91 program = None 

92 try: 

93 template = EventTemplate.get_by_id(templateid) 

94 if programid: 

95 program = Program.get_by_id(programid) 

96 except DoesNotExist as e: 

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

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

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

100 

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

102 eventData = template.templateData 

103 

104 eventData['program'] = program 

105 

106 if request.method == "GET": 

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

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

109 if program: 

110 eventData['location'] = program.defaultLocation 

111 if program.contactName: 

112 eventData['contactName'] = program.contactName 

113 if program.contactEmail: 

114 eventData['contactEmail'] = program.contactEmail 

115 

116 # Try to save the form 

117 if request.method == "POST": 

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

119 if eventData.get('isMultipleOffering'): 

120 multipleOfferingId = calculateNewMultipleOfferingId() 

121 

122 multipleOfferingData = json.loads(eventData.get('multipleOfferingData')) 

123 for event in multipleOfferingData: 

124 multipleOfferingDict = eventData.copy() 

125 multipleOfferingDict.update({ 

126 'name': event['eventName'], 

127 'startDate': event['eventDate'], 

128 'timeStart': event['startTime'], 

129 'timeEnd': event['endTime'], 

130 'multipleOfferingId': multipleOfferingId 

131 }) 

132 try: 

133 savedEvents, validationErrorMessage = attemptSaveEvent(multipleOfferingDict, getFilesFromRequest(request)) 

134 savedEventsList.append(savedEvents) 

135 

136 except Exception as e: 

137 print("Failed saving multi event", e) 

138 

139 else: 

140 try: 

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

142 except Exception as e: 

143 print("Failed saving regular event", e) 

144 

145 if savedEvents: 

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

147 for year in rsvpcohorts: 

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

149 addBonnerCohortToRsvpLog(int(year), savedEvents[0].id) 

150 

151 

152 noun = ((eventData.get('isRecurring') or eventData.get('isMultipleOffering')) and "Events" or "Event") # pluralize 

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

154 

155 

156 if program: 

157 if len(savedEvents) > 1 and eventData.get('isRecurring'): 

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

159 

160 elif len(savedEventsList) >= 1 and eventData.get('isMultipleOffering'): 

161 modifiedSavedEvents = [item for sublist in savedEventsList for item in sublist] 

162 

163 event_dates = [event_data[0].startDate.strftime('%m/%d/%Y') for event_data in savedEventsList] 

164 

165 event_list = ', '.join(f"<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>" for event in modifiedSavedEvents) 

166 

167 if len(modifiedSavedEvents) > 1: 

168 #creates list of events created in a multiple series to display in the logs 

169 event_list = ', '.join(event_list.split(', ')[:-1]) + f', and ' + event_list.split(', ')[-1] 

170 #get last date and stick at the end after 'and' so that it reads like a sentence in admin log 

171 last_event_date = event_dates[-1] 

172 event_dates = ', '.join(event_dates[:-1]) + f', and {last_event_date}' 

173 

174 createActivityLog(f"Created events {event_list} for {program.programName}, with start dates of {event_dates}.") 

175 

176 else: 

177 createActivityLog(f"Created events <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')}.") 

178 else: 

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

180 

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

182 else: 

183 flash(validationErrorMessage, 'warning') 

184 

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

186 preprocessEventData(eventData) 

187 isProgramManager = g.current_user.isProgramManagerFor(programid) 

188 

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

190 

191 requirements, bonnerCohorts = [], [] 

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

193 requirements = getCertRequirements(Certification.BONNER) 

194 bonnerCohorts = getBonnerCohorts(limit=5) 

195 return render_template(f"/events/{template.templateFile}", 

196 template = template, 

197 eventData = eventData, 

198 futureTerms = futureTerms, 

199 requirements = requirements, 

200 bonnerCohorts = bonnerCohorts, 

201 isProgramManager = isProgramManager) 

202 

203 

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

205def rsvpLogDisplay(eventId): 

206 event = Event.get_by_id(eventId) 

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

208 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc()) 

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

210 event = event, 

211 allLogs = allLogs) 

212 else: 

213 abort(403) 

214 

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

216def renewEvent(eventId): 

217 try: 

218 formData = request.form 

219 try: 

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

221 except AssertionError: 

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

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

224 

225 try: 

226 if formData.get('dateEnd'): 

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

228 except AssertionError: 

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

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

231 

232 

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

234 newEventDict = priorEvent.copy() 

235 newEventDict.pop('id') 

236 newEventDict.update({ 

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

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

239 'timeStart': formData['timeStart'], 

240 'timeEnd': formData['timeEnd'], 

241 'location': formData['location'], 

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

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

244 'isRecurring': bool(priorEvent['recurringId']), 

245 'isMultipleOffering': bool(priorEvent['multipleOffeirngId']), 

246 }) 

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

248 if message: 

249 flash(message, "danger") 

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

251 

252 copyRsvpToNewEvent(priorEvent, newEvent[0]) 

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

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

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

256 

257 

258 except Exception as e: 

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

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

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

262 

263 

264 

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

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

267def eventDisplay(eventId): 

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

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

270 viewer = g.current_user 

271 event = Event.get_by_id(eventId) 

272 addEventView(viewer,event) 

273 # Validate given URL 

274 try: 

275 event = Event.get_by_id(eventId) 

276 except DoesNotExist as e: 

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

278 abort(404) 

279 

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

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

282 abort(403) 

283 

284 eventData = model_to_dict(event, recurse=False) 

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

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

287 

288 image = None 

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

290 for attachment in associatedAttachments: 

291 for extension in picurestype: 

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

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

294 if image: 

295 break 

296 

297 

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

299 eventData = request.form.copy() 

300 try: 

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

302 

303 except Exception as e: 

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

305 savedEvents = False 

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

307 

308 

309 if savedEvents: 

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

311 for year in rsvpcohorts: 

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

313 addBonnerCohortToRsvpLog(int(year), event.id) 

314 

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

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

317 else: 

318 flash(validationErrorMessage, 'warning') 

319 

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

321 preprocessEventData(eventData) 

322 eventData['program'] = event.program 

323 futureTerms = selectSurroundingTerms(g.current_term) 

324 userHasRSVPed = checkUserRsvp(g.current_user, event) 

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

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

327 requirements, bonnerCohorts = [], [] 

328 

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

330 requirements = getCertRequirements(Certification.BONNER) 

331 bonnerCohorts = getBonnerCohorts(limit=5) 

332 

333 rule = request.url_rule 

334 

335 # Event Edit 

336 if 'edit' in rule.rule: 

337 return render_template("events/createEvent.html", 

338 eventData = eventData, 

339 futureTerms=futureTerms, 

340 event = event, 

341 requirements = requirements, 

342 bonnerCohorts = bonnerCohorts, 

343 userHasRSVPed = userHasRSVPed, 

344 isProgramManager = isProgramManager, 

345 filepaths = filepaths) 

346 # Event View 

347 else: 

348 # get text representations of dates for html 

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

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

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

352 eventCountdown = getCountdownToEvent(event) 

353 

354 

355 # Identify the next event in a recurring series 

356 if event.recurringId: 

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

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

359 .order_by(Event.startDate)) 

360 eventIndex = eventSeriesList.index(event) 

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

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

363 

364 currentEventRsvpAmount = getEventRsvpCount(event.id) 

365 

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

367 

368 return render_template("events/eventView.html", 

369 eventData=eventData, 

370 event=event, 

371 userHasRSVPed=userHasRSVPed, 

372 programTrainings=userParticipatedTrainingEvents, 

373 currentEventRsvpAmount=currentEventRsvpAmount, 

374 isProgramManager=isProgramManager, 

375 filepaths=filepaths, 

376 image=image, 

377 pageViewsCount=pageViewsCount, 

378 eventCountdown=eventCountdown 

379 ) 

380 

381 

382 

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

384def cancelRoute(eventId): 

385 if g.current_user.isAdmin: 

386 try: 

387 cancelEvent(eventId) 

388 return redirect(request.referrer) 

389 

390 except Exception as e: 

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

392 return "", 500 

393 

394 else: 

395 abort(403) 

396 

397@admin_bp.route('/event/undo', methods=['GET']) 

398def undoEvent(): 

399 try: 

400 events = session['lastDeletedEvent'] 

401 for eventId in events: 

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

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

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

405 if event.recurringId is not None: 

406 nameCounter = 1 

407 for recurringEvent in recurringEvents: 

408 newEventNameList = recurringEvent.name.split() 

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

410 newEventNameList = " ".join(newEventNameList) 

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

412 nameCounter += 1 

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

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

415 except Exception as e: 

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

417 return "", 500 

418 

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

420def deleteRoute(eventId): 

421 try: 

422 deleteEvent(eventId) 

423 session['lastDeletedEvent'] = [eventId] 

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

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

426 

427 except Exception as e: 

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

429 return "", 500 

430 

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

432def deleteEventAndAllFollowingRoute(eventId): 

433 try: 

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

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

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

437 

438 except Exception as e: 

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

440 return "", 500 

441 

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

443def deleteAllRecurringEventsRoute(eventId): 

444 try: 

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

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

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

448 

449 except Exception as e: 

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

451 return "", 500 

452 

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

454def addRecurringEvents(): 

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

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

457 

458 

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

460def userProfile(): 

461 volunteerName= request.form.copy() 

462 if volunteerName['searchStudentsInput']: 

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

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

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

466 else: 

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

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

469 

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

471def studentSearchPage(): 

472 if g.current_user.isAdmin: 

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

474 abort(403) 

475 

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

477def activityLogs(): 

478 if g.current_user.isCeltsAdmin: 

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

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

481 allLogs = allLogs) 

482 else: 

483 abort(403) 

484 

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

486def deleteEventFile(): 

487 fileData= request.form 

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

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

490 return "" 

491 

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

493def addCourseFile(): 

494 fileData = request.files['addCourseParticipants'] 

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

496 fileData.save(filePath) 

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

498 os.remove(filePath) 

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

500 

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

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

503def manageServiceLearningCourses(term=None): 

504 

505 """ 

506 The SLC management page for admins 

507 """ 

508 if not g.current_user.isCeltsAdmin: 

509 abort(403) 

510 

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

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

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

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

515 

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

517 

518 setRedirectTarget(request.full_path) 

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

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

521 courseID = session.get("alterCourseId") 

522 

523 if courseID: 

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

525 session.pop("alterCourseId") 

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

527 courseInstructors = getInstructorCourses(), 

528 unapprovedCourses = unapprovedCourses(manageTerm), 

529 approvedCourses = approvedCourses(manageTerm), 

530 importedCourses = getImportedCourses(manageTerm), 

531 terms = selectSurroundingTerms(g.current_term), 

532 term = manageTerm, 

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

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

535 courseID = courseID 

536 ) 

537 

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

539 courseInstructors = getInstructorCourses(), 

540 unapprovedCourses = unapprovedCourses(manageTerm), 

541 approvedCourses = approvedCourses(manageTerm), 

542 importedCourses = getImportedCourses(manageTerm), 

543 terms = selectSurroundingTerms(g.current_term), 

544 term = manageTerm, 

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

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

547 ) 

548 

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

550def getSidebarInformation() -> str: 

551 """ 

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

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

554 ajax request. 

555 """ 

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

557 interestedStudentsCount: int = len(getMinorInterest()) 

558 return {"unapprovedCoursesCount": unapprovedCoursesCount, 

559 "interestedStudentsCount": interestedStudentsCount} 

560 

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

562def removeFromSession(): 

563 try: 

564 session.pop('cpPreview') 

565 except KeyError: 

566 pass 

567 

568 return "" 

569 

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

571def alterImportedCourse(courseID): 

572 """ 

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

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

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

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

577 coming from the imported courses modal.  

578 """ 

579 if request.method == 'GET': 

580 try: 

581 targetCourse = Course.get_by_id(courseID) 

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

583 

584 try: 

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

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

587 serviceHours = 20 

588 

589 courseData = model_to_dict(targetCourse, recurse=False) 

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

591 courseData['hoursEarned'] = serviceHours 

592 

593 return jsonify(courseData) 

594 

595 except DoesNotExist: 

596 flash("Course not found") 

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

598 

599 if request.method == 'POST': 

600 # Update course information in the database 

601 courseData = request.form.copy() 

602 editImportedCourses(courseData) 

603 session['alterCourseId'] = courseID 

604 

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

606 

607 

608@admin_bp.route("/manageBonner") 

609def manageBonner(): 

610 if not g.current_user.isCeltsAdmin: 

611 abort(403) 

612 

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

614 cohorts=getBonnerCohorts(), 

615 events=getBonnerEvents(g.current_term), 

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

617 

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

619def updatecohort(year, method, username): 

620 if not g.current_user.isCeltsAdmin: 

621 abort(403) 

622 

623 try: 

624 user = User.get_by_id(username) 

625 except: 

626 abort(500) 

627 

628 if method == "add": 

629 try: 

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

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

632 except IntegrityError as e: 

633 # if they already exist, ignore the error 

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

635 pass 

636 

637 elif method == "remove": 

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

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

640 else: 

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

642 abort(500) 

643 

644 return "" 

645 

646@admin_bp.route("/bonnerxls") 

647def bonnerxls(): 

648 if not g.current_user.isCeltsAdmin: 

649 abort(403) 

650 

651 newfile = makeBonnerXls() 

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

653 

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

655def saveRequirements(certid): 

656 if not g.current_user.isCeltsAdmin: 

657 abort(403) 

658 

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

660 

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

662 

663 

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

665def displayEventFile(): 

666 fileData = request.form 

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

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

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

670 return ""