Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f341849
Seemingly fixed issue 1747
ArtemKurasov Jul 16, 2026
0615297
Adding an edge case scenario for graduating students and superseniors
ArtemKurasov Jul 16, 2026
36ef8eb
Correctly displaying previous years reflecting the academic year of a…
ArtemKurasov Jul 17, 2026
552c1e6
Added isCeltsOperationsTeam to user.py model and a business rule that…
ACBerea Aug 5, 2026
38850db
Finished functionality for removing Operation Team members and gave o…
ACBerea Aug 5, 2026
eb9be0a
Enabled event creation for Operation Team members in routes.py, enabl…
ACBerea Aug 6, 2026
083e9b1
Refactored logic for adding and removing OpperationsTeam members in u…
ACBerea Aug 6, 2026
48477ca
fixed the dropdown issue on manage proposal for summer years and year
brightfietsop-ux Aug 6, 2026
9bd4456
Merge pull request #1773 from BCStudentSoftwareDevTeam/development
BrianRamsay Aug 7, 2026
0694aae
New production backup
BrianRamsay Aug 7, 2026
620a82d
Removed unneeded comments.
ACBerea Aug 7, 2026
ce38d53
Resolved Merge Conflicts
ACBerea Aug 7, 2026
ed6cb7a
Improved message for Operations Team Members in CELTS Labor section o…
ACBerea Aug 7, 2026
209006e
Added test suite function for Operation Team in test_userManagement.p…
ACBerea Aug 7, 2026
adb83e3
Fixed error in test suite.
ACBerea Aug 7, 2026
d789454
Removed all instances of isInOperationsTeam, including the property, …
ACBerea Aug 7, 2026
80db683
Added a new function to main routes that handles situations where aut…
ACBerea Aug 7, 2026
8d50226
Fixed position on checkbox and provided a label for the Operations Te…
ACBerea Aug 7, 2026
1409c69
Changed canManagePrograms to canManageProgram.
ACBerea Aug 7, 2026
bd79a13
Merge branch 'development' into 1747_Bright_Artem
brightfietsop-ux Aug 7, 2026
cd909d8
Fixed remaining replacements.
ACBerea Aug 7, 2026
a8925dc
Merge branch 'development' of https://github.com/BCStudentSoftwareDev…
ACBerea Aug 7, 2026
f840316
Fixed left over canManagePrograms to canManageProgram.
ACBerea Aug 7, 2026
b7d02c7
Reverted use of canManageProgram.
ACBerea Aug 7, 2026
4114367
New production backup
BrianRamsay Aug 7, 2026
c8274a2
added a test suit for my function
brightfietsop-ux Aug 7, 2026
42ce124
Merge pull request #1774 from BCStudentSoftwareDevTeam/operationsTeam_AC
BrianRamsay Aug 7, 2026
f00a23f
Seemingly fixed issue 1747
ArtemKurasov Jul 16, 2026
005c3d1
Adding an edge case scenario for graduating students and superseniors
ArtemKurasov Jul 16, 2026
fd09690
Correctly displaying previous years reflecting the academic year of a…
ArtemKurasov Jul 17, 2026
928f1e3
fixed the dropdown issue on manage proposal for summer years and year
brightfietsop-ux Aug 6, 2026
818663c
added a test suit for my function
brightfietsop-ux Aug 7, 2026
ebb2bad
Merge remote-tracking branch 'origin/1747_Bright_Artem' into 1747_Bri…
brightfietsop-ux 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
6 changes: 3 additions & 3 deletions app/controllers/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def templateSelect():

@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST'])
def createEvent(templateid, programid):
if not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerFor(programid)):
if not g.current_user.canManageProgram(programid):
abort(403)

