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

313 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-03-18 18:03 +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.user import User 

13from app.models.eventTemplate import EventTemplate 

14from app.models.adminLog import AdminLog 

15from app.models.eventRsvpLog import EventRsvpLog 

16from app.models.attachmentUpload import AttachmentUpload 

17from app.models.bonnerCohort import BonnerCohort 

18from app.models.certification import Certification 

19from app.models.user import User 

20from app.models.term import Term 

21from app.models.eventViews import EventView 

22from app.models.courseStatus import CourseStatus 

23 

24from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates 

25from app.logic.createLogs import createAdminLog 

26from app.logic.certification import getCertRequirements, updateCertRequirements 

27from app.logic.utils import selectSurroundingTerms, getFilesFromRequest, getRedirectTarget, setRedirectTarget 

28from app.logic.events import cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount 

29from app.logic.participants import getEventParticipants, getParticipationStatusForTrainings, checkUserRsvp 

30from app.logic.fileHandler import FileHandler 

31from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort 

32from app.controllers.admin import admin_bp 

33from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getInstructorCourses 

34 

35 

36 

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

38def switchUser(): 

39 if app.env == "production": 

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

41 abort(403) 

42 

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

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

45 

46 return redirect(request.referrer) 

47 

48 

49@admin_bp.route('/eventTemplates') 

50def templateSelect(): 

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

52 allprograms = getAllowedPrograms(g.current_user) 

53 visibleTemplates = getAllowedTemplates(g.current_user) 

54 return render_template("/events/template_selector.html", 

55 programs=allprograms, 

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

57 templates=visibleTemplates) 

58 else: 

59 abort(403) 

60 

61 

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

63def createEvent(templateid, programid): 

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

65 abort(403) 

66 

67 # Validate given URL 

68 program = None 

69 try: 

70 template = EventTemplate.get_by_id(templateid) 

71 if programid: 

72 program = Program.get_by_id(programid) 

73 except DoesNotExist as e: 

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

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

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

77 

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

79 eventData = template.templateData 

80 

81 eventData['program'] = program 

82 

83 if request.method == "GET": 

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

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

86 if program: 

87 eventData['location'] = program.defaultLocation 

88 if program.contactName: 

89 eventData['contactName'] = program.contactName 

90 if program.contactEmail: 

91 eventData['contactEmail'] = program.contactEmail 

92 

93 # Try to save the form 

94 if request.method == "POST": 

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

96 try: 

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

98 

99 except Exception as e: 

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

101 savedEvents = False 

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

103 

104 if savedEvents: 

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

106 for year in rsvpcohorts: 

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

108 

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

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

111 

112 if program: 

113 if len(savedEvents) > 1: 

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

115 else: 

116 createAdminLog(f"Created <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a> for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.") 

117 else: 

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

119 

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

121 else: 

122 flash(validationErrorMessage, 'warning') 

123 

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

125 preprocessEventData(eventData) 

126 isProgramManager = g.current_user.isProgramManagerFor(programid) 

127 

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

129 

130 requirements, bonnerCohorts = [], [] 

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

132 requirements = getCertRequirements(Certification.BONNER) 

133 bonnerCohorts = getBonnerCohorts(limit=5) 

134 return render_template(f"/admin/{template.templateFile}", 

135 template = template, 

136 eventData = eventData, 

137 futureTerms = futureTerms, 

138 requirements = requirements, 

139 bonnerCohorts = bonnerCohorts, 

140 isProgramManager = isProgramManager) 

141 

142 

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

144def rsvpLogDisplay(eventId): 

145 event = Event.get_by_id(eventId) 

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

147 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc()) 

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

149 event = event, 

150 allLogs = allLogs) 

151 else: 

152 abort(403) 

153 

154 

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

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

157def eventDisplay(eventId): 

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

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

160 viewer = g.current_user 

161 event = Event.get_by_id(eventId) 

162 addEventView(viewer,event) 

163 # Validate given URL 

164 try: 

165 event = Event.get_by_id(eventId) 

166 except DoesNotExist as e: 

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

168 abort(404) 

169 

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

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

172 abort(403) 

173 

174 eventData = model_to_dict(event, recurse=False) 

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

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

177 

178 image = None 

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

180 for attachment in associatedAttachments: 

181 for extension in picurestype: 

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

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

184 if image: 

185 break 

186 

187 

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

189 eventData = request.form.copy() 

190 try: 

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

192 

193 except Exception as e: 

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

195 savedEvents = False 

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

197 

198 

199 if savedEvents: 

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

201 for year in rsvpcohorts: 

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

203 

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

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

206 else: 

207 flash(validationErrorMessage, 'warning') 

208 

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

210 preprocessEventData(eventData) 

211 eventData['program'] = event.program 

212 futureTerms = selectSurroundingTerms(g.current_term) 

213 userHasRSVPed = checkUserRsvp(g.current_user, event) 

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

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

216 requirements, bonnerCohorts = [], [] 

217 

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

219 requirements = getCertRequirements(Certification.BONNER) 

220 bonnerCohorts = getBonnerCohorts(limit=5) 

221 

222 rule = request.url_rule 

223 

224 # Event Edit 

225 if 'edit' in rule.rule: 

