Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7eaaecd
Built signature page and updated profile to show signature status
sheggen Jul 10, 2026
e30170e
Modifying location and usage of signature for handbook
sheggen Jul 13, 2026
6ebcd32
adding minor adjustments to the UI
sheggen Jul 13, 2026
9095cec
Removed unnecessary GET route
sheggen Jul 13, 2026
8d9777e
Now only the user can sign their own profile
sheggen Jul 13, 2026
25f4119
removed commented out css file
sheggen Jul 13, 2026
bb1b291
removed extra css file too
sheggen Jul 13, 2026
678abf6
Added Extravaganza page
sheggen Jul 13, 2026
72c564f
controller checks for studentID from ajax request
sheggen Jul 13, 2026
549eaf3
Added handbook upload to admin settings page
sheggen Jul 13, 2026
1edb027
migrates handbook to all terms in the same academic year, including w…
sheggen Jul 13, 2026
822234e
improving usability of signature canvas in profile
sheggen Jul 13, 2026
9a0ae29
Handles errors more cleanly when signing
sheggen Jul 13, 2026
a941d5a
Very minor cleanup
sheggen Jul 14, 2026
45eb7c7
cleaned up language between isLaborOnly and includesLabor usage. Both…
sheggen Jul 14, 2026
f5eeb8a
fixed bug with date formatting on series events
sheggen Jul 15, 2026
a5fdca3
fixed date formatting issue for recurring and weekly recurring events
sheggen Jul 15, 2026
07f8ece
Only shows switch for includes labor on non-labor-only events
sheggen Jul 15, 2026
5be2c9d
Updates to recurring event logic and model update for event
sheggen Jul 15, 2026
23baf8b
updates to base data to include two new shortcuts
sheggen Jul 15, 2026
5cc86dd
updating signature usability
sheggen Jul 15, 2026
29b9fee
Merge branch 'laborMeetingShortcut' into signature
sheggen Jul 15, 2026
52b4777
Connecting trainings to signatures
sheggen Jul 16, 2026
35c2292
NEXT: FIgure out why all events get isCeltsTraining: 1
sheggen Jul 16, 2026
c47861c
fixed silly little bug with a trailing comma making all events celts …
sheggen Jul 20, 2026
fdb96f8
starting working on PMs rosters
sheggen Jul 20, 2026
aa78fcf
Adding Program Manager Roster for each program to admin interface
sheggen Jul 20, 2026
c86574c
Working on Roster tables
sheggen Jul 21, 2026
16ff286
Adding rosters
sheggen Jul 21, 2026
4fa12b1
rolling out background check tie to term (not needed)
sheggen Jul 22, 2026
bee1926
Updating roster info and ui cleanup
sheggen Jul 22, 2026
e68c30d
Adding a user interest from the roster
sheggen Jul 22, 2026
fac3a68
[INCOMPLETE] Starting to work on the spreadsheet exporter
sheggen Jul 27, 2026
0304a07
Merging development
sheggen Jul 27, 2026
18cd901
Finished spreadsheet exporter
sheggen Jul 28, 2026
e324df2
Code cleanup
sheggen Jul 28, 2026
aa2fd23
Changed notify to interest on profile
sheggen Jul 28, 2026
ab54347
fixed double quote/single quote issue
sheggen Jul 28, 2026
0ddcf6f
Still working on tests. Added flags matrix to Event to handle boolean…
sheggen Jul 29, 2026
08eea6e
restoring run_tests.sh to less verbose output"
sheggen Jul 29, 2026
9942cdf
Removing prints
sheggen Jul 29, 2026
e4b316e
Fixing up tests with new flags matrix
sheggen Jul 30, 2026
260eae9
fixing Roster bug and improving UI
sheggen Jul 30, 2026
d52dc92
Removing background check form file upload and display (didn't want it)
sheggen Jul 30, 2026
85da7c1
Fixed RSVP bug from change to isElegible function
sheggen Jul 30, 2026
3fe6297
fixed test for graduationManagement
sheggen Jul 31, 2026
c0b71e9
Added QR code generator and display
sheggen Jul 31, 2026
dbfea0e
after confirming with CELTS, added fixes to events that broke flag ru…
sheggen Aug 4, 2026
a90deb9
fixing f-string issues with html inside that used double-quotes insid…
sheggen Aug 4, 2026
5b3814d
more f-string fixes
sheggen Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default_user: "ramsayb2"

celts_admin_contact: "celts@berea.edu"
support_email_contact: "support@bereacollege.onmicrosoft.com"
show_queries: True
show_queries: False
test_entry: "Default"

db:
Expand Down
49 changes: 35 additions & 14 deletions app/controllers/admin/routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import request, render_template, url_for, g, redirect
from flask import flash, abort, jsonify, session, send_file
from peewee import DoesNotExist, fn, IntegrityError
from peewee import DoesNotExist, IntegrityError
from playhouse.shortcuts import model_to_dict
import json
from datetime import datetime
Expand All @@ -27,6 +27,7 @@
from app.models.term import Term
from app.models.eventViews import EventView
from app.models.courseStatus import CourseStatus
from app.models.programManager import ProgramManager

from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates
from app.logic.createLogs import createActivityLog
Expand Down Expand Up @@ -75,6 +76,13 @@ def templateSelect():
if not programs:
abort(403)
visibleTemplates = getAllowedTemplates(g.current_user)

# Create extravaganza QR code if it doesn't exist
imgPath = app.config["files"]["image_path"] + "/extravaganza_qr.png"
if not os.path.exists(imgPath):
import qrcode
img = qrcode.make(request.url.split("/")[-2] + "/extravaganza")
img.save(imgPath)
return render_template("/events/templateSelector.html",
programs=programs,
celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored),
Expand All @@ -97,7 +105,7 @@ def createEvent(templateid, programid):
return redirect(url_for("admin.program_picker"))

# Get the data from the form or from the template
eventData = template.templateData
eventData = template.templateData
eventData['program'] = program

if request.method == "GET":
Expand All @@ -113,9 +121,8 @@ def createEvent(templateid, programid):
# Try to save the form
if request.method == "POST":
savedEvents = None
eventData.update(request.form.copy())
eventData.update(request.form.copy())
eventData = preprocessEventData(eventData)

if eventData.get('isSeries'):
eventData['seriesData'] = json.loads(eventData['seriesData'])
succeeded, savedEvents, failedSavedOfferings = attemptSaveMultipleOfferings(eventData, getFilesFromRequest(request))
Expand All @@ -128,9 +135,8 @@ def createEvent(templateid, programid):
try:
savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
except Exception as e:
print("Failed saving regular event", e)
print("Failed saving regular event: ", e)
validationErrorMessage = "Failed to save event."

if savedEvents:
rsvpCohorts = request.form.getlist("cohorts[]")
if rsvpCohorts:
Expand All @@ -144,10 +150,10 @@ def createEvent(templateid, programid):

if program:
if len(savedEvents) > 1 and eventData.get('isRepeating'):
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')}.")
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')}.''')
elif len(savedEvents) >= 1 and eventData.get('isSeries'):
eventDates = [eventData.startDate.strftime('%m/%d/%Y') for eventData in savedEvents]
eventList = ', '.join(f"<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>" for event in savedEvents)
eventList = ', '.join(f'''<a href=\"{url_for('admin.eventDisplay', eventId=event.id)}\">{event.name}</a>''' for event in savedEvents)