# Validate given URL
Expand Down Expand Up @@ -198,7 +198,7 @@ def createEvent(templateid, programid):
@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
def rsvpLogDisplay(eventId):
event = Event.get_by_id(eventId)
if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
if g.current_user.canManageProgram(event.program):
allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User, on=(EventRsvpLog.createdBy == User.username)).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
return render_template("/events/rsvpLog.html",
event = event,
Expand Down Expand Up @@ -274,7 +274,7 @@ def eventDisplay(eventId):
print(f"Unknown event: {eventId}")
abort(404)

notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
notPermitted = not g.current_user.canManageProgram(event.program)
if 'edit' in request.url_rule.rule and notPermitted:
abort(403)

Expand Down
20 changes: 12 additions & 8 deletions app/controllers/admin/userManagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from app.logic.term import changeCurrentTerm
from app.controllers.admin import admin_bp
from app.logic.fileHandler import FileHandler
from app.logic.userManagement import addCeltsAdmin,addCeltsStudentStaff, createSpreadsheetForRosters,removeCeltsAdmin,removeCeltsStudentStaff
from app.logic.userManagement import addCeltsAdmin,addCeltsStudentStaff,createSpreadsheetForRosters,addCeltsOperationsTeam,removeCeltsAdmin,removeCeltsStudentStaff,removeCeltsOperationsTeam
from app.logic.userManagement import changeProgramInfo
from app.logic.participants import getTrainingsForInterestedParticipants, getParticipantsForProgramForAY
from app.logic.utils import selectSurroundingTerms
Expand Down Expand Up @@ -59,14 +59,19 @@ def manageUsers():
else:
addCeltsStudentStaff(user)
flash(user.firstName + " " + user.lastName + " has been added as a CELTS Student Staff", 'success')
elif method == "addCeltsOperationsTeam":
addCeltsOperationsTeam(user)
flash(user.firstName + " " + user.lastName + " has been added as a CELTS Operations Team member", "success")
elif method == "removeCeltsAdmin":
removeCeltsAdmin(user)
flash(user.firstName + " " + user.lastName + " is no longer a CELTS Admin ", 'success')
elif method == "removeCeltsStudentStaff":
removeCeltsStudentStaff(user)
flash(user.firstName + " " + user.lastName + " is no longer a CELTS Student Staff", 'success')
return ("success")

elif method == "removeCeltsOperationsTeam":
removeCeltsOperationsTeam(user)
flash(user.firstName + " " + user.lastName + " is no longer a CELTS Operations Team member", "success")
return ("success", 200)

@admin_bp.route('/deleteProgramFile', methods=['POST'])
def deleteProgramFile():
Expand All @@ -76,7 +81,7 @@ def deleteProgramFile():

@admin_bp.route('/admin/updateProgramInfo/<programID>', methods=['POST'])
def updateProgramInfo(programID):
if g.current_user.isCeltsAdmin or g.current_user.isProgramManagerFor(programID):
if g.current_user.canManageProgram(programID):
try:
programInfo = request.form # grabs user inputs
uploadedFile = request.files.get('modalProgramImage')
Expand All @@ -92,7 +97,7 @@ def updateProgramInfo(programID):

@admin_bp.route('/admin/getProgramInfo/<programID>', methods = ['GET'])
def getProgramInfo(programID):
if g.current_user.isCeltsAdmin or g.current_user.isProgramManagerFor(programID):
if g.current_user.canManageProgram(programID):
try:
targetProgram = Program.get_by_id(programID)
programInfo = model_to_dict(targetProgram, recurse=False)
Expand Down Expand Up @@ -122,14 +127,13 @@ def userManagement():
.join(User, JOIN.LEFT_OUTER, on=(ProgramManager.user == User.username))
)

if not g.current_user.isCeltsAdmin:
if not g.current_user.isCeltsAdmin and not g.current_user.isCeltsOperationsTeam: #Allows CELTS Operations Team to view all programs.
currentPrograms = currentPrograms.where(ProgramManager.user == g.current_user.username)

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:
if g.current_user.isCeltsAdmin or g.current_user.isProgramManager or g.current_user.isCeltsOperationsTeam:
return render_template('admin/userManagement.html',
terms = terms,
programs = currentPrograms,
Expand Down
10 changes: 5 additions & 5 deletions app/controllers/admin/volunteers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def manageVolunteersPage(eventID):

# ------------ GET request ------------
elif request.method == "GET":
if not (g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerForEvent(event))):
if not (g.current_user.canManageProgram(event.program)):
abort(403)

