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
« 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
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.manageSLFaculty import getInstructorCourses
34from app.logic.courseManagement import unapprovedCourses, approvedCourses
35from app.logic.serviceLearningCoursesData import parseUploadedFile, saveCourseParticipantsToDatabase
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)
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']))
48 return redirect(request.referrer)
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)
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)
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"))
80 # Get the data from the form or from the template
81 eventData = template.templateData
83 eventData['program'] = program
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
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))
101 except Exception as e:
102 print("Error saving event:", e)
103 savedEvents = False
104 validationErrorMessage = "Unknown Error Saving Event. Please try again"
106 if savedEvents:
107 rsvpcohorts = request.form.getlist("cohorts[]")
108 for year in rsvpcohorts:
109 rsvpForBonnerCohort(int(year), savedEvents[0].id)
111 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
112 flash(f"{noun} successfully created!", 'success')
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')}.")
122 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
123 else:
124 flash(validationErrorMessage, 'warning')
126 # make sure our data is the same regardless of GET or POST
127 preprocessEventData(eventData)
128 isProgramManager = g.current_user.isProgramManagerFor(programid)
130 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
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)
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)
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)
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)
176 eventData = model_to_dict(event, recurse=False)
177 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
178 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
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
190 if request.method == "POST": # Attempt to save form
191 eventData = request.form.copy()
192 try:
193 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
195 except Exception as e:
196 print("Error saving event:", e)
197 savedEvents = False
198 validationErrorMessage = "Unknown Error Saving Event. Please try again"
201 if savedEvents:
202 rsvpcohorts = request.form.getlist("cohorts[]")
203 for year in rsvpcohorts:
204 rsvpForBonnerCohort(int(year), event.id)
206 flash("Event successfully updated!", "success")
207 return redirect(url_for("admin.eventDisplay", eventId = event.id))
208 else:
209 flash(validationErrorMessage, 'warning')
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 = [], []
220 if eventData['program'] and eventData['program'].isBonnerScholars:
221 requirements = getCertRequirements(Certification.BONNER)
222 bonnerCohorts = getBonnerCohorts(limit=5)
224 rule = request.url_rule
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")
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]
253 currentEventRsvpAmount = getEventRsvpCount(event.id)
255 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
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)
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)
276 except Exception as e:
277 print('Error while canceling event:', e)
278 return "", 500
280 else:
281 abort(403)
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))
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))
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))
310 except Exception as e:
311 print('Error while canceling event:', e)
312 return "", 500
314@admin_bp.route('/makeRecurringEvents', methods=['POST'])
315def addRecurringEvents():
316 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
317 return json.dumps(recurringEvents, default=str)
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'))
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)
337@admin_bp.route('/addParticipants', methods = ['GET'])
338def addParticipants():
339 '''Renders the page, will be removed once merged with full page'''
341 return render_template('addParticipants.html',
342 title="Add Participants")
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)
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 ""
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"))
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)
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'))
383 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
385 setRedirectTarget(request.full_path)
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 )
397@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
398def removeFromSession():
399 try:
400 session.pop('cpPreview')
401 except KeyError:
402 pass
404 return ""
406@admin_bp.route("/manageBonner")
407def manageBonner():
408 if not g.current_user.isCeltsAdmin:
409 abort(403)
411 return render_template("/admin/bonnerManagement.html",
412 cohorts=getBonnerCohorts(),
413 events=getBonnerEvents(g.current_term),
414 requirements = getCertRequirements(certification=Certification.BONNER))
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)
421 try:
422 user = User.get_by_id(username)
423 except:
424 abort(500)
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
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)
442 return ""
444@admin_bp.route("/bonnerxls")
445def bonnerxls():
446 if not g.current_user.isCeltsAdmin:
447 abort(403)
449 newfile = makeBonnerXls()
450 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
452@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
453def saveRequirements(certid):
454 if not g.current_user.isCeltsAdmin:
455 abort(403)
457 newRequirements = updateCertRequirements(certid, request.get_json())
459 return jsonify([requirement.id for requirement in newRequirements])
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 ""