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
« 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
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
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
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)
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']))
46 return redirect(request.referrer)
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)
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)
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"))
78 # Get the data from the form or from the template
79 eventData = template.templateData
81 eventData['program'] = program
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
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))
99 except Exception as e:
100 print("Error saving event:", e)
101 savedEvents = False
102 validationErrorMessage = "Unknown Error Saving Event. Please try again"
104 if savedEvents:
105 rsvpcohorts = request.form.getlist("cohorts[]")
106 for year in rsvpcohorts:
107 rsvpForBonnerCohort(int(year), savedEvents[0].id)
109 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
110 flash(f"{noun} successfully created!", 'success')
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')}.")
120 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
121 else:
122 flash(validationErrorMessage, 'warning')
124 # make sure our data is the same regardless of GET or POST
125 preprocessEventData(eventData)
126 isProgramManager = g.current_user.isProgramManagerFor(programid)
128 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
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)
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)
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)
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)
174 eventData = model_to_dict(event, recurse=False)
175 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
176 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
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
188 if request.method == "POST": # Attempt to save form
189 eventData = request.form.copy()
190 try:
191 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
193 except Exception as e:
194 print("Error saving event:", e)
195 savedEvents = False
196 validationErrorMessage = "Unknown Error Saving Event. Please try again"
199 if savedEvents:
200 rsvpcohorts = request.form.getlist("cohorts[]")
201 for year in rsvpcohorts:
202 rsvpForBonnerCohort(int(year), event.id)
204 flash("Event successfully updated!", "success")
205 return redirect(url_for("admin.eventDisplay", eventId = event.id))
206 else:
207 flash(validationErrorMessage, 'warning')
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 = [], []
218 if eventData['program'] and eventData['program'].isBonnerScholars:
219 requirements = getCertRequirements(Certification.BONNER)
220 bonnerCohorts = getBonnerCohorts(limit=5)
222 rule = request.url_rule
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")
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]
251 currentEventRsvpAmount = getEventRsvpCount(event.id)
253 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
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)
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)
274 except Exception as e:
275 print('Error while canceling event:', e)
276 return "", 500
278 else:
279 abort(403)
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))
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))
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))
308 except Exception as e:
309 print('Error while canceling event:', e)
310 return "", 500
312@admin_bp.route('/makeRecurringEvents', methods=['POST'])
313def addRecurringEvents():
314 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
315 return json.dumps(recurringEvents, default=str)
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'))
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)
335@admin_bp.route('/addParticipants', methods = ['GET'])
336def addParticipants():
337 '''Renders the page, will be removed once merged with full page'''
339 return render_template('addParticipants.html',
340 title="Add Participants")
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)
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 ""
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"))
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)
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'))
381 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
383 setRedirectTarget(request.full_path)
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 )
395@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
396def removeFromSession():
397 try:
398 session.pop('cpPreview')
399 except KeyError:
400 pass
402 return ""
404@admin_bp.route("/manageBonner")
405def manageBonner():
406 if not g.current_user.isCeltsAdmin:
407 abort(403)
409 return render_template("/admin/bonnerManagement.html",
410 cohorts=getBonnerCohorts(),
411 events=getBonnerEvents(g.current_term),
412 requirements = getCertRequirements(certification=Certification.BONNER))
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)
419 try:
420 user = User.get_by_id(username)
421 except:
422 abort(500)
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
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)
440 return ""
442@admin_bp.route("/bonnerxls")
443def bonnerxls():
444 if not g.current_user.isCeltsAdmin:
445 abort(403)
447 newfile = makeBonnerXls()
448 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
450@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
451def saveRequirements(certid):
452 if not g.current_user.isCeltsAdmin:
453 abort(403)
455 newRequirements = updateCertRequirements(certid, request.get_json())
457 return jsonify([requirement.id for requirement in newRequirements])
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 ""