Coverage for app/controllers/admin/routes.py: 23%
438 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-25 18:59 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-25 18:59 +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, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent, calculateNewMultipleOfferingId
34from app.logic.events import cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent, calculateNewMultipleOfferingId
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/template_selector.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'])
85def 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)
204@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
205def rsvpLogDisplay(eventId):
206 event = Event.get_by_id(eventId)
207 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
208 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
209 return render_template("/events/rsvpLog.html",
210 event = event,
211 allLogs = allLogs)
212 else:
213 abort(403)
215@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
216def renewEvent(eventId):
217 try:
218 formData = request.form
219 try:
220 assert formData['timeStart'] < formData['timeEnd']
221 except AssertionError:
222 flash("End time must be after start time", 'warning')
223 return redirect(url_for('admin.eventDisplay', eventId = eventId))
225 try:
226 if formData.get('dateEnd'):
227 assert formData['dateStart'] < formData['dateEnd']
228 except AssertionError:
229 flash("End date must be after start date", 'warning')
230 return redirect(url_for('admin.eventDisplay', eventId = eventId))
233 priorEvent = model_to_dict(Event.get_by_id(eventId))
234 newEventDict = priorEvent.copy()
235 newEventDict.pop('id')
236 newEventDict.update({
237 'program': int(priorEvent['program']['id']),
238 'term': int(priorEvent['term']['id']),
239 'timeStart': formData['timeStart'],
240 'timeEnd': formData['timeEnd'],
241 'location': formData['location'],
242 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
243 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
244 'isRecurring': bool(priorEvent['recurringId']),
245 'isMultipleOffering': bool(priorEvent['multipleOffeirngId']),
246 })
247 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
248 if message:
249 flash(message, "danger")
250 return redirect(url_for('admin.eventDisplay', eventId = eventId))
252 copyRsvpToNewEvent(priorEvent, newEvent[0])
253 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
254 flash("Event successfully renewed.", "success")
255 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
258 except Exception as e:
259 print("Error while trying to renew event:", e)
260 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
261 return redirect(url_for('admin.eventDisplay', eventId = eventId))
265@admin_bp.route('/event/<eventId>/view', methods=['GET'])
266@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
267def eventDisplay(eventId):
268 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
269 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
270 viewer = g.current_user
271 event = Event.get_by_id(eventId)
272 addEventView(viewer,event)
273 # Validate given URL
274 try:
275 event = Event.get_by_id(eventId)
276 except DoesNotExist as e:
277 print(f"Unknown event: {eventId}")
278 abort(404)
280 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
281 if 'edit' in request.url_rule.rule and notPermitted:
282 abort(403)
284 eventData = model_to_dict(event, recurse=False)
285 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
286 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
288 image = None
289 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
290 for attachment in associatedAttachments:
291 for extension in picurestype:
292 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
293 image = filepaths[attachment.fileName][0]
294 if image:
295 break
298 if request.method == "POST": # Attempt to save form
299 eventData = request.form.copy()
300 try:
301 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
303 except Exception as e:
304 print("Error saving event:", e)
305 savedEvents = False
306 validationErrorMessage = "Unknown Error Saving Event. Please try again"
309 if savedEvents:
310 rsvpcohorts = request.form.getlist("cohorts[]")
311 for year in rsvpcohorts:
312 rsvpForBonnerCohort(int(year), event.id)
313 addBonnerCohortToRsvpLog(int(year), event.id)
315 flash("Event successfully updated!", "success")
316 return redirect(url_for("admin.eventDisplay", eventId = event.id))
317 else:
318 flash(validationErrorMessage, 'warning')
320 # make sure our data is the same regardless of GET and POST
321 preprocessEventData(eventData)
322 eventData['program'] = event.program
323 futureTerms = selectSurroundingTerms(g.current_term)
324 userHasRSVPed = checkUserRsvp(g.current_user, event)
325 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
326 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
327 requirements, bonnerCohorts = [], []
329 if eventData['program'] and eventData['program'].isBonnerScholars:
330 requirements = getCertRequirements(Certification.BONNER)
331 bonnerCohorts = getBonnerCohorts(limit=5)
333 rule = request.url_rule
335 # Event Edit
336 if 'edit' in rule.rule:
337 return render_template("admin/createEvent.html",
338 eventData = eventData,
339 futureTerms=futureTerms,
340 event = event,
341 requirements = requirements,
342 bonnerCohorts = bonnerCohorts,
343 userHasRSVPed = userHasRSVPed,
344 isProgramManager = isProgramManager,
345 filepaths = filepaths)
346 # Event View
347 else:
348 # get text representations of dates for html
349 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
350 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
351 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
352 eventCountdown = getCountdownToEvent(event)
355 # Identify the next event in a recurring series
356 if event.recurringId:
357 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId)
358 .where((Event.isCanceled == False) | (Event.id == event.id))
359 .order_by(Event.startDate))
360 eventIndex = eventSeriesList.index(event)
361 if len(eventSeriesList) != (eventIndex + 1):
362 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1]
364 currentEventRsvpAmount = getEventRsvpCount(event.id)
366 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
368 return render_template("eventView.html",
369 eventData=eventData,
370 event=event,
371 userHasRSVPed=userHasRSVPed,
372 programTrainings=userParticipatedTrainingEvents,
373 currentEventRsvpAmount=currentEventRsvpAmount,
374 isProgramManager=isProgramManager,
375 filepaths=filepaths,
376 image=image,
377 pageViewsCount=pageViewsCount,
378 eventCountdown=eventCountdown
379 )
383@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
384def cancelRoute(eventId):
385 if g.current_user.isAdmin:
386 try:
387 cancelEvent(eventId)
388 return redirect(request.referrer)
390 except Exception as e:
391 print('Error while canceling event:', e)
392 return "", 500
394 else:
395 abort(403)
397@admin_bp.route('/event/undo', methods=['GET'])
398def undoEvent():
399 try:
400 events = session['lastDeletedEvent']
401 for eventId in events:
402 Event.update({Event.deletionDate: None, Event.deletedBy: None}).where(Event.id == eventId).execute()
403 event = Event.get_or_none(Event.id == eventId)
404 recurringEvents = list(Event.select().where((Event.recurringId==event.recurringId) & (Event.deletionDate == None)).order_by(Event.id))
405 if event.recurringId is not None:
406 nameCounter = 1
407 for recurringEvent in recurringEvents:
408 newEventNameList = recurringEvent.name.split()
409 newEventNameList[-1] = f"{nameCounter}"
410 newEventNameList = " ".join(newEventNameList)
411 Event.update({Event.name: newEventNameList}).where(Event.id==recurringEvent.id).execute()
412 nameCounter += 1
413 flash("Deletion successfully undone.", "success")
414 return redirect('/eventsList/' + str(g.current_term))
415 except Exception as e:
416 print('Error while canceling event:', e)
417 return "", 500
419@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
420def deleteRoute(eventId):
421 try:
422 deleteEvent(eventId)
423 session['lastDeletedEvent'] = [eventId]
424 flash("Event successfully deleted.", "success")
425 return redirect(url_for("main.events", selectedTerm=g.current_term))
427 except Exception as e:
428 print('Error while canceling event:', e)
429 return "", 500
431@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
432def deleteEventAndAllFollowingRoute(eventId):
433 try:
434 session["lastDeletedEvent"] = deleteEventAndAllFollowing(eventId)
435 flash("Events successfully deleted.", "success")
436 return redirect(url_for("main.events", selectedTerm=g.current_term))
438 except Exception as e:
439 print('Error while canceling event:', e)
440 return "", 500
442@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
443def deleteAllRecurringEventsRoute(eventId):
444 try:
445 session["lastDeletedEvent"] = deleteAllRecurringEvents(eventId)
446 flash("Events successfully deleted.", "success")
447 return redirect(url_for("main.events", selectedTerm=g.current_term))
449 except Exception as e:
450 print('Error while canceling event:', e)
451 return "", 500
453@admin_bp.route('/makeRecurringEvents', methods=['POST'])
454def addRecurringEvents():
455 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
456 return json.dumps(recurringEvents, default=str)
459@admin_bp.route('/userProfile', methods=['POST'])
460def userProfile():
461 volunteerName= request.form.copy()
462 if volunteerName['searchStudentsInput']:
463 username = volunteerName['searchStudentsInput'].strip("()")
464 user=username.split('(')[-1]
465 return redirect(url_for('main.viewUsersProfile', username=user))
466 else:
467 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
468 return redirect(url_for('admin.studentSearchPage'))
470@admin_bp.route('/search_student', methods=['GET'])
471def studentSearchPage():
472 if g.current_user.isAdmin:
473 return render_template("/admin/searchStudentPage.html")
474 abort(403)
476@admin_bp.route('/addParticipants', methods = ['GET'])
477def addParticipants():
478 '''Renders the page, will be removed once merged with full page'''
480 return render_template('addParticipants.html',
481 title="Add Participants")
483@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
484def activityLogs():
485 if g.current_user.isCeltsAdmin:
486 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
487 return render_template("/admin/activityLogs.html",
488 allLogs = allLogs)
489 else:
490 abort(403)
492@admin_bp.route("/deleteEventFile", methods=["POST"])
493def deleteEventFile():
494 fileData= request.form
495 eventfile=FileHandler(eventId=fileData["databaseId"])
496 eventfile.deleteFile(fileData["fileId"])
497 return ""
499@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
500def addCourseFile():
501 fileData = request.files['addCourseParticipants']
502 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
503 fileData.save(filePath)
504 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
505 os.remove(filePath)
506 return redirect(url_for("admin.manageServiceLearningCourses"))
508@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
509@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
510def manageServiceLearningCourses(term=None):
512 """
513 The SLC management page for admins
514 """
515 if not g.current_user.isCeltsAdmin:
516 abort(403)
518 if request.method == 'POST' and "submitParticipant" in request.form:
519 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
520 flash('Courses and participants saved successfully!', 'success')
521 return redirect(url_for('admin.manageServiceLearningCourses'))
523 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
525 setRedirectTarget(request.full_path)
526 # retrieve and store the courseID of the imported course from a session variable if it exists.
527 # This allows us to export the courseID in the html and use it.
528 courseID = session.get("alterCourseId")
530 if courseID:
531 # delete courseID from the session if it was retrieved, for storage purposes.
532 session.pop("alterCourseId")
533 return render_template('/admin/manageServiceLearningFaculty.html',
534 courseInstructors = getInstructorCourses(),
535 unapprovedCourses = unapprovedCourses(manageTerm),
536 approvedCourses = approvedCourses(manageTerm),
537 importedCourses = getImportedCourses(manageTerm),
538 terms = selectSurroundingTerms(g.current_term),
539 term = manageTerm,
540 cpPreview = session.get('cpPreview', {}),
541 cpPreviewErrors = session.get('cpErrors', []),
542 courseID = courseID
543 )
545 return render_template('/admin/manageServiceLearningFaculty.html',
546 courseInstructors = getInstructorCourses(),
547 unapprovedCourses = unapprovedCourses(manageTerm),
548 approvedCourses = approvedCourses(manageTerm),
549 importedCourses = getImportedCourses(manageTerm),
550 terms = selectSurroundingTerms(g.current_term),
551 term = manageTerm,
552 cpPreview= session.get('cpPreview',{}),
553 cpPreviewErrors = session.get('cpErrors',[])
554 )
556@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
557def getSidebarInformation() -> str:
558 """
559 Get the count of unapproved courses and students interested in the minor for the current term
560 to display in the admin sidebar. It must be returned as a string to be received by the
561 ajax request.
562 """
563 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
564 interestedStudentsCount: int = len(getMinorInterest())
565 return {"unapprovedCoursesCount": unapprovedCoursesCount,
566 "interestedStudentsCount": interestedStudentsCount}
568@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
569def removeFromSession():
570 try:
571 session.pop('cpPreview')
572 except KeyError:
573 pass
575 return ""
577@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
578def alterImportedCourse(courseID):
579 """
580 This route handles a GET and a POST request for the purpose of imported courses.
581 The GET request provides preexisting information of an imported course in a modal.
582 The POST request updates a specific imported course (course name, course abbreviation,
583 hours earned on completion, list of instructors) in the database with new information
584 coming from the imported courses modal.
585 """
586 if request.method == 'GET':
587 try:
588 targetCourse = Course.get_by_id(courseID)
589 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
591 try:
592 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
593 except IndexError: # If a course has no participant, IndexError will be raised
594 serviceHours = 20
596 courseData = model_to_dict(targetCourse, recurse=False)
597 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
598 courseData['hoursEarned'] = serviceHours
600 return jsonify(courseData)
602 except DoesNotExist:
603 flash("Course not found")
604 return jsonify({"error": "Course not found"}), 404
606 if request.method == 'POST':
607 # Update course information in the database
608 courseData = request.form.copy()
609 editImportedCourses(courseData)
610 session['alterCourseId'] = courseID
612 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
615@admin_bp.route("/manageBonner")
616def manageBonner():
617 if not g.current_user.isCeltsAdmin:
618 abort(403)
620 return render_template("/admin/bonnerManagement.html",
621 cohorts=getBonnerCohorts(),
622 events=getBonnerEvents(g.current_term),
623 requirements=getCertRequirements(certification=Certification.BONNER))
625@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
626def updatecohort(year, method, username):
627 if not g.current_user.isCeltsAdmin:
628 abort(403)
630 try:
631 user = User.get_by_id(username)
632 except:
633 abort(500)
635 if method == "add":
636 try:
637 BonnerCohort.create(year=year, user=user)
638 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
639 except IntegrityError as e:
640 # if they already exist, ignore the error
641 flash(f'Error: {user.fullName} already added.', "danger")
642 pass
644 elif method == "remove":
645 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
646 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
647 else:
648 flash(f"Error: {user.fullName} can't be added.", "danger")
649 abort(500)
651 return ""
653@admin_bp.route("/bonnerxls")
654def bonnerxls():
655 if not g.current_user.isCeltsAdmin:
656 abort(403)
658 newfile = makeBonnerXls()
659 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
661@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
662def saveRequirements(certid):
663 if not g.current_user.isCeltsAdmin:
664 abort(403)
666 newRequirements = updateCertRequirements(certid, request.get_json())
668 return jsonify([requirement.id for requirement in newRequirements])
671@admin_bp.route("/displayEventFile", methods=["POST"])
672def displayEventFile():
673 fileData = request.form
674 eventfile = FileHandler(eventId=fileData["id"])
675 isChecked = fileData.get('checked') == 'true'
676 eventfile.changeDisplay(fileData['id'], isChecked)
677 return ""