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

315 statements  

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

34from app.logic.courseManagement import unapprovedCourses, approvedCourses 

35from app.logic.serviceLearningCoursesData import parseUploadedFile, saveCourseParticipantsToDatabase 

36 

37 

38 

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

40def switchUser(): 

41 if app.env == "production": 

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

43 abort(403) 

44 

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

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

47 

48 return redirect(request.referrer) 

49 

50 

51@admin_bp.route('/eventTemplates') 

52def templateSelect(): 

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

54 allprograms = getAllowedPrograms(g.current_user) 

55 visibleTemplates = getAllowedTemplates(g.current_user) 

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

57 programs=allprograms, 

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

59 templates=visibleTemplates) 

60 else: 

61 abort(403) 

62 

63 

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

65def createEvent(templateid, programid): 

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

67 abort(403) 

68 

69 # Validate given URL 

70 program = None 

71 try: 

72 template = EventTemplate.get_by_id(templateid) 

73 if programid: 

74 program = Program.get_by_id(programid) 

75 except DoesNotExist as e: 

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

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

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

79 

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

81 eventData = template.templateData 

82 

83 eventData['program'] = program 

84 

85 if request.method == "GET": 

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

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

88 if program: 

89 eventData['location'] = program.defaultLocation 

90 if program.contactName: 

91 eventData['contactName'] = program.contactName 

92 if program.contactEmail: 

93 eventData['contactEmail'] = program.contactEmail 

94 

95 # Try to save the form 

96 if request.method == "POST": 

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

98 try: 

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

100 

101 except Exception as e: 

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

103 savedEvents = False 

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

105 

106 if savedEvents: 

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

108 for year in rsvpcohorts: 

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

110 

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

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

113 

114 if program: 

115 if len(savedEvents) > 1: 

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

117 else: 

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

119 else: 

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

121 

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

123 else: 

124 flash(validationErrorMessage, 'warning') 

125 

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

127 preprocessEventData(eventData) 

128 isProgramManager = g.current_user.isProgramManagerFor(programid) 

129 

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

131 

132 requirements, bonnerCohorts = [], [] 

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

134 requirements = getCertRequirements(Certification.BONNER) 

135 bonnerCohorts = getBonnerCohorts(limit=5) 

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

137 template = template, 

138 eventData = eventData, 

139 futureTerms = futureTerms, 

140 requirements = requirements, 

141 bonnerCohorts = bonnerCohorts, 

142 isProgramManager = isProgramManager) 

143 

144 

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

146def rsvpLogDisplay(eventId): 

147 event = Event.get_by_id(eventId) 

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

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

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

151 event = event, 

152 allLogs = allLogs) 

153 else: 

154 abort(403) 

155 

156 

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

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

159def eventDisplay(eventId): 

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

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

162 viewer = g.current_user 

163 event = Event.get_by_id(eventId) 

164 addEventView(viewer,event) 

165 # Validate given URL 

166 try: 

167 event = Event.get_by_id(eventId) 

168 except DoesNotExist as e: 

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

170 abort(404) 

171 

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

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

174 abort(403) 

175 

176 eventData = model_to_dict(event, recurse=False) 

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

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

179 

180 image = None 

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

182 for attachment in associatedAttachments: 

183 for extension in picurestype: 

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

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

186 if image: 

187 break 

188 

189 

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

191 eventData = request.form.copy() 

192 try: 

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

194 

195 except Exception as e: 

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

197 savedEvents = False 

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

199 

200 

201 if savedEvents: 

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

203 for year in rsvpcohorts: 

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

205 

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

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

208 else: 

209 flash(validationErrorMessage, 'warning') 

210 

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

212 preprocessEventData(eventData) 

213 eventData['program'] = event.program 

214 futureTerms = selectSurroundingTerms(g.current_term) 

215 userHasRSVPed = checkUserRsvp(g.current_user, event) 

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

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

218 requirements, bonnerCohorts = [], [] 

219 

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

221 requirements = getCertRequirements(Certification.BONNER) 

222 bonnerCohorts = getBonnerCohorts(limit=5) 

223 

224 rule = request.url_rule 

225 

226 # Event Edit 

227 if 'edit' in rule.rule: 

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

229 eventData = eventData, 

230 futureTerms=futureTerms, 

231 event = event, 

232 requirements = requirements, 

233 bonnerCohorts = bonnerCohorts, 

234 userHasRSVPed = userHasRSVPed, 

235 isProgramManager = isProgramManager, 

236 filepaths = filepaths) 

237 # Event View 

238 else: 

239 # get text representations of dates 

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

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

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

