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

434 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-09-13 18:43 +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, NEWdeleteEvent, attemptSaveEvent, NEWattemptSaveEvent, preprocessEventData, NEWpreprocessEventData, calculateRecurringEventFrequency, calculateRepeatingEventFrequency, \ 

34 deleteEventAndAllFollowing, NEWdeleteEventAndAllFollowing, deleteAllRecurringEvents, deleteAllEventsInSeries, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent, calculateNewMultipleOfferingId, calculateNewSeriesId 

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

85# def 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# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

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

205def createEvent(templateid, programid): 

206 savedEventsList = [] 

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

208 abort(403) 

209 

210 # Validate given URL 

211 program = None 

212 try: 

213 template = EventTemplate.get_by_id(templateid) 

214 if programid: 

215 program = Program.get_by_id(programid) 

216 except DoesNotExist as e: 

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

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

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

220 

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

222 eventData = template.templateData 

223 

224 eventData['program'] = program 

225 

226 if request.method == "GET": 

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

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

229 if program: 

230 eventData['location'] = program.defaultLocation 

231 if program.contactName: 

232 eventData['contactName'] = program.contactName 

233 if program.contactEmail: 

234 eventData['contactEmail'] = program.contactEmail 

235 

236 # Try to save the form 

237 # RepeatingImplementation: Not sure why multiple offerings is saved differently from recurring? 

238 if request.method == "POST": 

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

240 

241 if eventData.get('isSeries') and eventData.get('isRepeating') is None: 

242 seriesId = calculateNewSeriesId() 

243 

244 seriesEvents = json.loads(eventData.get('seriesData')) 

245 for event in seriesEvents: 

246 eventDict = eventData.copy() 

247 eventDict.update({ 

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

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

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

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

252 'seriesId': seriesId 

253 }) 

254 try: 

255 savedEvents, validationErrorMessage = NEWattemptSaveEvent(eventDict, getFilesFromRequest(request)) 

256 savedEventsList.append(savedEvents) 

257 

258 except Exception as e: 

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

260 

261 else: 

262 try: 

263 savedEvents, validationErrorMessage = NEWattemptSaveEvent(eventData, getFilesFromRequest(request)) 

264 except Exception as e: 

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

266 

267 if savedEvents: 

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

269 for year in rsvpcohorts: 

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

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

272 

273 

274 noun = (eventData.get('isSeries') or eventData.get('isRepeating') and "Events" or "Event") # pluralize 

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

276 

277 

278 if program: 

279 if eventData.get('isRepeating'): 

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

281 elif len(savedEventsList) >= 1 and eventData.get('isSeries'): 

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

283 

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

285 

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

287 

288 if len(modifiedSavedEvents) > 1: 

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

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

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

292 last_event_date = event_dates[-1] 

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

294 

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

296 else: 

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

298 else: 

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

300 

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

302 else: 

303 flash(validationErrorMessage, 'warning') 

304 

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

306 NEWpreprocessEventData(eventData) 

307 isProgramManager = g.current_user.isProgramManagerFor(programid) 

308 

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

310 

311 requirements, bonnerCohorts = [], [] 

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

313 requirements = getCertRequirements(Certification.BONNER) 

314 bonnerCohorts = getBonnerCohorts(limit=5) 

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

316 template = template, 

317 eventData = eventData, 

318 futureTerms = futureTerms, 

319 requirements = requirements, 

320 bonnerCohorts = bonnerCohorts, 

321 isProgramManager = isProgramManager) 

322 

323 

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

325def rsvpLogDisplay(eventId): 

326 event = Event.get_by_id(eventId) 

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

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

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

330 event = event, 

331 allLogs = allLogs) 

332 else: 

333 abort(403) 

334 

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

336def renewEvent(eventId): 

337 try: 

338 formData = request.form 

339 try: 

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

341 except AssertionError: 

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

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

344 

345 try: 

346 if formData.get('dateEnd'): 

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