# ------- Grab the different lists of participants -------
Expand Down Expand Up @@ -98,7 +98,7 @@ def volunteerDetailsPage(eventID):
print(f"No event found for {eventID}", e)
abort(404)

if not (g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerForEvent(event))):
if not g.current_user.canManageProgram(event.program):
abort(403)

eventRsvpData = list(EventRsvp.select(EmergencyContact, InsuranceInfo, EventRsvp)
Expand Down Expand Up @@ -165,7 +165,7 @@ def addVolunteer(eventId):
def rsvpFromWaitlist(username, eventId):
event = Event.get_by_id(eventId)
isProgramManager = g.current_user.isProgramManagerFor(event.program)
if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and isProgramManager):
if g.current_user.canManageProgram(event.program) or isProgramManager:
waitlistUsers = EventRsvp.select(EventRsvp, User).join(User).where(EventRsvp.user == username, EventRsvp.event==eventId).execute()
if (waitlistUsers):
createRsvpLog(event.id, f"Moved {waitlistUsers[0].user.fullName} from waitlist to RSVP.")
Expand Down Expand Up @@ -195,7 +195,7 @@ def removeVolunteerFromEvent():

@admin_bp.route('/addBackgroundCheck', methods = ['POST'])
def addBackgroundCheck():
if g.current_user.isCeltsAdmin:
if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam:
eventData = request.form
user = eventData['user']
bgStatus = eventData['bgStatus']
Expand All @@ -206,7 +206,7 @@ def addBackgroundCheck():

@admin_bp.route('/deleteBackgroundCheck', methods = ['POST'])
def deleteBackgroundCheck():
if g.current_user.isCeltsAdmin:
if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam:
backgroundData = request.form
bgToDelete = BackgroundCheck.get_by_id(backgroundData['bgID'])
session["lastDeletedBgCheck"] = bgToDelete.id
Expand Down
9 changes: 6 additions & 3 deletions app/controllers/minor/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from app.models.term import Term
from app.models.attachmentUpload import AttachmentUpload
from app.logic.fileHandler import FileHandler
from app.logic.utils import selectSurroundingTerms, getFilesFromRequest
from app.logic.utils import selectSurroundingTerms, getFilesFromRequest, selectAllSummerTerms
from app.logic.minor import (
changeProposalStatus,
createOtherEngagement,
Expand Down Expand Up @@ -117,13 +117,16 @@ def createSummerExperienceRequest(username):
createSummerExperience(username, request.form)
flash("Proposal successfully created.", "success")
return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals"))

student = User.get_by_id(username)
year_name = User.rawClassLevel

summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True)
summerTerms = selectAllSummerTerms(g.current_term, student)

return render_template("minor/summerExperience.html",
selectableTerms = summerTerms,
contentAreas = [],
user = User.get_by_id(username),
user = student,
)

@minor_bp.route('/cceMinor/<username>/getEngagementInformation/<type>/<term>/<id>', methods=['GET'])
Expand Down
4 changes: 3 additions & 1 deletion app/logic/searchUsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ def searchUsers(query, category=None):
userWhere = (User.isCeltsAdmin)
elif category == "studentstaff":
userWhere = (User.isCeltsStudentStaff)
elif category == "operationsTeam":
userWhere = (User.isCeltsOperationsTeam)
elif category == "celtsLinkAdmin":
userWhere = (User.isFaculty | User.isStaff | User.isCeltsStudentStaff)
userWhere = (User.isFaculty | User.isStaff | User.isCeltsStudentStaff | User.isCeltsOperationsTeam)
elif category == "all":
userWhere = (True)
else:
Expand Down
20 changes: 17 additions & 3 deletions app/logic/userManagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from playhouse.shortcuts import DoesNotExist, model_to_dict
import xlsxwriter


