Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 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
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
26 changes: 19 additions & 7 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 @@ -97,7 +98,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 @@ -114,8 +115,9 @@ def createEvent(templateid, programid):
if request.method == "POST":
savedEvents = None
eventData.update(request.form.copy())
print(">>>>>>>> createEvent before preprocess 1: ", list(Event.select().where(Event.isCeltsTraining)))
eventData = preprocessEventData(eventData)

print(">>>>>>>> createEvent after preprocess 1: ", list(Event.select().where(Event.isCeltsTraining)))
if eventData.get('isSeries'):
eventData['seriesData'] = json.loads(eventData['seriesData'])
succeeded, savedEvents, failedSavedOfferings = attemptSaveMultipleOfferings(eventData, getFilesFromRequest(request))
Expand All @@ -130,7 +132,6 @@ def createEvent(templateid, programid):
except Exception as e:
print("Failed saving regular event", e)
validationErrorMessage = "Failed to save event."

if savedEvents:
rsvpCohorts = request.form.getlist("cohorts[]")
if rsvpCohorts:
Expand Down Expand Up @@ -180,8 +181,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 @@ -289,7 +288,7 @@ 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))

Expand Down Expand Up @@ -682,3 +681,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)
62 changes: 59 additions & 3 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, 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 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,71 @@ 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", "backgroundForm"]:
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
elif fileCategory == "backgroundForm":
t.backgroundForm = 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
)

55 changes: 47 additions & 8 deletions app/controllers/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from http import cookies
from playhouse.shortcuts import model_to_dict
from flask import request, render_template, jsonify, g, abort, flash, redirect, url_for, make_response, session, request
from dateutil.relativedelta import relativedelta


from app.controllers.main import main_bp
from app import app
Expand Down Expand Up @@ -36,7 +38,7 @@
from app.logic.certification import getCertRequirementsWithCompletion
from app.logic.landingPage import getManagerProgramDict, getActiveEventTab
from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal
from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent
from app.logic.participants import hasGoneToTraining, unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent
from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo

@main_bp.route('/logout', methods=['GET'])
Expand Down Expand Up @@ -196,7 +198,6 @@ def viewUsersProfile(username):
allBackgroundHistory = getUserBGCheckHistory(volunteer)
backgroundTypes = list(BackgroundCheckType.select())



eligibilityTable = []

Expand Down Expand Up @@ -233,6 +234,10 @@ def viewUsersProfile(username):
managersProgramDict = getManagerProgramDict(g.current_user)
managersList = [id[1] for id in managersProgramDict.items()]
totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer))
handbookOverdue = getHandbookStatus(volunteer)
currentTerm = Term.get(Term.isCurrentTerm)

training = hasGoneToTraining(g.current_user, g.current_term)

return render_template ("/main/userProfile.html",
username=username,
Expand All @@ -252,9 +257,19 @@ def viewUsersProfile(username):
managersList = managersList,
participatedInLabor = getCeltsLaborHistory(volunteer),
totalSustainedEngagements = totalSustainedEngagements,
handbookOverdue = handbookOverdue,
training = training,
currentTerm = currentTerm
)
abort(403)

def getHandbookStatus(volunteer):
handbookOverdue = False

if not volunteer.signatureTerm or volunteer.signatureTerm.academicYear != g.current_term.academicYear:
handbookOverdue = True
return handbookOverdue

@main_bp.route('/profile/<username>/emergencyContact', methods=['GET', 'POST'])
def emergencyContactInfo(username):
"""
Expand Down Expand Up @@ -452,21 +467,24 @@ def unban(program_id, username):
flash("Failed to unban the volunteer", "danger")
return "Failed to unban the volunteer", 500


@main_bp.route('/<username>/addInterest/<program_id>', methods=['POST'])
def addInterest(program_id, username):
@main_bp.route('/<username>/addInterest/<program_id>/<showFlash>', methods=['POST'])
def addInterest(program_id, username, showFlash = True):
"""
This function adds a program to the list of programs a user interested in
program_id: the primary id of the program the student is adding interest of
username: unique value of a user to correctly identify them
"""
"""
showFlash = False if showFlash == "False" else True
try:
success = addUserInterest(program_id, username)
if success:
flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
return ""
if bool(showFlash):
flash("Successfully added " + Program.get_by_id(program_id).programName + " as an interest", "success")
return jsonify(model_to_dict(User.get_or_none(User.username == username)))
else:
flash("Was unable to remove " + Program.get_by_id(program_id).programName + " as an interest.", "danger")
if bool(showFlash):
flash("Was unable to add " + Program.get_by_id(program_id).programName + " as an interest.", "danger")

except Exception as e:
print(e)
Expand Down Expand Up @@ -643,3 +661,24 @@ def updateMinorDeclaration(username):
tab = request.args.get("tab", "interested")
return redirect(url_for('admin.manageMinor', tab=tab))

@main_bp.route('/extravaganza', methods=['GET'])
def extravaganza():
programs = Program.select().where(Program.isOtherCeltsSponsored == False,
Program.programName != "Hunger Initiatives",
Program.programName != "Bonner Scholars")
interests = Interest.select(Interest, Program).join(Program).where(Interest.user == g.current_user)
programsInterested = [interest.program for interest in interests]

upcomingAllVolunteers = Event.select().join(Term).where(Event.isAllVolunteerTraining, Term.academicYear == g.current_term.academicYear)
upcomingTrainings = Event.select().join(Term).where(Event.isTraining, Term.academicYear == g.current_term.academicYear)

for training in upcomingTrainings:
training.startDate = training.startDate.strftime("%b %d")
training.timeStart = training.timeStart.strftime("%I:%M %p")

return render_template("main/extravanganzaWelcome.html",
programs = programs,
programsInterested = programsInterested,
upcomingTrainings = upcomingTrainings,
upcomingAllVolunteers = upcomingAllVolunteers
)
Loading