Coverage for app/controllers/admin/routes.py: 23%
418 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-31 16:31 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-07-31 16:31 +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
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/template_selector.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
102 eventData['program'] = program
104 if request.method == "GET":
105 eventData['contactName'] = "CELTS Admin"
106 eventData['contactEmail'] = app.config['celts_admin_contact']
107 if program:
108 eventData['location'] = program.defaultLocation
109 if program.contactName:
110 eventData['contactName'] = program.contactName
111 if program.contactEmail:
112 eventData['contactEmail'] = program.contactEmail
114 # Try to save the form
115 if request.method == "POST":
116 eventData.update(request.form.copy())
117 try:
118 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
120 except Exception as e:
121 print("Error saving event:", e)
122 savedEvents = False
123 validationErrorMessage = "Unknown Error Saving Event. Please try again"
125 if savedEvents:
126 rsvpcohorts = request.form.getlist("cohorts[]")
127 for year in rsvpcohorts:
128 rsvpForBonnerCohort(int(year), savedEvents[0].id)
129 addBonnerCohortToRsvpLog(int(year), savedEvents[0].id)
132 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
133 flash(f"{noun} successfully created!", 'success')
135 if program:
136 if len(savedEvents) > 1:
137 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')}.")
138 else:
139 createActivityLog(f"Created <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a> for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.")
140 else:
141 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')}.")
143 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
144 else:
145 flash(validationErrorMessage, 'warning')
147 # make sure our data is the same regardless of GET or POST
148 preprocessEventData(eventData)
149 isProgramManager = g.current_user.isProgramManagerFor(programid)
151 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
153 requirements, bonnerCohorts = [], []
154 if eventData['program'] is not None and eventData['program'].isBonnerScholars:
155 requirements = getCertRequirements(Certification.BONNER)
156 bonnerCohorts = getBonnerCohorts(limit=5)
157 return render_template(f"/admin/{template.templateFile}",
158 template = template,
159 eventData = eventData,
160 futureTerms = futureTerms,
161 requirements = requirements,
162 bonnerCohorts = bonnerCohorts,
163 isProgramManager = isProgramManager)
166@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
167def rsvpLogDisplay(eventId):
168 event = Event.get_by_id(eventId)
169 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
170 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
171 return render_template("/events/rsvpLog.html",
172 event = event,
173 allLogs = allLogs)
174 else:
175 abort(403)
177@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
178def renewEvent(eventId):
179 try:
180 formData = request.form
181 try:
182 assert formData['timeStart'] < formData['timeEnd']
183 except AssertionError:
184 flash("End time must be after start time", 'warning')
185 return redirect(url_for('admin.eventDisplay', eventId = eventId))
187 try:
188 if formData.get('dateEnd'):
189 assert formData['dateStart'] < formData['dateEnd']
190 except AssertionError:
191 flash("End date must be after start date", 'warning')
192 return redirect(url_for('admin.eventDisplay', eventId = eventId))
195 priorEvent = model_to_dict(Event.get_by_id(eventId))
196 newEventDict = priorEvent.copy()
197 newEventDict.pop('id')
198 newEventDict.update({
199 'program': int(priorEvent['program']['id']),
200 'term': int(priorEvent['term']['id']),
201 'timeStart': formData['timeStart'],
202 'timeEnd': formData['timeEnd'],
203 'location': formData['location'],
204 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
205 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
206 'isRecurring': bool(priorEvent['recurringId'])
207 })
208 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
209 if message:
210 flash(message, "danger")
211 return redirect(url_for('admin.eventDisplay', eventId = eventId))
213 copyRsvpToNewEvent(priorEvent, newEvent[0])
214 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
215 flash("Event successfully renewed.", "success")
216 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
219 except Exception as e:
220 print("Error while trying to renew event:", e)
221 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
222 return redirect(url_for('admin.eventDisplay', eventId = eventId))
226@admin_bp.route('/event/<eventId>/view', methods=['GET'])
227@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
228def eventDisplay(eventId):
229 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
230 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
231 viewer = g.current_user
232 event = Event.get_by_id(eventId)
233 addEventView(viewer,event)
234 # Validate given URL
235 try:
236 event = Event.get_by_id(eventId)
237 except DoesNotExist as e:
238 print(f"Unknown event: {eventId}")
239 abort(404)
241 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
242 if 'edit' in request.url_rule.rule and notPermitted:
243 abort(403)
245 eventData = model_to_dict(event, recurse=False)
246 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
247 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
249 image = None
250 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
251 for attachment in associatedAttachments:
252 for extension in picurestype:
253 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
254 image = filepaths[attachment.fileName][0]
255 if image:
256 break
259 if request.method == "POST": # Attempt to save form
260 eventData = request.form.copy()
261 try:
262 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
264 except Exception as e:
265 print("Error saving event:", e)
266 savedEvents = False
267 validationErrorMessage = "Unknown Error Saving Event. Please try again"
270 if savedEvents:
271 rsvpcohorts = request.form.getlist("cohorts[]")
272 for year in rsvpcohorts:
273 rsvpForBonnerCohort(int(year), event.id)
274 addBonnerCohortToRsvpLog(int(year), event.id)
276 flash("Event successfully updated!", "success")
277 return redirect(url_for("admin.eventDisplay", eventId = event.id))
278 else:
279 flash(validationErrorMessage, 'warning')
281 # make sure our data is the same regardless of GET and POST
282 preprocessEventData(eventData)
283 eventData['program'] = event.program
284 futureTerms = selectSurroundingTerms(g.current_term)
285 userHasRSVPed = checkUserRsvp(g.current_user, event)
286 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
287 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
288 requirements, bonnerCohorts = [], []
290 if eventData['program'] and eventData['program'].isBonnerScholars:
291 requirements = getCertRequirements(Certification.BONNER)
292 bonnerCohorts = getBonnerCohorts(limit=5)
294 rule = request.url_rule
296 # Event Edit
297 if 'edit' in rule.rule:
298 return render_template("admin/createEvent.html",
299 eventData = eventData,
300 futureTerms=futureTerms,
301 event = event,
302 requirements = requirements,
303 bonnerCohorts = bonnerCohorts,
304 userHasRSVPed = userHasRSVPed,
305 isProgramManager = isProgramManager,
306 filepaths = filepaths)
307 # Event View
308 else:
309 # get text representations of dates for html
310 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
311 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
312 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
313 eventCountdown = getCountdownToEvent(event)
316 # Identify the next event in a recurring series
317 if event.recurringId:
318 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId)
319 .where((Event.isCanceled == False) | (Event.id == event.id))
320 .order_by(Event.startDate))
321 eventIndex = eventSeriesList.index(event)
322 if len(eventSeriesList) != (eventIndex + 1):
323 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1]
325 currentEventRsvpAmount = getEventRsvpCount(event.id)
327 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
329 return render_template("eventView.html",
330 eventData=eventData,
331 event=event,
332 userHasRSVPed=userHasRSVPed,
333 programTrainings=userParticipatedTrainingEvents,
334 currentEventRsvpAmount=currentEventRsvpAmount,
335 isProgramManager=isProgramManager,
336 filepaths=filepaths,
337 image=image,
338 pageViewsCount=pageViewsCount,
339 eventCountdown=eventCountdown
340 )
344@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
345def cancelRoute(eventId):
346 if g.current_user.isAdmin:
347 try:
348 cancelEvent(eventId)
349 return redirect(request.referrer)
351 except Exception as e:
352 print('Error while canceling event:', e)
353 return "", 500
355 else:
356 abort(403)
358@admin_bp.route('/event/undo', methods=['GET'])
359def undoEvent():
360 try:
361 events = session['lastDeletedEvent']
362 for eventId in events:
363 Event.update({Event.deletionDate: None, Event.deletedBy: None}).where(Event.id == eventId).execute()
364 event = Event.get_or_none(Event.id == eventId)
365 recurringEvents = list(Event.select().where((Event.recurringId==event.recurringId) & (Event.deletionDate == None)).order_by(Event.id))
366 if event.recurringId is not None:
367 nameCounter = 1
368 for recurringEvent in recurringEvents:
369 newEventNameList = recurringEvent.name.split()
370 newEventNameList[-1] = f"{nameCounter}"
371 newEventNameList = " ".join(newEventNameList)
372 Event.update({Event.name: newEventNameList}).where(Event.id==recurringEvent.id).execute()
373 nameCounter += 1
374 flash("Deletion successfully undone.", "success")
375 return redirect('/eventsList/' + str(g.current_term))
376 except Exception as e:
377 print('Error while canceling event:', e)
378 return "", 500
380@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
381def deleteRoute(eventId):
382 try:
383 deleteEvent(eventId)
384 session['lastDeletedEvent'] = [eventId]
385 flash("Event successfully deleted.", "success")
386 return redirect(url_for("main.events", selectedTerm=g.current_term))
388 except Exception as e:
389 print('Error while canceling event:', e)
390 return "", 500
392@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
393def deleteEventAndAllFollowingRoute(eventId):
394 try:
395 session["lastDeletedEvent"] = deleteEventAndAllFollowing(eventId)
396 flash("Events successfully deleted.", "success")
397 return redirect(url_for("main.events", selectedTerm=g.current_term))
399 except Exception as e:
400 print('Error while canceling event:', e)
401 return "", 500
403@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
404def deleteAllRecurringEventsRoute(eventId):
405 try:
406 session["lastDeletedEvent"] = deleteAllRecurringEvents(eventId)
407 flash("Events successfully deleted.", "success")
408 return redirect(url_for("main.events", selectedTerm=g.current_term))
410 except Exception as e:
411 print('Error while canceling event:', e)
412 return "", 500
414@admin_bp.route('/makeRecurringEvents', methods=['POST'])
415def addRecurringEvents():
416 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
417 return json.dumps(recurringEvents, default=str)
420@admin_bp.route('/userProfile', methods=['POST'])
421def userProfile():
422 volunteerName= request.form.copy()
423 if volunteerName['searchStudentsInput']:
424 username = volunteerName['searchStudentsInput'].strip("()")
425 user=username.split('(')[-1]
426 return redirect(url_for('main.viewUsersProfile', username=user))
427 else:
428 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
429 return redirect(url_for('admin.studentSearchPage'))
431@admin_bp.route('/search_student', methods=['GET'])
432def studentSearchPage():
433 if g.current_user.isAdmin:
434 return render_template("/admin/searchStudentPage.html")
435 abort(403)
437@admin_bp.route('/addParticipants', methods = ['GET'])
438def addParticipants():
439 '''Renders the page, will be removed once merged with full page'''
441 return render_template('addParticipants.html',
442 title="Add Participants")
444@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
445def activityLogs():
446 if g.current_user.isCeltsAdmin:
447 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
448 return render_template("/admin/activityLogs.html",
449 allLogs = allLogs)
450 else:
451 abort(403)
453@admin_bp.route("/deleteEventFile", methods=["POST"])
454def deleteEventFile():
455 fileData= request.form
456 eventfile=FileHandler(eventId=fileData["databaseId"])
457 eventfile.deleteFile(fileData["fileId"])
458 return ""
460@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
461def addCourseFile():
462 fileData = request.files['addCourseParticipants']
463 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
464 fileData.save(filePath)
465 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
466 os.remove(filePath)
467 return redirect(url_for("admin.manageServiceLearningCourses"))
469@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
470@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
471def manageServiceLearningCourses(term=None):
473 """
474 The SLC management page for admins
475 """
476 if not g.current_user.isCeltsAdmin:
477 abort(403)
479 if request.method == 'POST' and "submitParticipant" in request.form:
480 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
481 flash('Courses and participants saved successfully!', 'success')
482 return redirect(url_for('admin.manageServiceLearningCourses'))
484 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
486 setRedirectTarget(request.full_path)
487 # retrieve and store the courseID of the imported course from a session variable if it exists.
488 # This allows us to export the courseID in the html and use it.
489 courseID = session.get("alterCourseId")
491 if courseID:
492 # delete courseID from the session if it was retrieved, for storage purposes.
493 session.pop("alterCourseId")
494 return render_template('/admin/manageServiceLearningFaculty.html',
495 courseInstructors = getInstructorCourses(),
496 unapprovedCourses = unapprovedCourses(manageTerm),
497 approvedCourses = approvedCourses(manageTerm),
498 importedCourses = getImportedCourses(manageTerm),
499 terms = selectSurroundingTerms(g.current_term),
500 term = manageTerm,
501 cpPreview = session.get('cpPreview', {}),
502 cpPreviewErrors = session.get('cpErrors', []),
503 courseID = courseID
504 )
506 return render_template('/admin/manageServiceLearningFaculty.html',
507 courseInstructors = getInstructorCourses(),
508 unapprovedCourses = unapprovedCourses(manageTerm),
509 approvedCourses = approvedCourses(manageTerm),
510 importedCourses = getImportedCourses(manageTerm),
511 terms = selectSurroundingTerms(g.current_term),
512 term = manageTerm,
513 cpPreview= session.get('cpPreview',{}),
514 cpPreviewErrors = session.get('cpErrors',[])
515 )
517@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
518def getSidebarInformation() -> str:
519 """
520 Get the count of unapproved courses and students interested in the minor for the current term
521 to display in the admin sidebar. It must be returned as a string to be received by the
522 ajax request.
523 """
524 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
525 interestedStudentsCount: int = len(getMinorInterest())
526 return {"unapprovedCoursesCount": unapprovedCoursesCount,
527 "interestedStudentsCount": interestedStudentsCount}
529@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
530def removeFromSession():
531 try:
532 session.pop('cpPreview')
533 except KeyError:
534 pass
536 return ""
538@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
539def alterImportedCourse(courseID):
540 """
541 This route handles a GET and a POST request for the purpose of imported courses.
542 The GET request provides preexisting information of an imported course in a modal.
543 The POST request updates a specific imported course (course name, course abbreviation,
544 hours earned on completion, list of instructors) in the database with new information
545 coming from the imported courses modal.
546 """
547 if request.method == 'GET':
548 try:
549 targetCourse = Course.get_by_id(courseID)
550 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
552 try:
553 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
554 except IndexError: # If a course has no participant, IndexError will be raised
555 serviceHours = 20
557 courseData = model_to_dict(targetCourse, recurse=False)
558 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
559 courseData['hoursEarned'] = serviceHours
561 return jsonify(courseData)
563 except DoesNotExist:
564 flash("Course not found")
565 return jsonify({"error": "Course not found"}), 404
567 if request.method == 'POST':
568 # Update course information in the database
569 courseData = request.form.copy()
570 editImportedCourses(courseData)
571 session['alterCourseId'] = courseID
573 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
576@admin_bp.route("/manageBonner")
577def manageBonner():
578 if not g.current_user.isCeltsAdmin:
579 abort(403)
581 return render_template("/admin/bonnerManagement.html",
582 cohorts=getBonnerCohorts(),
583 events=getBonnerEvents(g.current_term),
584 requirements=getCertRequirements(certification=Certification.BONNER))
586@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
587def updatecohort(year, method, username):
588 if not g.current_user.isCeltsAdmin:
589 abort(403)
591 try:
592 user = User.get_by_id(username)
593 except:
594 abort(500)
596 if method == "add":
597 try:
598 BonnerCohort.create(year=year, user=user)
599 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
600 except IntegrityError as e:
601 # if they already exist, ignore the error
602 flash(f'Error: {user.fullName} already added.', "danger")
603 pass
605 elif method == "remove":
606 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
607 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
608 else:
609 flash(f"Error: {user.fullName} can't be added.", "danger")
610 abort(500)
612 return ""
614@admin_bp.route("/bonnerxls")
615def bonnerxls():
616 if not g.current_user.isCeltsAdmin:
617 abort(403)
619 newfile = makeBonnerXls()
620 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
622@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
623def saveRequirements(certid):
624 if not g.current_user.isCeltsAdmin:
625 abort(403)
627 newRequirements = updateCertRequirements(certid, request.get_json())
629 return jsonify([requirement.id for requirement in newRequirements])
632@admin_bp.route("/displayEventFile", methods=["POST"])
633def displayEventFile():
634 fileData = request.form
635 eventfile = FileHandler(eventId=fileData["id"])
636 isChecked = fileData.get('checked') == 'true'
637 eventfile.changeDisplay(fileData['id'], isChecked)
638 return ""