if len(savedEvents) > 1:
#creates list of events created in a multiple series to display in the logs
Expand All @@ -159,9 +165,9 @@ def createEvent(templateid, programid):
createActivityLog(f"Created series {eventList} for {program.programName}, with start dates of {eventDates}.")

else:
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')}.")
createActivityLog('''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')}.''')
else:
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')}.")
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')}.''')

return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
else:
Expand All @@ -180,8 +186,6 @@ def createEvent(templateid, programid):
for year, cohort in rawBonnerCohorts.items():
if cohort:
bonnerCohorts[year] = cohort


return render_template(f"/events/{template.templateFile}",
template = template,
eventData = eventData,
Expand Down Expand Up @@ -239,7 +243,7 @@ def renewEvent(eventId):
return redirect(url_for('admin.eventDisplay', eventId = eventId))

copyRsvpToNewEvent(priorEvent, newEvent[0])
createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
createActivityLog(f'''Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.''')
flash("Event successfully renewed.", "success")
return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))

Expand Down Expand Up @@ -289,10 +293,14 @@ def eventDisplay(eventId):


if request.method == "POST": # Attempt to save form
eventData = request.form.copy()
eventData = request.form.copy()
try:
savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))

except IntegrityError as e:
print("Error saving event:", e)
savedEvents = False
validationErrorMessage = "This combination of settings is not allowed."
except Exception as e:
print("Error saving event:", e)
savedEvents = False
Expand Down Expand Up @@ -682,3 +690,16 @@ def displayEventFile():
isChecked = fileData.get('checked') == 'true'
eventfile.changeDisplay(fileData['id'], isChecked)
return ""

@admin_bp.route("/handbookSignature", methods=["POST"])
def handbookSignature():
data = request.get_json()
if not (g.current_user.username == data.get('studentID')):
abort(403)
signer = User.get(User.username == g.current_user.username)
if signer:
signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d")
signer.signatureTerm = g.current_term
signer.save()
return "", 200
abort(403)
69 changes: 65 additions & 4 deletions app/controllers/admin/userManagement.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
from flask import render_template,request, flash, g, abort, redirect, url_for, jsonify
import os
from pathlib import Path

from app.models.term import Term
from flask import render_template,request, flash, g, abort, redirect, send_file, url_for, jsonify, session
from playhouse.shortcuts import model_to_dict
from peewee import fn, JOIN, DoesNotExist
import re
from werkzeug.utils import secure_filename


