Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
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
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
42ce124
Merge pull request #1774 from BCStudentSoftwareDevTeam/operationsTeam_AC
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
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
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
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
25 changes: 18 additions & 7 deletions app/templates/main/userProfile.html
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ <h5>CELTS Labor History:</h5>
{% endfor %}
</div>
{% endif %}
{% if volunteer.isCeltsStudentStaff %}
{% if volunteer.isCeltsStudentStaff and not volunteer.isCeltsOperationsTeam %}
<div class="col-md-6">
<h5>{{volunteer.firstName}} {{volunteer.lastName}} is the current manager of:</h5>
<ul class="list-unstyled">
Expand All @@ -310,6 +310,17 @@ <h5>{{volunteer.firstName}} {{volunteer.lastName}} is the current manager of:</h
{% endif %}
</div>
{% endif %}
{% if volunteer.isCeltsOperationsTeam %}
<div class="col-md-6">
<h5>Operations Team Member:</h5>
<p>{{volunteer.firstName}} {{volunteer.lastName}} is currently an Operations Team member who manages all programs.</p>
</div>
<div>
{% if g.current_user.isCeltsAdmin %}
<a href="/admin?accordion=program" class="btn btn-primary btn-sm">Update Program Managers</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
Expand All @@ -332,7 +343,7 @@ <h3 class="accordion-header" id="headingFive">
<thead>
<tr>
<th scope="col">Description</th>
{% if g.current_user.isCeltsAdmin %}
{% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %}
<th class="required" scope="col">Status</th>
<th class="required" scope="col">Date</th>
<th class="" id="displaySave" scope="col"></th>
Expand All @@ -342,7 +353,7 @@ <h3 class="accordion-header" id="headingFive">
<tbody>
{% set ns = namespace(backgroundChecksExist=false) %}
{% for bgType in backgroundTypes %}
{% if not g.current_user.isCeltsAdmin and not allBackgroundHistory[bgType.id] %}
{% if not g.current_user.isCeltsAdmin and not g.current_user.isCeltsOperationsTeam and not allBackgroundHistory[bgType.id] %}
{% continue %}
{% endif %}
{% set ns.backgroundChecksExist = true %}
Expand All @@ -356,7 +367,7 @@ <h6>{{bgType.description}}</h6>
{{ bgStatus.backgroundCheckStatus }}:
{{ bgStatus.dateCompleted.strftime("%m/%d/%Y") }}
</span>
{% if g.current_user.isCeltsAdmin %}
{% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %}
<button
class="btn btn-sm btn-link text-danger p-0"
id="deleteBgHistory"
Expand All @@ -369,7 +380,7 @@ <h6>{{bgType.description}}</h6>
{% endfor %}
</ul>
</td>
{% if g.current_user.isCeltsAdmin %}
{% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %}
<td>
<select class="passedBackgroundCheck form-select" id="{{bgType.id}}" data-id="{{bgType.id}}">
<option value=''></option>
Expand Down Expand Up @@ -419,7 +430,7 @@ <h3 class="accordion-header" id="headingSix">
</tr>
</thead>
<tbody>
{% if g.current_user.isCeltsAdmin %}
{% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %}
{% set userTier = 3 %}
{% elif g.current_user.isCeltsStudentStaff %}
{% set userTier = 2 %}
Expand All @@ -443,7 +454,7 @@ <h3 class="accordion-header" id="headingSix">
{{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}}
{% endif %}
<td class ="text-nowrap">
{% if (g.current_user == row.note.createdBy) or g.current_user.isCeltsAdmin%}
{% if (g.current_user == row.note.createdBy) or g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %}
<button type="button" class="btn btn-sm btn-primary me-1 editNoteButton"data-noteid="{{ row.id }}"data-notetext="{{ row.note.noteContent }}"data-username="{{ volunteer.username }}"data-visibility="{{ row.viewTier }}"data-bonner="{{ 'yes' if row.isBonnerNote else 'no' }}">Edit</button>
<button type="button" class="btn btn-sm btn-danger deleteNoteButton" data-noteid="{{ row.id}}" data-username="{{volunteer.username}}">Delete</button>
{% else %}
Expand Down
Loading
Loading