Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
c16819d
Merge pull request #1741 from BCStudentSoftwareDevTeam/production
BrianRamsay Jul 1, 2026
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
85e9cd0
Initial commit
BhushanSah Jul 16, 2026
31d409e
changed the title of 'Edit Volunteer Information' -> 'Edit Personal...'
fritzj2 Jul 16, 2026
4cab3e9
Changed the dropdown to individual buttons
BhushanSah Jul 16, 2026
ba85efc
Separate zero-hour events into Education and Training section
BhushanSah Jul 16, 2026
180f7b3
Changed wording of Manage Proposals to My Proposals if the user is a …
ACBerea Jul 16, 2026
ac2c7ca
Removed the id categoryDropdown
BhushanSah Jul 16, 2026
d1a3245
Fixed Banned terminology on UI to ineligible and Unbanned to Eligible
nahom70 Jul 16, 2026
31f4f10
Fixed edge case when switching profiles between students causing the …
ACBerea Jul 16, 2026
4410acf
Fixed redunancy in checks user identity checks when changing Manage P…
ACBerea Jul 16, 2026
018bd67
Fix volunteer spreadsheet tests for term and senior results
BhushanSah Jul 17, 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
f400fc6
Merge pull request #1769 from BCStudentSoftwareDevTeam/TestCASE
MImran2002 Jul 20, 2026
ce99e13
Merge branch 'development' into rename-edit-volunt-info
MImran2002 Jul 20, 2026
5603d7b
Merge pull request #1758 from BCStudentSoftwareDevTeam/rename-edit-vo…
MImran2002 Jul 20, 2026
9737b76
Merge branch 'development' into GraduationFilter
MImran2002 Jul 20, 2026
31e2518
Merge pull request #1759 from BCStudentSoftwareDevTeam/GraduationFilter
MImran2002 Jul 20, 2026
1a703e2
Merge branch 'development' into ServiceTranscript
MImran2002 Jul 20, 2026
94fd04c
Merge pull request #1760 from BCStudentSoftwareDevTeam/ServiceTranscript
MImran2002 Jul 20, 2026
f08dcdf
Merge branch 'development' into manageProposeal_ad
MImran2002 Jul 20, 2026
0559624
Merge pull request #1762 from BCStudentSoftwareDevTeam/manageProposea…
MImran2002 Jul 20, 2026
b2fe163
Merge branch 'development' into Banned_terminology
MImran2002 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
23f4697
Merge pull request #1763 from BCStudentSoftwareDevTeam/Banned_termino…
MImran2002 Aug 4, 2026
f5cf734
Merge branch 'development' into signature
BrianRamsay Aug 5, 2026
77bc486
undo default config change
BrianRamsay Aug 5, 2026
cbef82b
Simplify handbook signature
BrianRamsay Aug 6, 2026
bc79154
Clean up handbook uploading
BrianRamsay Aug 6, 2026
6e64700
Make sure session has handbook values, clean up management a little
BrianRamsay Aug 6, 2026
e097096
Small changes
BrianRamsay Aug 7, 2026
be2fd87
Merge pull request #1756 from BCStudentSoftwareDevTeam/signature
BrianRamsay Aug 7, 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
48 changes: 34 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,15 @@ def displayEventFile():
isChecked = fileData.get('checked') == 'true'
eventfile.changeDisplay(fileData['id'], isChecked)
return ""

@admin_bp.route("/handbookSignature", methods=["POST"])
def handbookSignature():
if not (g.current_user.username == request.form.get('studentUsername')):
abort(403)

signer = g.current_user
signer.lastHandbookSignature = datetime.now()
signer.signatureTerm = g.current_term
signer.save()

return ""
98 changes: 84 additions & 14 deletions app/controllers/admin/userManagement.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
from flask import render_template,request, flash, g, abort, redirect, url_for, jsonify
from playhouse.shortcuts import model_to_dict
from peewee import fn, JOIN, DoesNotExist
import os
from pathlib import Path
import re

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


from app import app
from app.logic.term import changeCurrentTerm
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
from app.models.term import Term
from app.models.user import User
from app.models.program import Program

@admin_bp.route('/admin/manageUsers', methods = ['POST'])
def manageUsers():
Expand Down Expand Up @@ -114,28 +125,87 @@ def userManagement():
if not g.current_user.isCeltsAdmin:
currentPrograms = currentPrograms.where(ProgramManager.user == g.current_user.username)

currentPrograms = currentPrograms.group_by(Program.id)

currentPrograms = list(currentPrograms.group_by(Program.id))
currentAdmins = list(User.select().where(User.isCeltsAdmin))
currentStudentStaff = list(User.select().where(User.isCeltsStudentStaff))

if g.current_user.isCeltsAdmin or g.current_user.isProgramManager:
return render_template('admin/userManagement.html',
terms = terms,
programs = list(currentPrograms),
programs = currentPrograms,
currentAdmins = currentAdmins,
currentStudentStaff = currentStudentStaff,
)
abort(403)

@admin_bp.route('/admin/changeTerm', methods=['POST'])
def changeTerm():
termData = request.form
term = int(termData["id"])
changeCurrentTerm(term)
newTerm = changeCurrentTerm(int(request.form.get('id')))
flash(f"Current term successfully changed to {newTerm.description}", "success")

return ""

@admin_bp.route('/admin/addNewTerm', methods = ['POST'])
def addNewTerm():
addNextTerm()
flash("New term added", "success")
newTerm = addNextTerm()
flash(f"Successfully added {newTerm.description}", "success")

return ""

@admin_bp.route('/upload/<userFileCategory>/<userTermId>', methods = ['POST'])
def upload(userFileCategory, userTermId):
try:
handbookTerm = Term.get_by_id(userTermId)
except DoesNotExist:
abort(405)

if userFileCategory not in ["laborHandbook", "volunteerHandbook"]:
abort(405)
fileCategory = userFileCategory

# Save file to fs
file = request.files[fileCategory]
newFilename = g.current_term.academicYear + "-" + fileCategory + "." + secure_filename(file.filename).split(".")[-1]

dir_path = Path(app.config['files']['base_path'], fileCategory)
dir_path.mkdir(parents=True, exist_ok=True)
full_path = os.path.join(dir_path, newFilename)
if os.path.exists(full_path):
os.remove(full_path)
file.save(full_path)

# Update all terms in the Academic Year with the handbook filename
for ayTerm in Term.select().where(Term.academicYear == handbookTerm.academicYear):
setattr(ayTerm, fileCategory, newFilename)
ayTerm.save()

# refresh the session with the new object
changeCurrentTerm(g.current_term, refreshOnly=True)

flash(f"Handbook saved successfully to {ayTerm.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(program, interestedUsers)
lastYearsParticipants = getParticipantsForProgramForAY(program, g.current_term.previousAcademicYear)
currentYearsParticipants = getParticipantsForProgramForAY(program, 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