348 except AssertionError: 

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

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

351 

352 

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

354 newEventDict = priorEvent.copy() 

355 newEventDict.pop('id') 

356 newEventDict.update({ 

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

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

359 'timeStart': formData['timeStart'], 

360 'timeEnd': formData['timeEnd'], 

361 'location': formData['location'], 

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

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

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

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

366 }) 

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

368 if message: 

369 flash(message, "danger") 

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

371 

372 copyRsvpToNewEvent(priorEvent, newEvent[0]) 

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

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

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

376 

377 

378 except Exception as e: 

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

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

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

382 

383 

384 

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

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

387# def eventDisplay(eventId): 

388# pageViewsCount = EventView.select().where(EventView.event == eventId).count() 

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

390# viewer = g.current_user 

391# event = Event.get_by_id(eventId) 

392# addEventView(viewer,event)  

393# # Validate given URL 

394# try: 

395# event = Event.get_by_id(eventId) 

396# except DoesNotExist as e: 

397# print(f"Unknown event: {eventId}") 

398# abort(404) 

399 

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

401# if 'edit' in request.url_rule.rule and notPermitted: 

402# abort(403) 

403 

404# eventData = model_to_dict(event, recurse=False) 

405# associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event) 

406# filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) 

407 

408# image = None 

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

410# for attachment in associatedAttachments: 

411# for extension in picurestype: 

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

413# image = filepaths[attachment.fileName][0] 

414# if image: 

415# break 

416 

417 

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

419# eventData = request.form.copy() 

420# try: 

421# savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request)) 

422 

423# except Exception as e: 

424# print("Error saving event:", e) 

425# savedEvents = False 

426# validationErrorMessage = "Unknown Error Saving Event. Please try again" 

427 

428 

429# if savedEvents: 

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

431# for year in rsvpcohorts: 

432# rsvpForBonnerCohort(int(year), event.id) 

433# addBonnerCohortToRsvpLog(int(year), event.id) 

434 

435# flash("Event successfully updated!", "success") 

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

437# else: 

438# flash(validationErrorMessage, 'warning') 

439 

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

441# preprocessEventData(eventData) 

442# eventData['program'] = event.program 

443# futureTerms = selectSurroundingTerms(g.current_term) 

444# userHasRSVPed = checkUserRsvp(g.current_user, event)  

445# filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) 

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

447# requirements, bonnerCohorts = [], [] 

448 

449# if eventData['program'] and eventData['program'].isBonnerScholars: 

450# requirements = getCertRequirements(Certification.BONNER) 

451# bonnerCohorts = getBonnerCohorts(limit=5) 

452 

453# rule = request.url_rule 

454 

455# # Event Edit 

456# if 'edit' in rule.rule: 

457# return render_template("admin/createEvent.html", 

458# eventData = eventData, 

459# futureTerms=futureTerms, 

460# event = event, 

461# requirements = requirements, 

462# bonnerCohorts = bonnerCohorts, 

463# userHasRSVPed = userHasRSVPed, 

464# isProgramManager = isProgramManager, 

465# filepaths = filepaths) 

466# # Event View 

467# else: 

468# # get text representations of dates for html 

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

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

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

472# eventCountdown = getCountdownToEvent(event) 

473 

474 

475# # Identify the next event in a recurring series 

476# if event.recurringId: 

477# eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId) 

478# .where((Event.isCanceled == False) | (Event.id == event.id)) 

479# .order_by(Event.startDate)) 

480# eventIndex = eventSeriesList.index(event) 

481# if len(eventSeriesList) != (eventIndex + 1): 

482# eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1] 

483 

484# currentEventRsvpAmount = getEventRsvpCount(event.id) 

485 

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

487 

488# return render_template("eventView.html", 

489# eventData=eventData, 

490# event=event, 

491# userHasRSVPed=userHasRSVPed, 

492# programTrainings=userParticipatedTrainingEvents, 

493# currentEventRsvpAmount=currentEventRsvpAmount, 