from app import app
from app.logic.participants import getParticipantsForProgramForAY, getTrainingsForInterestedParticipants
from app.logic.users import getProgramInterest
from app.logic.volunteerSpreadsheet import makeDataXls
from app.models import user
from app.models.user import User
from app.models.term import Term
from app.models.programManager import ProgramManager
Expand All @@ -22,13 +24,19 @@ def addCeltsAdmin(user):
user.save()
createActivityLog(f'Made {user.firstName} {user.lastName} a CELTS admin member.')


def addCeltsStudentStaff(user):
user = User.get_by_id(user)
user.isCeltsStudentStaff = True
user.save()
createActivityLog(f'Made {user.firstName} {user.lastName} a CELTS student staff member.')

def addCeltsOperationsTeam(username):
user = User.get_by_id(username)
if not user.isCeltsStudentStaff:
raise ValueError("A user must be CELTS Student Staff before joining the Operations Team.")
user.isCeltsOperationsTeam = True
user.save()
createActivityLog(f"Made {user.fullName} a CELTS operations team member.")

def removeCeltsAdmin(user):
user = User.get_by_id(user)
Expand All @@ -47,6 +55,12 @@ def removeCeltsStudentStaff(user):
createActivityLog(f'Removed {user.firstName} {user.lastName} from a CELTS student staff member'+
(f', and as a manager of {programManagerRoles}.' if programManagerRoles else "."))

def removeCeltsOperationsTeam(user):
user = User.get_by_id(user)
user.isCeltsOperationsTeam = False
user.save()
createActivityLog(f'Removed {user.firstName} {user.lastName} from CELTS operations team members.')

