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
« 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
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
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
41from app.controllers.admin import admin_bp
42from app.logic.spreadsheet import createSpreadsheet
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)
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)
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)
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']))
68 return redirect(request.referrer)
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)
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)
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"))
101# # Get the data from the form or from the template
102# eventData = template.templateData
104# eventData['program'] = program
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
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()
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)
136# except Exception as e:
137# print("Failed saving multi event", e)
139# else:
140# try:
141# savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
142# except Exception as e:
143# print("Failed saving regular event", e)
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)
152# noun = ((eventData.get('isRecurring') or eventData.get('isMultipleOffering')) and "Events" or "Event") # pluralize
153# flash(f"{noun} successfully created!", 'success')
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')}.")
160# elif len(savedEventsList) >= 1 and eventData.get('isMultipleOffering'):
161# modifiedSavedEvents = [item for sublist in savedEventsList for item in sublist]
163# event_dates = [event_data[0].startDate.strftime('%m/%d/%Y') for event_data in savedEventsList]
165# event_list = ', '.join(f"<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>" for event in modifiedSavedEvents)
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}'
174# createActivityLog(f"Created events {event_list} for {program.programName}, with start dates of {event_dates}.")
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')}.")
181# return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
182# else:
183# flash(validationErrorMessage, 'warning')
185# # make sure our data is the same regardless of GET or POST
186# preprocessEventData(eventData)
187# isProgramManager = g.current_user.isProgramManagerFor(programid)
189# futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
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)
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)
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"))
221 # Get the data from the form or from the template
222 eventData = template.templateData
224 eventData['program'] = program
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
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())
241 if eventData.get('isSeries') and eventData.get('isRepeating') is None:
242 seriesId = calculateNewSeriesId()
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)
258 except Exception as e:
259 print("Failed saving multi event", e)
261 else:
262 try:
263 savedEvents, validationErrorMessage = NEWattemptSaveEvent(eventData, getFilesFromRequest(request))
264 except Exception as e:
265 print("Failed saving regular event", e)
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)
274 noun = (eventData.get('isSeries') or eventData.get('isRepeating') and "Events" or "Event") # pluralize
275 flash(f"{noun} successfully created!", 'success')
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]
284 event_dates = [event_data[0].startDate.strftime('%m/%d/%Y') for event_data in savedEventsList]
286 event_list = ', '.join(f"<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>" for event in modifiedSavedEvents)
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}'
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')}.")
301 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
302 else:
303 flash(validationErrorMessage, 'warning')
305 # make sure our data is the same regardless of GET or POST
306 NEWpreprocessEventData(eventData)
307 isProgramManager = g.current_user.isProgramManagerFor(programid)
309 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
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)
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)
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))
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))
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))
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))
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))
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)
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)
404# eventData = model_to_dict(event, recurse=False)
405# associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
406# filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
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
418# if request.method == "POST": # Attempt to save form
419# eventData = request.form.copy()
420# try:
421# savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
423# except Exception as e:
424# print("Error saving event:", e)
425# savedEvents = False
426# validationErrorMessage = "Unknown Error Saving Event. Please try again"
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)
435# flash("Event successfully updated!", "success")
436# return redirect(url_for("admin.eventDisplay", eventId = event.id))
437# else:
438# flash(validationErrorMessage, 'warning')
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 = [], []
449# if eventData['program'] and eventData['program'].isBonnerScholars:
450# requirements = getCertRequirements(Certification.BONNER)
451# bonnerCohorts = getBonnerCohorts(limit=5)
453# rule = request.url_rule
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)
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]
484# currentEventRsvpAmount = getEventRsvpCount(event.id)
486# userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
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# )
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)
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)
519 eventData = model_to_dict(event, recurse=False)
520 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
521 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
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
533 if request.method == "POST": # Attempt to save form
534 eventData = request.form.copy()
535 try:
536 savedEvents, validationErrorMessage = NEWattemptSaveEvent(eventData, getFilesFromRequest(request))
538 except Exception as e:
539 print("Error saving event:", e)
540 savedEvents = False
541 validationErrorMessage = "Unknown Error Saving Event. Please try again"
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)
550 flash("Event successfully updated!", "success")
551 return redirect(url_for("admin.eventDisplay", eventId = event.id))
552 else:
553 flash(validationErrorMessage, 'warning')
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 = [], []
564 if eventData['program'] and eventData['program'].isBonnerScholars:
565 requirements = getCertRequirements(Certification.BONNER)
566 bonnerCohorts = getBonnerCohorts(limit=5)
568 rule = request.url_rule
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)
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]
599 currentEventRsvpAmount = getEventRsvpCount(event.id)
601 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
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 )
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)
625 except Exception as e:
626 print('Error while canceling event:', e)
627 return "", 500
629 else:
630 abort(403)
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
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
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))
685# except Exception as e:
686# print('Error while canceling event:', e)
687# return "", 500
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))
698 except Exception as e:
699 print('Error while canceling event:', e)
700 return "", 500
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))
709# except Exception as e:
710# print('Error while canceling event:', e)
711# return "", 500
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))
721 except Exception as e:
722 print('Error while canceling event:', e)
723 return "", 500
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))
732# except Exception as e:
733# print('Error while canceling event:', e)
734# return "", 500
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))
744 except Exception as e:
745 print('Error while canceling event:', e)
746 return "", 500
748# @admin_bp.route('/makeRecurringEvents', methods=['POST'])
749# def addRecurringEvents():
750# recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
751# return json.dumps(recurringEvents, default=str)
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)
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'))
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)
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)
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 ""
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"))
801@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
802@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
803def manageServiceLearningCourses(term=None):
805 """
806 The SLC management page for admins
807 """
808 if not g.current_user.isCeltsAdmin:
809 abort(403)
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'))
816 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
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")
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 )
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 )
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}
861@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
862def removeFromSession():
863 try:
864 session.pop('cpPreview')
865 except KeyError:
866 pass
868 return ""
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)
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
889 courseData = model_to_dict(targetCourse, recurse=False)
890 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
891 courseData['hoursEarned'] = serviceHours
893 return jsonify(courseData)
895 except DoesNotExist:
896 flash("Course not found")
897 return jsonify({"error": "Course not found"}), 404
899 if request.method == 'POST':
900 # Update course information in the database
901 courseData = request.form.copy()
902 editImportedCourses(courseData)
903 session['alterCourseId'] = courseID
905 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
908@admin_bp.route("/manageBonner")
909def manageBonner():
910 if not g.current_user.isCeltsAdmin:
911 abort(403)
913 return render_template("/admin/bonnerManagement.html",
914 cohorts=getBonnerCohorts(),
915 events=getBonnerEvents(g.current_term),
916 requirements=getCertRequirements(certification=Certification.BONNER))
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)
923 try:
924 user = User.get_by_id(username)
925 except:
926 abort(500)
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
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)
944 return ""
946@admin_bp.route("/bonnerxls")
947def bonnerxls():
948 if not g.current_user.isCeltsAdmin:
949 abort(403)
951 newfile = makeBonnerXls()
952 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
954@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
955def saveRequirements(certid):
956 if not g.current_user.isCeltsAdmin:
957 abort(403)
959 newRequirements = updateCertRequirements(certid, request.get_json())
961 return jsonify([requirement.id for requirement in newRequirements])
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 ""