494# isProgramManager=isProgramManager, 

495# filepaths=filepaths, 

496# image=image, 

497# pageViewsCount=pageViewsCount, 

498# eventCountdown=eventCountdown 

499# ) 

500 

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

502def eventDisplay(eventId): 

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

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

505 viewer = g.current_user 

506 event = Event.get_by_id(eventId) 

507 addEventView(viewer,event) 

508 # Validate given URL 

509 try: 

510 event = Event.get_by_id(eventId) 

511 except DoesNotExist as e: 

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

513 abort(404) 

514 

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

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

517 abort(403) 

518 

519 eventData = model_to_dict(event, recurse=False) 

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

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

522 

523 image = None 

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

525 for attachment in associatedAttachments: 

526 for extension in picurestype: 

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

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

529 if image: 

530 break 

531 

532 

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

534 eventData = request.form.copy() 

535 try: 

536 savedEvents, validationErrorMessage = NEWattemptSaveEvent(eventData, getFilesFromRequest(request)) 

537 

538 except Exception as e: 

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

540 savedEvents = False 

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

542 

543 

544 if savedEvents: 

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

546 for year in rsvpcohorts: 

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

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

549 

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

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

552 else: 

553 flash(validationErrorMessage, 'warning') 

554 

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

556 NEWpreprocessEventData(eventData) 

557 eventData['program'] = event.program 

558 futureTerms = selectSurroundingTerms(g.current_term) 

559 userHasRSVPed = checkUserRsvp(g.current_user, event) 

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

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

562 requirements, bonnerCohorts = [], [] 

563 

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

565 requirements = getCertRequirements(Certification.BONNER) 

566 bonnerCohorts = getBonnerCohorts(limit=5) 

567 

568 rule = request.url_rule 

569 

570 # Event Edit 

571 if 'edit' in rule.rule: 

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

573 eventData = eventData, 

574 futureTerms=futureTerms, 

575 event = event, 

576 requirements = requirements, 

577 bonnerCohorts = bonnerCohorts, 

578 userHasRSVPed = userHasRSVPed, 

579 isProgramManager = isProgramManager, 

580 filepaths = filepaths) 

581 # Event View 

582 else: 

583 # get text representations of dates for html 

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

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

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

587 eventCountdown = getCountdownToEvent(event) 

588 

589 

590 # Identify the next event in a recurring series 

591 if event.isRepeating: 

592 eventSeriesList = list(Event.select().where(Event.seriesId == event.seriesId) 

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

594 .order_by(Event.startDate)) 

595 eventIndex = eventSeriesList.index(event) 

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

597 eventData["nextRepeatingEvent"] = eventSeriesList[eventIndex + 1] 

598 

599 currentEventRsvpAmount = getEventRsvpCount(event.id) 

600 

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

602 

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

604 eventData=eventData, 

605 event=event, 

606 userHasRSVPed=userHasRSVPed, 

607 programTrainings=userParticipatedTrainingEvents, 

608 currentEventRsvpAmount=currentEventRsvpAmount, 

609 isProgramManager=isProgramManager, 

610 filepaths=filepaths, 

611 image=image, 

612 pageViewsCount=pageViewsCount, 

613 eventCountdown=eventCountdown 

614 ) 

615 

616 

617 

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

619def cancelRoute(eventId): 

620 if g.current_user.isAdmin: 

621 try: 

622 cancelEvent(eventId) 

623 return redirect(request.referrer) 

624 

625 except Exception as e: 

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

627 return "", 500 

628 

629 else: 

630 abort(403) 

631 

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

633# def undoEvent(): 

634# try: 

635# events = session['lastDeletedEvent'] 

636# for eventId in events:  

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

638# event = Event.get_or_none(Event.id == eventId) 

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

640# if event.recurringId is not None: 

641# nameCounter = 1 

642# for recurringEvent in recurringEvents: 

643# newEventNameList = recurringEvent.name.split() 