243 

244 # Identify the next event in a recurring series 

245 if event.recurringId: 

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

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

248 .order_by(Event.startDate)) 

249 eventIndex = eventSeriesList.index(event) 

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

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

252 

253 currentEventRsvpAmount = getEventRsvpCount(event.id) 

254 

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

256 

257 return render_template("eventView.html", 

258 eventData = eventData, 

259 event = event, 

260 userHasRSVPed = userHasRSVPed, 

261 programTrainings = userParticipatedTrainingEvents, 

262 currentEventRsvpAmount = currentEventRsvpAmount, 

263 isProgramManager = isProgramManager, 

264 filepaths = filepaths, 

265 image = image, 

266 pageViewsCount= pageViewsCount) 

267 

268 

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

270def cancelRoute(eventId): 

271 if g.current_user.isAdmin: 

272 try: 

273 cancelEvent(eventId) 

274 return redirect(request.referrer) 

275 

276 except Exception as e: 

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

278 return "", 500 

279 

280 else: 

281 abort(403) 

282 

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

284def deleteRoute(eventId): 

285 try: 

286 deleteEvent(eventId) 

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

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

289 

290 except Exception as e: 

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

292 return "", 500 

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

294def deleteEventAndAllFollowingRoute(eventId): 

295 try: 

296 deleteEventAndAllFollowing(eventId) 

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

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

299 

300 except Exception as e: 

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

302 return "", 500 

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

304def deleteAllRecurringEventsRoute(eventId): 

305 try: 

306 deleteAllRecurringEvents(eventId) 

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

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

309 

310 except Exception as e: 

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

312 return "", 500 

313 

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

315def addRecurringEvents(): 

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

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

318 

319 

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

321def userProfile(): 

322 volunteerName= request.form.copy() 

323 if volunteerName['searchStudentsInput']: 

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

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

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

327 else: 

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

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

330 

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

332def studentSearchPage(): 

333 if g.current_user.isAdmin: 

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

335 abort(403) 

336 

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

338def addParticipants(): 

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

340 

341 return render_template('addParticipants.html', 

342 title="Add Participants") 

343 

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

345def adminLogs(): 

346 if g.current_user.isCeltsAdmin: 

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

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

349 allLogs = allLogs) 

350 else: 

351 abort(403) 

352 

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

354def deleteEventFile(): 

355 fileData= request.form 

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

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

358 return "" 

359 

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

361def addCourseFile(): 

362 fileData = request.files['addCourseParticipants'] 

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

364 fileData.save(filePath) 

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

366 os.remove(filePath) 

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

368 

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

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

371def manageServiceLearningCourses(term=None): 

372 """ 

373 The SLC management page for admins 

374 """ 

375 if not g.current_user.isCeltsAdmin: 

376 abort(403) 

377 

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

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

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

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

382 

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

384 

385 setRedirectTarget(request.full_path) 

386 

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

388 courseInstructors = getInstructorCourses(), 

389 unapprovedCourses = unapprovedCourses(manageTerm), 

390 approvedCourses = approvedCourses(manageTerm), 

391 terms = selectSurroundingTerms(g.current_term), 

392 term = manageTerm, 

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

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

395 ) 

396 

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

398def removeFromSession(): 

399 try: 

400 session.pop('cpPreview') 

401 except KeyError: 

402 pass 

403 

404 return "" 

405 

406@admin_bp.route("/manageBonner") 

407def manageBonner(): 

408 if not g.current_user.isCeltsAdmin: 

409 abort(403) 

410 

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

412 cohorts=getBonnerCohorts(), 

413 events=getBonnerEvents(g.current_term), 

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

415 

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

417def updatecohort(year, method, username): 

418 if not g.current_user.isCeltsAdmin: 

419 abort(403) 

420 

421 try: 

422 user = User.get_by_id(username) 

423 except: 

424 abort(500) 

425 

426 if method == "add": 

427 try: 

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

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

430 except IntegrityError as e: 

431 # if they already exist, ignore the error 

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

433 pass 

434 

435 elif method == "remove": 

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

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

438 else: 

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

440 abort(500) 

441 

442 return "" 

443 

444@admin_bp.route("/bonnerxls") 

445def bonnerxls(): 

446 if not g.current_user.isCeltsAdmin: 

447 abort(403) 

448 

449 newfile = makeBonnerXls() 

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

451 

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

453def saveRequirements(certid): 

454 if not g.current_user.isCeltsAdmin: 

455 abort(403) 

456 

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

458 

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

460 

461 

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

463def displayEventFile(): 

464 fileData= request.form 

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

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

467 return ""