226 return render_template("admin/createEvent.html", 

227 eventData = eventData, 

228 futureTerms=futureTerms, 

229 event = event, 

230 requirements = requirements, 

231 bonnerCohorts = bonnerCohorts, 

232 userHasRSVPed = userHasRSVPed, 

233 isProgramManager = isProgramManager, 

234 filepaths = filepaths) 

235 # Event View 

236 else: 

237 # get text representations of dates 

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

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

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

241 

242 # Identify the next event in a recurring series 

243 if event.recurringId: 

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

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

246 .order_by(Event.startDate)) 

247 eventIndex = eventSeriesList.index(event) 

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

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

250 

251 currentEventRsvpAmount = getEventRsvpCount(event.id) 

252 

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

254 

255 return render_template("eventView.html", 

256 eventData = eventData, 

257 event = event, 

258 userHasRSVPed = userHasRSVPed, 

259 programTrainings = userParticipatedTrainingEvents, 

260 currentEventRsvpAmount = currentEventRsvpAmount, 

261 isProgramManager = isProgramManager, 

262 filepaths = filepaths, 

263 image = image, 

264 pageViewsCount= pageViewsCount) 

265 

266 

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

268def cancelRoute(eventId): 

269 if g.current_user.isAdmin: 

270 try: 

271 cancelEvent(eventId) 

272 return redirect(request.referrer) 

273 

274 except Exception as e: 

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

276 return "", 500 

277 

278 else: 

279 abort(403) 

280 

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

282def deleteRoute(eventId): 

283 try: 

284 deleteEvent(eventId) 

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

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

287 

288 except Exception as e: 

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

290 return "", 500 

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

292def deleteEventAndAllFollowingRoute(eventId): 

293 try: 

294 deleteEventAndAllFollowing(eventId) 

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

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

297 

298 except Exception as e: 

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

300 return "", 500 

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

302def deleteAllRecurringEventsRoute(eventId): 

303 try: 

304 deleteAllRecurringEvents(eventId) 

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

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

307 

308 except Exception as e: 

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

310 return "", 500 

311 

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

313def addRecurringEvents(): 

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

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

316 

317 

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

319def userProfile(): 

320 volunteerName= request.form.copy() 

321 if volunteerName['searchStudentsInput']: 

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

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

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

325 else: 

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

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

328 

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

330def studentSearchPage(): 

331 if g.current_user.isAdmin: 

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

333 abort(403) 

334 

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

336def addParticipants(): 

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

338 

339 return render_template('addParticipants.html', 

340 title="Add Participants") 

341 

342@admin_bp.route('/adminLogs', methods = ['GET', 'POST']) 

343def adminLogs(): 

344 if g.current_user.isCeltsAdmin: 

345 allLogs = AdminLog.select(AdminLog, User).join(User).order_by(AdminLog.createdOn.desc()) 

346 return render_template("/admin/adminLogs.html", 

347 allLogs = allLogs) 

348 else: 

349 abort(403) 

350 

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

352def deleteEventFile(): 

353 fileData= request.form 

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

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

356 return "" 

357 

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

359def addCourseFile(): 

360 fileData = request.files['addCourseParticipants'] 

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

362 fileData.save(filePath) 

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

364 os.remove(filePath) 

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

366 

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

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

369def manageServiceLearningCourses(term=None): 

370 """ 

371 The SLC management page for admins 

372 """ 

373 if not g.current_user.isCeltsAdmin: 

374 abort(403) 

375 

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

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

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

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

380 

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

382 

383 setRedirectTarget(request.full_path) 

384 

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

386 courseInstructors = getInstructorCourses(), 

387 unapprovedCourses = unapprovedCourses(manageTerm), 

388 approvedCourses = approvedCourses(manageTerm), 

389 terms = selectSurroundingTerms(g.current_term), 

390 term = manageTerm, 

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

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

393 ) 

394 

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

396def removeFromSession(): 

397 try: 

398 session.pop('cpPreview') 

399 except KeyError: 

400 pass 

401 

402 return "" 

403 

404@admin_bp.route("/manageBonner") 

405def manageBonner(): 

406 if not g.current_user.isCeltsAdmin: 

407 abort(403) 

408 

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

410 cohorts=getBonnerCohorts(), 

411 events=getBonnerEvents(g.current_term), 

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

413 

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

415def updatecohort(year, method, username): 

416 if not g.current_user.isCeltsAdmin: 

417 abort(403) 

418 

419 try: 

420 user = User.get_by_id(username) 

421 except: 

422 abort(500) 

423 

424 if method == "add": 

425 try: 

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

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

428 except IntegrityError as e: 

429 # if they already exist, ignore the error 

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

431 pass 

432 

433 elif method == "remove": 

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

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

436 else: 

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

438 abort(500) 

439 

440 return "" 

441 

442@admin_bp.route("/bonnerxls") 

443def bonnerxls(): 

444 if not g.current_user.isCeltsAdmin: 

445 abort(403) 

446 

447 newfile = makeBonnerXls() 

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

449 

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

451def saveRequirements(certid): 

452 if not g.current_user.isCeltsAdmin: 

453 abort(403) 

454 

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

456 

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

458 

459 

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

461def displayEventFile(): 

462 fileData= request.form 

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

464 eventfile.changeDisplay(fileData['id']) 

465 return ""