644# newEventNameList[-1] = f"{nameCounter}" 

645# newEventNameList = " ".join(newEventNameList) 

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

647# nameCounter += 1  

648# flash("Deletion successfully undone.", "success") 

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

650# except Exception as e: 

651# print('Error while canceling event:', e) 

652# return "", 500 

653 

654# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

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

656def NEWundoEvent(): 

657 try: 

658 events = session['lastDeletedEvent'] 

659 for eventId in events: 

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

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

662 repeatingEvents = list(Event.select().where((Event.seriesId==event.seriesId) & (Event.deletionDate == None)).order_by(Event.id)) 

663 if event.isRepeating is True: 

664 nameCounter = 1 

665 for repeatingEvent in repeatingEvents: 

666 newEventNameList = repeatingEvent.name.split() 

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

668 newEventNameList = " ".join(newEventNameList) 

669 Event.update({Event.name: newEventNameList}).where(Event.id==repeatingEvent.id).execute() 

670 nameCounter += 1 

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

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

673 except Exception as e: 

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

675 return "", 500 

676 

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

678# def deleteRoute(eventId): 

679# try: 

680# deleteEvent(eventId) 

681# session['lastDeletedEvent'] = [eventId] 

682# flash("Event successfully deleted.", "success") 

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

684 

685# except Exception as e: 

686# print('Error while canceling event:', e) 

687# return "", 500 

688 

689# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

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

691def deleteRoute(eventId): 

692 try: 

693 NEWdeleteEvent(eventId) 

694 session['lastDeletedEvent'] = [eventId] 

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

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

697 

698 except Exception as e: 

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

700 return "", 500 

701 

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

703# def deleteEventAndAllFollowingRoute(eventId): 

704# try: 

705# session["lastDeletedEvent"] = deleteEventAndAllFollowing(eventId) 

706# flash("Events successfully deleted.", "success") 

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

708 

709# except Exception as e: 

710# print('Error while canceling event:', e) 

711# return "", 500 

712 

713# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

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

715def deleteEventAndAllFollowingRoute(eventId): 

716 try: 

717 session["lastDeletedEvent"] = NEWdeleteEventAndAllFollowing(eventId) 

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

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

720 

721 except Exception as e: 

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

723 return "", 500 

724 

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

726# def deleteAllRecurringEventsRoute(eventId): 

727# try: 

728# session["lastDeletedEvent"] = deleteAllRecurringEvents(eventId) 

729# flash("Events successfully deleted.", "success") 

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

731 

732# except Exception as e: 

733# print('Error while canceling event:', e) 

734# return "", 500 

735 

736# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

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

738def deleteAllEventsInSeriesRoute(eventId): 

739 try: 

740 session["lastDeletedEvent"] = deleteAllEventsInSeries(eventId) 

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

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

743 

744 except Exception as e: 

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

746 return "", 500 

747 

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

749# def addRecurringEvents(): 

750# recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy())) 

751# return json.dumps(recurringEvents, default=str) 

752 

753# RepeatingImplementation: Remove function above; remove "NEW" from the function name  

754@admin_bp.route('/makeRepeatingEvents', methods=['POST']) 

755def addRepeatingEvents(): 

756 repeatingEvents = calculateRepeatingEventFrequency(NEWpreprocessEventData(request.form.copy())) 

757 return json.dumps(repeatingEvents, default=str) 

758 

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

760def userProfile(): 

761 volunteerName= request.form.copy() 

762 if volunteerName['searchStudentsInput']: 

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

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

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

766 else: 

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

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

769 

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

771def studentSearchPage(): 

772 if g.current_user.isAdmin: 

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

774 abort(403) 

775 

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

777def activityLogs(): 

778 if g.current_user.isCeltsAdmin: 

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

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

781 allLogs = allLogs) 

782 else: 

783 abort(403) 

784 

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

786def deleteEventFile(): 

787 fileData= request.form 

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

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

790 return "" 