def changeProgramInfo(programId,
attachment,
programName= None,
Expand Down Expand Up @@ -98,7 +112,7 @@ def changeProgramInfo(programId,

def getAllowedPrograms(currentUser):
"""Returns a list of all visible programs depending on who the current user is."""
if currentUser.isCeltsAdmin:
if currentUser.isCeltsAdmin or currentUser.isCeltsOperationsTeam:
return Program.select().order_by(Program.programName)
else:
return Program.select().join(ProgramManager).where(ProgramManager.user==currentUser).order_by(Program.programName)
Expand All @@ -107,7 +121,7 @@ def getAllowedPrograms(currentUser):

def getAllowedTemplates(currentUser):
"""Returns a list of all visible templates depending on who the current user is. If they are not an admin it should always be none."""
if currentUser.isCeltsAdmin:
if currentUser.isCeltsAdmin or currentUser.isCeltsOperationsTeam:
return EventTemplate.select().where(EventTemplate.isVisible==True).order_by(EventTemplate.name)
else:
return []
Expand Down
38 changes: 37 additions & 1 deletion app/logic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,43 @@ def selectSurroundingTerms(currentTerm, prevTerms=2, summerOnly=False):

return surroundingTerms

def selectAllSummerTerms(currentTerm, student):
"""
Select the summer terms during which a CCE Minor student could be enrolled.

The user record does not store an admission date, so the admission academic
year is inferred from the student's current class level. The range never
extends beyond the student's inferred final school year; students marked as
``Graduating`` are treated as fifth-year/fall-graduating students.
"""
classYears = {
"Freshman": 1,
"Sophomore": 2,
"Junior": 3,
"Senior": 4,
"Graduating": 5,
}

if student.hasGraduated or student.rawClassLevel not in classYears:
return []

classYear = classYears[student.rawClassLevel]
# A Spring/Summer term belongs to the academic year that began the prior fall.
academicYearStart = (currentTerm.year if currentTerm.description.startswith("Fall")
else currentTerm.year - 1)
inferredAdmissionYear = academicYearStart - (classYear - 1)

firstSummer = inferredAdmissionYear + 1
lastSummer = inferredAdmissionYear + max(4, classYear) - 1
if currentTerm.description.startswith("Summer"):
lastSummer = max(lastSummer, currentTerm.year)

return (Term.select()
.where(Term.isSummer,
Term.year >= firstSummer,
Term.year <= lastSummer)
.order_by(Term.termOrder))

def getStartofCurrentAcademicYear(currentTerm):
if ("Summer" in currentTerm.description) or ("Spring" in currentTerm.description):
fallTerm = Term.select().where(Term.year==currentTerm.year-1, Term.description == f"Fall {currentTerm.year-1}").get()
Expand Down Expand Up @@ -97,4 +134,3 @@ def setRedirectTarget(target):
return: None
"""
session["redirectTarget"] = target

6 changes: 4 additions & 2 deletions app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class User(baseModel):
isStaff = BooleanField(default=False)
isCeltsAdmin = BooleanField(default=False)
isCeltsStudentStaff = BooleanField(default=False)
isCeltsOperationsTeam = BooleanField(default=False) # A user MUST be a CELTS Student Staff member to be a CELTS Operations Team member.
dietRestriction = TextField(null=True)
minorInterest = BooleanField(null=True)
hasGraduated = BooleanField(default=False)
Expand Down Expand Up @@ -103,6 +104,9 @@ def isProgramManagerFor(self, program):
def isProgramManagerForEvent(self, event):
# Looks to see who the Program Manager for a specific event is
return self.isProgramManagerFor(event.program)

def canManageProgram(self, program):
return self.isCeltsAdmin or self.isCeltsOperationsTeam or (self.isCeltsStudentStaff and self.isProgramManager(program))

@property
def isProgramManager(self):
Expand All @@ -112,5 +116,3 @@ def isProgramManager(self):
self._isProgramManagerCache = ProgramManager.select().where(ProgramManager.user == self).exists()

return self._isProgramManagerCache


16 changes: 16 additions & 0 deletions app/static/js/userManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ function callbackProgramManager(selected, action = 'add') {
}
}

function operationsTeamCheckboxHandler() {
let isCeltsOperationsTeam = $(this).is(':checked');
let username = $(this).data('username');

$('[data-toggle="tooltip"]').tooltip();

if (isCeltsOperationsTeam) {
submitRequest("addCeltsOperationsTeam", username);
} else {
submitRequest("removeCeltsOperationsTeam", username);
}
}

$(document).ready(function(){

// Admin Management
Expand All @@ -47,6 +60,9 @@ $(document).ready(function(){
$(".removeStudentStaff").on("click",function(){
submitRequest("removeCeltsStudentStaff", $(this).data("username"));
});

$(".operations-team-checkbox").on("change", operationsTeamCheckboxHandler);

$("#programManagersTable").on("click", ".removeProgramManager", function(){
let row = $(this).closest("tr");
let fullName = row.find("td").eq(0).text().trim();
Expand Down
16 changes: 15 additions & 1 deletion app/templates/admin/userManagement.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,27 @@ <h3 class="accordion-header" id="headingOne">
<table class="table mb-3">
<thead>
<tr>
<th colspan="2" scope="col">Current Student Staff</th>
<th scope="col">Current Student Staff</th>
<th scope="col" class="text-center">Operations Team</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for studentStaff in currentStudentStaff %}
<tr>
<td id="{{studentStaff.username}}">{{studentStaff.firstName}} {{studentStaff.lastName}}</td>
<td class="text-center">
<label class="checkbox-inline">
<input
type="checkbox"
class="operations-team-checkbox"
data-username="{{ studentStaff.username }}"
data-toggle="tooltip"
data-placement="top"
title="Check to add or remove this student staff member to the CELTS Operations Team."
{% if studentStaff.isCeltsOperationsTeam %}checked{% endif %}>
</label>
</td>
<td class="text-end"><button data-username="{{studentStaff.username}}" type="button" class="btn btn-danger view removeStudentStaff">Remove</button></td>
</tr>
{% endfor %}
Expand Down
2 changes: 1 addition & 1 deletion app/templates/events/eventNav.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{% endblock %}

{% block navbar %}
{% if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)) %}
{% if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)) or g.current_user.isCeltsOperationsTeam %}

<div class="btn-group">
<ul class="nav nav-tabs nav-fill mx-3 mb-3" id="pills-tab" role="tablist">
Expand Down
Loading
Loading