from app.controllers.admin import admin_bp
from app.models.user import User
from app.models.program import Program
from app.logic.fileHandler import FileHandler
from app.logic.userManagement import addCeltsAdmin,addCeltsStudentStaff,removeCeltsAdmin,removeCeltsStudentStaff
from app.logic.userManagement import addCeltsAdmin,addCeltsStudentStaff, createSpreadsheetForRosters,removeCeltsAdmin,removeCeltsStudentStaff
from app.logic.userManagement import changeProgramInfo
from app.logic.participants import getTrainingsForInterestedParticipants, getParticipantsForProgramForAY
from app.logic.utils import selectSurroundingTerms
from app.logic.term import addNextTerm, changeCurrentTerm
from app.logic.users import getProgramInterest
from app.logic.volunteers import setProgramManager
from app.models.attachmentUpload import AttachmentUpload
from app.models.programManager import ProgramManager
from app.models.programBan import ProgramBan
from app.models.user import User

@admin_bp.route('/admin/manageUsers', methods = ['POST'])
Expand Down Expand Up @@ -118,24 +127,76 @@ def userManagement():

currentAdmins = list(User.select().where(User.isCeltsAdmin))
currentStudentStaff = list(User.select().where(User.isCeltsStudentStaff))
currentTerm = Term.get(Term.isCurrentTerm)
if g.current_user.isCeltsAdmin or g.current_user.isProgramManager:
return render_template('admin/userManagement.html',
terms = terms,
programs = list(currentPrograms),
currentAdmins = currentAdmins,
currentStudentStaff = currentStudentStaff,
currentTerm = currentTerm
)
abort(403)

@admin_bp.route('/admin/changeTerm', methods=['POST'])
def changeTerm():
termData = request.form
term = int(termData["id"])
changeCurrentTerm(term)
return ""
newTerm = changeCurrentTerm(term)
return model_to_dict(newTerm)

@admin_bp.route('/admin/addNewTerm', methods = ['POST'])
def addNewTerm():
addNextTerm()
flash("New term added", "success")
return ""

@admin_bp.route('/upload/<fileCategory>/<currentTerm>', methods = ['POST'])
def upload(fileCategory, currentTerm):
term = Term.select().where(Term.id == currentTerm).get()
allAYterm = Term.select().where(Term.academicYear == term.academicYear)
if not fileCategory in ["laborHandbook", "volunteerHandbook"]:
abort(405)
dir_path = Path("app/static/files/", fileCategory)
dir_path.mkdir(parents=True, exist_ok=True)
file = request.files[fileCategory]
filename = g.current_term.academicYear + "-" + fileCategory + "." + secure_filename(file.filename).split(".")[-1]
full_path = os.path.join(dir_path, filename)
if os.path.exists(full_path):
os.remove(full_path)
file.save(full_path)
for t in allAYterm:
if fileCategory == "volunteerHandbook":
t.volunteerHandbook = filename
elif fileCategory == "laborHandbook":
t.laborHandbook = filename
else:
abort(405)
t.save()
g.current_term = term
flash(f"Handbook saved successfully to {term.description}!", "success")
return redirect(request.referrer)

@admin_bp.route('/viewRoster/<programID>', methods = ['GET'])
def viewRoster(programID):
program = Program.get_by_id(programID)
interestedUsers = list(getProgramInterest(program))
trainedAndInterested = getTrainingsForInterestedParticipants(programID, interestedUsers)
lastYearsParticipants = getParticipantsForProgramForAY(programID, g.current_term.previousAcademicYear)
currentYearsParticipants = getParticipantsForProgramForAY(programID, g.current_term.academicYear)
return render_template('admin/viewRoster.html',
program = program,
interestedUsers = interestedUsers,
trainedAndInterested = trainedAndInterested,
lastYearsParticipants = lastYearsParticipants,
currentYearsParticipants = currentYearsParticipants
)

@admin_bp.route('/exportRosters/<programID>/<academicYear>', methods = ['GET'])
def exportRosters(programID, academicYear):
try:
outFile = createSpreadsheetForRosters(academicYear, programID)
filepath = os.path.abspath(outFile)
return send_file(filepath, as_attachment=True, download_name=filepath.split("/")[-1], mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
except DoesNotExist:
abort(403)
4 changes: 2 additions & 2 deletions app/controllers/admin/volunteers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from app.models.insuranceInfo import InsuranceInfo
from app.logic.searchUsers import searchUsers
from app.logic.volunteers import updateEventParticipants, getEventLengthInHours, addUserBackgroundCheck, setProgramManager, deleteUserBackgroundCheck
from app.logic.participants import trainedParticipants, addPersonToEvent, getParticipationStatusForTrainings, sortParticipantsByStatus
from app.logic.participants import addPersonToEvent, getParticipationStatusForTrainings, sortParticipantsByStatus
from app.logic.events import getPreviousSeriesEventData, getEventRsvpCount
from app.models.eventRsvp import EventRsvp
from app.models.backgroundCheck import BackgroundCheck
from app.logic.createLogs import createActivityLog, createRsvpLog
from app.logic.users import getBannedUsers, isBannedFromEvent
from app.logic.users import getBannedUsers, isBannedFromEvent, trainedParticipants


@admin_bp.route('/searchVolunteers/<query>', methods = ['GET'])
Expand Down
Loading
Loading