Coverage for app/controllers/admin/routes.py: 22%
432 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-11-19 23:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-11-19 23:53 +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 attemptSaveMultipleOfferings, cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, getRepeatingEventsData, deleteEventAndAllFollowing, deleteAllEventsInSeries, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent, calculateNewSeriesId
34from app.logic.participants import getParticipationStatusForTrainings, checkUserRsvp
35from app.logic.minor import getMinorInterest
36from app.logic.fileHandler import FileHandler
37from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort, addBonnerCohortToRsvpLog
38from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getImportedCourses, getInstructorCourses, editImportedCourses
40from app.controllers.admin import admin_bp
41from app.logic.spreadsheet import createSpreadsheet
44@admin_bp.route('/admin/reports')
45def reports():
46 academicYears = Term.select(Term.academicYear).distinct().order_by(Term.academicYear.desc())
47 academicYears = list(map(lambda t: t.academicYear, academicYears))
48 return render_template("/admin/reports.html", academicYears=academicYears)
50@admin_bp.route('/admin/reports/download', methods=['POST'])
51def downloadFile():
52 academicYear = request.form.get('academicYear')
53 filepath = os.path.abspath(createSpreadsheet(academicYear))
54 return send_file(filepath, as_attachment=True)
58@admin_bp.route('/switch_user', methods=['POST'])
59def switchUser():
60 if app.env == "production":
61 print(f"An attempt was made to switch to another user by {g.current_user.username}!")
62 abort(403)
64 print(f"Switching user from {g.current_user} to",request.form['newuser'])
65 session['current_user'] = model_to_dict(User.get_by_id(request.form['newuser']))
67 return redirect(request.referrer)
70@admin_bp.route('/eventTemplates')
71def templateSelect():
72 if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff:
73 allprograms = getAllowedPrograms(g.current_user)
74 visibleTemplates = getAllowedTemplates(g.current_user)
75 return render_template("/events/templateSelector.html",
76 programs=allprograms,
77 celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored),
78 templates=visibleTemplates)
79 else:
80 abort(403)
83@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST'])
84def createEvent(templateid, programid):
85 if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)):
86 abort(403)
88 # Validate given URL
89 program = None
90 try:
91 template = EventTemplate.get_by_id(templateid)
92 if programid:
93 program = Program.get_by_id(programid)
94 except DoesNotExist as e:
95 print("Invalid template or program id:", e)
96 flash("There was an error with your selection. Please try again or contact Systems Support.", "danger")
97 return redirect(url_for("admin.program_picker"))
99 # Get the data from the form or from the template
100 eventData = template.templateData
101 eventData['program'] = program
103 if request.method == "GET":
104 eventData['contactName'] = "CELTS Admin"
105 eventData['contactEmail'] = app.config['celts_admin_contact']
106 if program:
107 eventData['location'] = program.defaultLocation
108 if program.contactName:
109 eventData['contactName'] = program.contactName
110 if program.contactEmail:
111 eventData['contactEmail'] = program.contactEmail
113 # Try to save the form
114 if request.method == "POST":
115 savedEvents = None
116 eventData.update(request.form.copy())
117 eventData = preprocessEventData(eventData)
118 if eventData.get('isSeries'):
119 eventData['seriesData'] = json.loads(eventData['seriesData'])
120 succeeded, savedEvents, failedSavedOfferings = attemptSaveMultipleOfferings(eventData, getFilesFromRequest(request))
121 if not succeeded:
122 for index, validationErrorMessage in failedSavedOfferings:
123 eventData['seriesData'][index]['isDuplicate'] = True
124 validationErrorMessage = failedSavedOfferings[-1][1] # The last validation error message from the list of offerings if there are multiple
125 print(f"Failed to save offerings {failedSavedOfferings}")
126 else:
127 try:
128 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
129 except Exception as e:
130 print("Failed saving regular event", e)
131 validationErrorMessage = "Failed to save event."
133 if savedEvents:
134 rsvpcohorts = request.form.getlist("cohorts[]")
135 for year in rsvpcohorts:
136 rsvpForBonnerCohort(int(year), savedEvents[0].id)
137 addBonnerCohortToRsvpLog(int(year), savedEvents[0].id)
140 noun = ((eventData.get('isSeries')) and "Events" or "Event") # pluralize
141 flash(f"{noun} successfully created!", 'success')
144 if program:
145 if len(savedEvents) > 1 and eventData.get('isRepeating'):
146 createActivityLog(f"Created a repeating series, <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name[:-7]}</a>, for {program.programName}, with a start date of {datetime.strftime(savedEvents[0].startDate, '%m/%d/%Y')}. The last event in the series will be on {datetime.strftime(savedEvents[-1].startDate, '%m/%d/%Y')}.")
147 elif len(savedEvents) >= 1 and eventData.get('isSeries'):
148 eventDates = [eventData.startDate.strftime('%m/%d/%Y') for eventData in savedEvents]
149 eventList = ', '.join(f"<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>" for event in savedEvents)
151 if len(savedEvents) > 1:
152 #creates list of events created in a multiple series to display in the logs
153 eventList = ', '.join(eventList.split(', ')[:-1]) + f', and ' + eventList.split(', ')[-1]
154 #get last date and stick at the end after 'and' so that it reads like a sentence in admin log
155 lastEventDate = eventDates[-1]
156 eventDates = ', '.join(eventDates[:-1]) + f', and {lastEventDate}'
158 createActivityLog(f"Created series {eventList} for {program.programName}, with start dates of {eventDates}.")
160 else:
161 createActivityLog(f"Created 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')}.")
162 else:
163 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')}.")
165 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
166 else:
167 flash(validationErrorMessage, 'warning')
169 # make sure our data is the same regardless of GET or POST
170 preprocessEventData(eventData)
171 isProgramManager = g.current_user.isProgramManagerFor(programid)
173 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
175 requirements, bonnerCohorts = [], []
176 if eventData['program'] is not None and eventData['program'].isBonnerScholars:
177 requirements = getCertRequirements(Certification.BONNER)
178 bonnerCohorts = getBonnerCohorts(limit=5)
179 return render_template(f"/events/{template.templateFile}",
180 template = template,
181 eventData = eventData,
182 futureTerms = futureTerms,
183 requirements = requirements,
184 bonnerCohorts = bonnerCohorts,
185 isProgramManager = isProgramManager)
188@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
189def rsvpLogDisplay(eventId):
190 event = Event.get_by_id(eventId)
191 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
192 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
193 return render_template("/events/rsvpLog.html",
194 event = event,
195 allLogs = allLogs)
196 else:
197 abort(403)
199@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
200def renewEvent(eventId):
201 try:
202 formData = request.form
203 try:
204 assert formData['timeStart'] < formData['timeEnd']
205 except AssertionError:
206 flash("End time must be after start time", 'warning')
207 return redirect(url_for('admin.eventDisplay', eventId = eventId))
209 try:
210 if formData.get('dateEnd'):
211 assert formData['dateStart'] < formData['dateEnd']
212 except AssertionError:
213 flash("End date must be after start date", 'warning')
214 return redirect(url_for('admin.eventDisplay', eventId = eventId))
217 priorEvent = model_to_dict(Event.get_by_id(eventId))
218 newEventDict = priorEvent.copy()
219 newEventDict.pop('id')
220 newEventDict.update({
221 'program': int(priorEvent['program']['id']),
222 'term': int(priorEvent['term']['id']),
223 'timeStart': formData['timeStart'],
224 'timeEnd': formData['timeEnd'],
225 'location': formData['location'],
226 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
227 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
228 'isRepeating': bool(priorEvent['isRepeating']),
229 'seriesId': priorEvent['seriesId'],
230 })
231 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
232 if message:
233 flash(message, "danger")
234 return redirect(url_for('admin.eventDisplay', eventId = eventId))
236 copyRsvpToNewEvent(priorEvent, newEvent[0])
237 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
238 flash("Event successfully renewed.", "success")
239 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
242 except Exception as e:
243 print("Error while trying to renew event:", e)
244 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
245 return redirect(url_for('admin.eventDisplay', eventId = eventId))
249@admin_bp.route('/event/<eventId>/view', methods=['GET'])
250@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
251def eventDisplay(eventId):
252 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
253 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
254 viewer = g.current_user
255 event = Event.get_by_id(eventId)
256 addEventView(viewer,event)
257 # Validate given URL
258 try:
259 event = Event.get_by_id(eventId)
260 except DoesNotExist as e:
261 print(f"Unknown event: {eventId}")
262 abort(404)
264 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
265 if 'edit' in request.url_rule.rule and notPermitted:
266 abort(403)
268 eventData = model_to_dict(event, recurse=False)
269 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
270 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
272 image = None
273 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
274 for attachment in associatedAttachments:
275 for extension in picurestype:
276 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
277 image = filepaths[attachment.fileName][0]
278 if image:
279 break
282 if request.method == "POST": # Attempt to save form
283 eventData = request.form.copy()
284 try:
285 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
287 except Exception as e:
288 print("Error saving event:", e)
289 savedEvents = False
290 validationErrorMessage = "Unknown Error Saving Event. Please try again"
293 if savedEvents:
294 rsvpcohorts = request.form.getlist("cohorts[]")
295 for year in rsvpcohorts:
296 rsvpForBonnerCohort(int(year), event.id)
297 addBonnerCohortToRsvpLog(int(year), event.id)
299 flash("Event successfully updated!", "success")
300 return redirect(url_for("admin.eventDisplay", eventId = event.id))
301 else:
302 flash(validationErrorMessage, 'warning')
304 # make sure our data is the same regardless of GET and POST
305 preprocessEventData(eventData)
306 eventData['program'] = event.program
307 futureTerms = selectSurroundingTerms(g.current_term)
308 userHasRSVPed = checkUserRsvp(g.current_user, event)
309 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
310 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
311 requirements, bonnerCohorts = [], []
313 if eventData['program'] and eventData['program'].isBonnerScholars:
314 requirements = getCertRequirements(Certification.BONNER)
315 bonnerCohorts = getBonnerCohorts(limit=5)
317 rule = request.url_rule
319 # Event Edit
320 if 'edit' in rule.rule:
321 return render_template("events/createEvent.html",
322 eventData = eventData,
323 futureTerms=futureTerms,
324 event = event,
325 requirements = requirements,
326 bonnerCohorts = bonnerCohorts,
327 userHasRSVPed = userHasRSVPed,
328 isProgramManager = isProgramManager,
329 filepaths = filepaths)
330 # Event View
331 else:
332 # get text representations of dates for html
333 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
334 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
335 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
336 eventCountdown = getCountdownToEvent(event)
339 # Identify the next event in a repeating series
340 if event.isRepeating:
341 eventSeriesList = list(Event.select().where(Event.seriesId == event.seriesId)
342 .where((Event.isCanceled == False) | (Event.id == event.id))
343 .order_by(Event.startDate))
344 eventIndex = eventSeriesList.index(event)
345 if len(eventSeriesList) != (eventIndex + 1):
346 eventData["nextRepeatingEvent"] = eventSeriesList[eventIndex + 1]
348 currentEventRsvpAmount = getEventRsvpCount(event.id)
350 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
352 return render_template("events/eventView.html",
353 eventData=eventData,
354 event=event,
355 userHasRSVPed=userHasRSVPed,
356 programTrainings=userParticipatedTrainingEvents,
357 currentEventRsvpAmount=currentEventRsvpAmount,
358 isProgramManager=isProgramManager,
359 filepaths=filepaths,
360 image=image,
361 pageViewsCount=pageViewsCount,
362 eventCountdown=eventCountdown
363 )
367@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
368def cancelRoute(eventId):
369 if g.current_user.isAdmin:
370 try:
371 cancelEvent(eventId)
372 return redirect(request.referrer)
374 except Exception as e:
375 print('Error while canceling event:', e)
376 return "", 500
378 else:
379 abort(403)
381@admin_bp.route('/event/undo', methods=['GET'])
382def undoEvent():
383 try:
384 eventIds = session['lastDeletedEvent'] #list of Ids of the events that got deleted
385 for eventId in eventIds:
386 Event.update({Event.deletionDate: None, Event.deletedBy: None}).where(Event.id == eventId).execute()
387 event = Event.get_or_none(Event.id == eventId)
388 repeatingEvents = list(Event.select().where((Event.seriesId == event.seriesId) & (Event.isRepeating) & (Event.deletionDate == None)).order_by(Event.id))
389 if event.isRepeating:
390 nameCounter = 1
391 for repeatingEvent in repeatingEvents:
392 newEventNameList = repeatingEvent.name.split()
393 newEventNameList[-1] = f"{nameCounter}"
394 newEventNameList = " ".join(newEventNameList)
395 Event.update({Event.name: newEventNameList}).where(Event.id==repeatingEvent.id).execute()
396 nameCounter += 1
397 flash("Deletion successfully undone.", "success")
398 return redirect(url_for("main.events", selectedTerm=g.current_term))
400 except Exception as e:
401 print('Error while canceling event:', e)
402 return "", 500
404@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
405def deleteRoute(eventId):
406 try:
407 deleteEvent(eventId)
408 session['lastDeletedEvent'] = [eventId]
409 flash("Event successfully deleted.", "success")
410 return redirect(url_for("main.events", selectedTerm=g.current_term))
412 except Exception as e:
413 print('Error while canceling event:', e)
414 return "", 500
416@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
417def deleteEventAndAllFollowingRoute(eventId):
418 try:
419 session["lastDeletedEvent"] = deleteEventAndAllFollowing(eventId)
420 flash("Events successfully deleted.", "success")
421 return redirect(url_for("main.events", selectedTerm=g.current_term))
423 except Exception as e:
424 print('Error while canceling event:', e)
425 return "", 500
427@admin_bp.route('/event/<eventId>/deleteAllEventsInSeries', methods=['POST'])
428def deleteAllEventsInSeriesRoute(eventId):
429 try:
430 session["lastDeletedEvent"] = deleteAllEventsInSeries(eventId)
431 flash("Events successfully deleted.", "success")
432 return redirect(url_for("main.events", selectedTerm=g.current_term))
434 except Exception as e:
435 print('Error while canceling event:', e)
436 return "", 500
438@admin_bp.route('/makeRepeatingEvents', methods=['POST'])
439def addRepeatingEvents():
440 repeatingEvents = getRepeatingEventsData(preprocessEventData(request.form.copy()))
441 return json.dumps(repeatingEvents, default=str)
444@admin_bp.route('/userProfile', methods=['POST'])
445def userProfile():
446 volunteerName= request.form.copy()
447 if volunteerName['searchStudentsInput']:
448 username = volunteerName['searchStudentsInput'].strip("()")
449 user=username.split('(')[-1]
450 return redirect(url_for('main.viewUsersProfile', username=user))
451 else:
452 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
453 return redirect(url_for('admin.studentSearchPage'))
455@admin_bp.route('/search_student', methods=['GET'])
456def studentSearchPage():
457 if g.current_user.isAdmin:
458 return render_template("/admin/searchStudentPage.html")
459 abort(403)
461@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
462def activityLogs():
463 if g.current_user.isCeltsAdmin:
464 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
465 return render_template("/admin/activityLogs.html",
466 allLogs = allLogs)
467 else:
468 abort(403)
470@admin_bp.route("/deleteEventFile", methods=["POST"])
471def deleteEventFile():
472 fileData= request.form
473 eventfile=FileHandler(eventId=fileData["databaseId"])
474 eventfile.deleteFile(fileData["fileId"])
475 return ""
477@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
478def addCourseFile():
479 fileData = request.files['addCourseParticipants']
480 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
481 fileData.save(filePath)
482 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
483 os.remove(filePath)
484 return redirect(url_for("admin.manageServiceLearningCourses"))
486@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
487@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
488def manageServiceLearningCourses(term=None):
490 """
491 The SLC management page for admins
492 """
493 if not g.current_user.isCeltsAdmin:
494 abort(403)
496 if request.method == 'POST' and "submitParticipant" in request.form:
497 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
498 flash('Courses and participants saved successfully!', 'success')
499 return redirect(url_for('admin.manageServiceLearningCourses'))
501 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
503 setRedirectTarget(request.full_path)
504 # retrieve and store the courseID of the imported course from a session variable if it exists.
505 # This allows us to export the courseID in the html and use it.
506 courseID = session.get("alterCourseId")
508 if courseID:
509 # delete courseID from the session if it was retrieved, for storage purposes.
510 session.pop("alterCourseId")
511 return render_template('/admin/manageServiceLearningFaculty.html',
512 courseInstructors = getInstructorCourses(),
513 unapprovedCourses = unapprovedCourses(manageTerm),
514 approvedCourses = approvedCourses(manageTerm),
515 importedCourses = getImportedCourses(manageTerm),
516 terms = selectSurroundingTerms(g.current_term),
517 term = manageTerm,
518 cpPreview = session.get('cpPreview', {}),
519 cpPreviewErrors = session.get('cpErrors', []),
520 courseID = courseID
521 )
523 return render_template('/admin/manageServiceLearningFaculty.html',
524 courseInstructors = getInstructorCourses(),
525 unapprovedCourses = unapprovedCourses(manageTerm),
526 approvedCourses = approvedCourses(manageTerm),
527 importedCourses = getImportedCourses(manageTerm),
528 terms = selectSurroundingTerms(g.current_term),
529 term = manageTerm,
530 cpPreview= session.get('cpPreview',{}),
531 cpPreviewErrors = session.get('cpErrors',[])
532 )
534@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
535def getSidebarInformation() -> str:
536 """
537 Get the count of unapproved courses and students interested in the minor for the current term
538 to display in the admin sidebar. It must be returned as a string to be received by the
539 ajax request.
540 """
541 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
542 interestedStudentsCount: int = len(getMinorInterest())
543 return {"unapprovedCoursesCount": unapprovedCoursesCount,
544 "interestedStudentsCount": interestedStudentsCount}
546@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
547def removeFromSession():
548 try:
549 session.pop('cpPreview')
550 except KeyError:
551 pass
553 return ""
555@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
556def alterImportedCourse(courseID):
557 """
558 This route handles a GET and a POST request for the purpose of imported courses.
559 The GET request provides preexisting information of an imported course in a modal.
560 The POST request updates a specific imported course (course name, course abbreviation,
561 hours earned on completion, list of instructors) in the database with new information
562 coming from the imported courses modal.
563 """
564 if request.method == 'GET':
565 try:
566 targetCourse = Course.get_by_id(courseID)
567 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
569 try:
570 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
571 except IndexError: # If a course has no participant, IndexError will be raised
572 serviceHours = 20
574 courseData = model_to_dict(targetCourse, recurse=False)
575 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
576 courseData['hoursEarned'] = serviceHours
578 return jsonify(courseData)
580 except DoesNotExist:
581 flash("Course not found")
582 return jsonify({"error": "Course not found"}), 404
584 if request.method == 'POST':
585 # Update course information in the database
586 courseData = request.form.copy()
587 editImportedCourses(courseData)
588 session['alterCourseId'] = courseID
590 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
593@admin_bp.route("/manageBonner")
594def manageBonner():
595 if not g.current_user.isCeltsAdmin:
596 abort(403)
598 return render_template("/admin/bonnerManagement.html",
599 cohorts=getBonnerCohorts(),
600 events=getBonnerEvents(g.current_term),
601 requirements=getCertRequirements(certification=Certification.BONNER))
603@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
604def updatecohort(year, method, username):
605 if not g.current_user.isCeltsAdmin:
606 abort(403)
608 try:
609 user = User.get_by_id(username)
610 except:
611 abort(500)
613 if method == "add":
614 try:
615 BonnerCohort.create(year=year, user=user)
616 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
617 except IntegrityError as e:
618 # if they already exist, ignore the error
619 flash(f'Error: {user.fullName} already added.', "danger")
620 pass
622 elif method == "remove":
623 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
624 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
625 else:
626 flash(f"Error: {user.fullName} can't be added.", "danger")
627 abort(500)
629 return ""
631@admin_bp.route("/bonnerxls")
632def bonnerxls():
633 if not g.current_user.isCeltsAdmin:
634 abort(403)
636 newfile = makeBonnerXls()
637 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
639@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
640def saveRequirements(certid):
641 if not g.current_user.isCeltsAdmin:
642 abort(403)
644 newRequirements = updateCertRequirements(certid, request.get_json())
646 return jsonify([requirement.id for requirement in newRequirements])
649@admin_bp.route("/displayEventFile", methods=["POST"])
650def displayEventFile():
651 fileData = request.form
652 eventfile = FileHandler(eventId=fileData["id"])
653 isChecked = fileData.get('checked') == 'true'
654 eventfile.changeDisplay(fileData['id'], isChecked)
655 return ""