791 

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

793def addCourseFile(): 

794 fileData = request.files['addCourseParticipants'] 

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

796 fileData.save(filePath) 

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

798 os.remove(filePath) 

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

800 

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

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

803def manageServiceLearningCourses(term=None): 

804 

805 """ 

806 The SLC management page for admins 

807 """ 

808 if not g.current_user.isCeltsAdmin: 

809 abort(403) 

810 

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

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

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

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

815 

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

817 

818 setRedirectTarget(request.full_path) 

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

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

821 courseID = session.get("alterCourseId") 

822 

823 if courseID: 

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

825 session.pop("alterCourseId") 

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

827 courseInstructors = getInstructorCourses(), 

828 unapprovedCourses = unapprovedCourses(manageTerm), 

829 approvedCourses = approvedCourses(manageTerm), 

830 importedCourses = getImportedCourses(manageTerm), 

831 terms = selectSurroundingTerms(g.current_term), 

832 term = manageTerm, 

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

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

835 courseID = courseID 

836 ) 

837 

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

839 courseInstructors = getInstructorCourses(), 

840 unapprovedCourses = unapprovedCourses(manageTerm), 

841 approvedCourses = approvedCourses(manageTerm), 

842 importedCourses = getImportedCourses(manageTerm), 

843 terms = selectSurroundingTerms(g.current_term), 

844 term = manageTerm, 

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

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

847 ) 

848 

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

850def getSidebarInformation() -> str: 

851 """ 

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

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

854 ajax request. 

855 """ 

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

857 interestedStudentsCount: int = len(getMinorInterest()) 

858 return {"unapprovedCoursesCount": unapprovedCoursesCount, 

859 "interestedStudentsCount": interestedStudentsCount} 

860 

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

862def removeFromSession(): 

863 try: 

864 session.pop('cpPreview') 

865 except KeyError: 

866 pass 

867 

868 return "" 

869 

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

871def alterImportedCourse(courseID): 

872 """ 

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

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

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

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

877 coming from the imported courses modal.  

878 """ 

879 if request.method == 'GET': 

880 try: 

881 targetCourse = Course.get_by_id(courseID) 

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

883 

884 try: 

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

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

887 serviceHours = 20 

888 

889 courseData = model_to_dict(targetCourse, recurse=False) 

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

891 courseData['hoursEarned'] = serviceHours 

892 

893 return jsonify(courseData) 

894 

895 except DoesNotExist: 

896 flash("Course not found") 

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

898 

899 if request.method == 'POST': 

900 # Update course information in the database 

901 courseData = request.form.copy() 

902 editImportedCourses(courseData) 

903 session['alterCourseId'] = courseID 

904 

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

906 

907 

908@admin_bp.route("/manageBonner") 

909def manageBonner(): 

910 if not g.current_user.isCeltsAdmin: 

911 abort(403) 

912 

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

914 cohorts=getBonnerCohorts(), 

915 events=getBonnerEvents(g.current_term), 

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

917 

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

919def updatecohort(year, method, username): 

920 if not g.current_user.isCeltsAdmin: 

921 abort(403) 

922 

923 try: 

924 user = User.get_by_id(username) 

925 except: 

926 abort(500) 

927 

928 if method == "add": 

929 try: 

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

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

932 except IntegrityError as e: 

933 # if they already exist, ignore the error 

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

935 pass 

936 

937 elif method == "remove": 

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

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

940 else: 

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

942 abort(500) 

943 

944 return "" 

945 

946@admin_bp.route("/bonnerxls") 

947def bonnerxls(): 

948 if not g.current_user.isCeltsAdmin: 

949 abort(403) 

950 

951 newfile = makeBonnerXls() 

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

953 

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

955def saveRequirements(certid): 

956 if not g.current_user.isCeltsAdmin: 

957 abort(403) 

958 

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

960 

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

962 

963 

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

965def displayEventFile(): 

966 fileData = request.form 

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

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

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

970 return ""