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

438 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-08-27 21: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, 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/template_selector.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"/admin/{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("admin/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("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('/addParticipants', methods = ['GET']) 

477def addParticipants(): 

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

479 

480 return render_template('addParticipants.html', 

481 title="Add Participants") 

482 

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

484def activityLogs(): 

485 if g.current_user.isCeltsAdmin: 

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

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

488 allLogs = allLogs) 

489 else: 

490 abort(403) 

491 

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

493def deleteEventFile(): 

494 fileData= request.form 

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

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

497 return "" 

498 

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

500def addCourseFile(): 

501 fileData = request.files['addCourseParticipants'] 

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

503 fileData.save(filePath) 

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

505 os.remove(filePath) 

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

507 

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

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

510def manageServiceLearningCourses(term=None): 

511 

512 """ 

513 The SLC management page for admins 

514 """ 

515 if not g.current_user.isCeltsAdmin: 

516 abort(403) 

517 

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

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

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

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

522 

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

524 

525 setRedirectTarget(request.full_path) 

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

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

528 courseID = session.get("alterCourseId") 

529 

530 if courseID: 

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

532 session.pop("alterCourseId") 

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

534 courseInstructors = getInstructorCourses(), 

535 unapprovedCourses = unapprovedCourses(manageTerm), 

536 approvedCourses = approvedCourses(manageTerm), 

537 importedCourses = getImportedCourses(manageTerm), 

538 terms = selectSurroundingTerms(g.current_term), 

539 term = manageTerm, 

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

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

542 courseID = courseID 

543 ) 

544 

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

546 courseInstructors = getInstructorCourses(), 

547 unapprovedCourses = unapprovedCourses(manageTerm), 

548 approvedCourses = approvedCourses(manageTerm), 

549 importedCourses = getImportedCourses(manageTerm), 

550 terms = selectSurroundingTerms(g.current_term), 

551 term = manageTerm, 

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

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

554 ) 

555 

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

557def getSidebarInformation() -> str: 

558 """ 

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

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

561 ajax request. 

562 """ 

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

564 interestedStudentsCount: int = len(getMinorInterest()) 

565 return {"unapprovedCoursesCount": unapprovedCoursesCount, 

566 "interestedStudentsCount": interestedStudentsCount} 

567 

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

569def removeFromSession(): 

570 try: 

571 session.pop('cpPreview') 

572 except KeyError: 

573 pass 

574 

575 return "" 

576 

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

578def alterImportedCourse(courseID): 

579 """ 

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

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

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

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

584 coming from the imported courses modal.  

585 """ 

586 if request.method == 'GET': 

587 try: 

588 targetCourse = Course.get_by_id(courseID) 

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

590 

591 try: 

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

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

594 serviceHours = 20 

595 

596 courseData = model_to_dict(targetCourse, recurse=False) 

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

598 courseData['hoursEarned'] = serviceHours 

599 

600 return jsonify(courseData) 

601 

602 except DoesNotExist: 

603 flash("Course not found") 

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

605 

606 if request.method == 'POST': 

607 # Update course information in the database 

608 courseData = request.form.copy() 

609 editImportedCourses(courseData) 

610 session['alterCourseId'] = courseID 

611 

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

613 

614 

615@admin_bp.route("/manageBonner") 

616def manageBonner(): 

617 if not g.current_user.isCeltsAdmin: 

618 abort(403) 

619 

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

621 cohorts=getBonnerCohorts(), 

622 events=getBonnerEvents(g.current_term), 

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

624 

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

626def updatecohort(year, method, username): 

627 if not g.current_user.isCeltsAdmin: 

628 abort(403) 

629 

630 try: 

631 user = User.get_by_id(username) 

632 except: 

633 abort(500) 

634 

635 if method == "add": 

636 try: 

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

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

639 except IntegrityError as e: 

640 # if they already exist, ignore the error 

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

642 pass 

643 

644 elif method == "remove": 

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

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

647 else: 

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

649 abort(500) 

650 

651 return "" 

652 

653@admin_bp.route("/bonnerxls") 

654def bonnerxls(): 

655 if not g.current_user.isCeltsAdmin: 

656 abort(403) 

657 

658 newfile = makeBonnerXls() 

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

660 

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

662def saveRequirements(certid): 

663 if not g.current_user.isCeltsAdmin: 

664 abort(403) 

665 

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

667 

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

669 

670 

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

672def displayEventFile(): 

673 fileData = request.form 

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

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

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

677 return ""