Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
dad0a49
Reverted the merges
ArtemKurasov Jul 20, 2026
0684323
Got all of the changes from the Manage_Members_Bright_Artem branch
ArtemKurasov Jul 20, 2026
8bcd89b
made the portal to work
brightfietsop-ux Jul 20, 2026
24d7ca1
made the isBanned banner to display a popup
brightfietsop-ux Jul 20, 2026
194ff1c
Fixed the Assign Coordinator switch + moved some of the logic to mana…
ArtemKurasov Jul 21, 2026
52b7efc
Used functions from app/logic/manageMembers..py
ArtemKurasov Jul 21, 2026
c2052a7
removed a duplicate function
brightfietsop-ux Jul 21, 2026
a649439
broke down the mangeMembers into samller functions and moved it app/l…
brightfietsop-ux Jul 21, 2026
6a8185a
added some test cases
brightfietsop-ux Jul 22, 2026
a4648dd
fixed the broken import statemnets
brightfietsop-ux Jul 22, 2026
42e0e7c
Add tests for manage members helper functions
BhushanSah Jul 22, 2026
01baa28
Add tests for manage members helper logic
BhushanSah Jul 22, 2026
01ee63a
removed the changes from supervisor.py which was out of scope
BhushanSah Jul 22, 2026
162400e
Removed the extra line
BhushanSah Jul 22, 2026
f04fbce
Removed the code which was out of scope
BhushanSah Jul 22, 2026
4f72b3f
Removed the file out of scope
BhushanSah Jul 22, 2026
c089049
deleted empty file
brightfietsop-ux Jul 22, 2026
7950862
Removed the file out of scope
BhushanSah Jul 22, 2026
6979e82
Merge branch 'Bright_Artem_manage_people' of https://github.com/BCStu…
brightfietsop-ux Jul 22, 2026
647ba11
Removed the file out of scope
BhushanSah Jul 22, 2026
28bee64
Merge branch 'Bright_Artem_manage_people' of https://github.com/BCStu…
brightfietsop-ux Jul 22, 2026
322f1fd
Removed the extra spaces
BhushanSah Jul 22, 2026
c9e7871
Delete app/static/js/addSupervisorsToDepartment.js
brightfietsop-ux Jul 22, 2026
436198f
Made it same as before
BhushanSah Jul 22, 2026
97d0460
Made it same as before as it was out of scope
BhushanSah Jul 22, 2026
28490eb
Removed the unnecessary changes
BhushanSah Jul 22, 2026
0ee4970
Removed Unneccessary spaces
BhushanSah Jul 22, 2026
a982310
Removed space
BhushanSah Jul 22, 2026
2fa25e9
Added space same as before
BhushanSah Jul 22, 2026
c10d0fe
Removed unnecessary function
BhushanSah Jul 22, 2026
37b715f
Merge branch 'Bright_Artem_manage_people' of https://github.com/BCStu…
brightfietsop-ux Jul 22, 2026
08ef1dd
added test cases for our functions
brightfietsop-ux Jul 22, 2026
f5a6924
edited the message and then made it to fade out after 5seconds
brightfietsop-ux Jul 22, 2026
fd37b48
Handle missing user in manage members route
BhushanSah Jul 27, 2026
6fd3441
Fix manage members template context and markup
BhushanSah Jul 27, 2026
3aaee60
Restore shared supervisor department helper
BhushanSah Jul 27, 2026
39df63d
Clean up manage members PR scope
BhushanSah Jul 27, 2026
e00b702
Address manage members review feedback
BhushanSah Jul 27, 2026
76b2320
Address manage members review feedback
BhushanSah Jul 27, 2026
1a923d3
Clean up manage members review changes
BhushanSah Jul 27, 2026
e824434
Clean up manage members review changes
BhushanSah Jul 27, 2026
baa19f1
Clean up manage members changes
BhushanSah Jul 27, 2026
5e02f2c
Use existing search helper for manage members
BhushanSah Jul 27, 2026
ea2e6eb
Keep manage members search closed on load
BhushanSah Jul 27, 2026
10bc99e
Fixed the test
BhushanSah Jul 27, 2026
300e18f
Implemented a popover for removing members
brightfietsop-ux Jul 28, 2026
f648de8
refactored the formatting of the code
brightfietsop-ux Jul 28, 2026
7e4d743
Resolved merge conflict and connected our page to the view all member…
brightfietsop-ux Jul 28, 2026
fd16541
Address manage members review comments
BhushanSah Jul 29, 2026
5de6421
Changed g.currentUser to currentUser
BhushanSah Jul 29, 2026
e3ddeb4
Address remaining manage members review comments
BhushanSah Jul 29, 2026
39537e4
Update manage members test coverage
BhushanSah Jul 29, 2026
21dacc7
fixed init.py
BhushanSah Jul 29, 2026
8bfaf02
Add current year context variable
BhushanSah Jul 29, 2026
1ae75a2
Removed Space
BhushanSah Jul 29, 2026
a869602
Allow department supervisors to access manage members
BhushanSah Jul 29, 2026
5d58f3f
Merged department-portal-base
BhushanSah Jul 29, 2026
f90d754
optimised addUserToDept
BhushanSah Jul 29, 2026
9a0112a
Updated js file of addUsertoDept
BhushanSah Jul 29, 2026
c6b1424
Fixed error on departmentportal.py
BhushanSah Jul 30, 2026
09346c3
fixed the failing test issues and then properly connected the button …
brightfietsop-ux Jul 30, 2026
a083395
changed the flash color msg
brightfietsop-ux Jul 30, 2026
13b50ce
Updated test files
BhushanSah Jul 30, 2026
d5f461c
Merge department-portal-base into Bright_Artem_manage_people
BhushanSah Jul 30, 2026
393d9de
made the checks so that labor office and labor students can manage de…
brightfietsop-ux Jul 30, 2026
4723ec7
Removed supervisorsDbToDict() and reused buildSupervisorDisplay()
brightfietsop-ux Jul 30, 2026
cd508c4
Removed duplicate helper function getCurrentDeptMembers()
brightfietsop-ux Jul 30, 2026
eff7a57
added a test for getSupervisorDepartments
brightfietsop-ux Jul 30, 2026
0971c55
removed the confirmation popup before a member gets removed
brightfietsop-ux Jul 30, 2026
661ff17
added my name to the contributor list
brightfietsop-ux Jul 30, 2026
bc46dae
Merge pull request #661 from BCStudentSoftwareDevTeam/Bright-contribu…
brightfietsop-ux Jul 30, 2026
1e234fe
changed the flasher fot consistnecy
brightfietsop-ux Jul 31, 2026
d2bda86
Changed supervisor ineligible button to toggle
BhushanSah Jul 31, 2026
9dc49ae
Merge department-portal-base into Bright_Artem_manage_people
BhushanSah Jul 31, 2026
813362e
Address manage members review feedback
BhushanSah Jul 31, 2026
386decf
Refine manage members styling and tests
BhushanSah Jul 31, 2026
e805d6f
Removed Comment
BhushanSah Jul 31, 2026
33a891f
Update manage members permissions
BhushanSah Jul 31, 2026
1be130c
Fixed the typo error
BhushanSah Jul 31, 2026
17919f5
Removed a console.log error msg
brightfietsop-ux Aug 1, 2026
f6a5a09
Merge department-portal-base into Bright_Artem_manage_people
BhushanSah Aug 3, 2026
09d5b6e
merge
BhushanSah Aug 3, 2026
efa9b90
merge
BhushanSah Aug 3, 2026
a0fe036
Fix manage members responsive layout
BhushanSah Aug 3, 2026
e4436c8
Align manage members action controls
BhushanSah Aug 3, 2026
faef000
Fix manage members active and pending counts
BhushanSah Aug 3, 2026
6d68125
Refactored according to Imran commet
BhushanSah Aug 3, 2026
2c4425e
Renamed variables
BhushanSah Aug 3, 2026
c506a25
fixed the logic
BhushanSah Aug 3, 2026
2eb341f
Remove demo data changes from manage members PR
BhushanSah Aug 4, 2026
5dc2048
Fixed Toggle
BhushanSah Aug 4, 2026
3c9c643
used g.currentYear, made pewee command as single one and also updated…
BhushanSah Aug 4, 2026
6ef0131
Refresh cached user when login changes
BhushanSah Aug 4, 2026
075f554
Made pewee command into one line
BhushanSah Aug 4, 2026
51ee08a
Made pewee command into one line
BhushanSah Aug 4, 2026
ab9f802
Fixed Bug
BhushanSah Aug 4, 2026
705cda1
Merge branch 'department-portal-base', remote-tracking branch 'origin…
BhushanSah Aug 4, 2026
35dbd4d
Update contributors.yaml by removing and editing entries
BhushanSah Aug 4, 2026
ea760b5
Restore contributors.yaml
BhushanSah 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
46 changes: 35 additions & 11 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os

from flask import Flask
from flask_restful import Api
from datetime import date
from flask import Flask, g, request, session
from flask_bootstrap import Bootstrap
from playhouse.shortcuts import model_to_dict, dict_to_model
from flask_restful import Api
from playhouse.shortcuts import dict_to_model, model_to_dict


app = Flask(__name__)
Expand Down Expand Up @@ -55,17 +55,27 @@ def new_execute(*args, **kwargs):
from app.controllers.api_routes.routes import initializeApiRoutes
initializeApiRoutes(api)

from flask import g
from app.models.user import User
from app.login_manager import require_login
from app.login_manager import getUsernameFromEnv, require_login
@app.before_request
def load_user():
try:
g.currentUser = dict_to_model(User, session['currentUser'])
requestUsername = getUsernameFromEnv(request.environ)
try:
cachedUser = session['currentUser']

if cachedUser.get('username') == requestUsername:
g.currentUser = dict_to_model(User, cachedUser)
return

session.pop('currentUser', None)
session.pop('username', None)

except Exception as e:
user = require_login()
session['currentUser'] = model_to_dict(user)
g.currentUser = user
pass

user = require_login()
session['currentUser'] = model_to_dict(user)
g.currentUser = user

from app.models.term import Term
from app.login_manager import getOpenTerm
Expand All @@ -79,6 +89,20 @@ def load_openTerm():
session['openTerm'] = model_to_dict(term)
g.openTerm = term

def getCurrentYear():
today = date.today()
Comment thread
BhushanSah marked this conversation as resolved.
year = today.year

if today.month < 7:
return year - 1, year

return year, year + 1

@app.before_request
def load_currentYear():
g.currentYear = getCurrentYear()


@app.context_processor
def inject_environment():
return dict(env=app.config['ENV'])
Expand Down
1 change: 1 addition & 0 deletions app/controllers/main_routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def injectGlobalData():
'lastStaticUpdate': lastStaticUpdate}

from app.controllers.main_routes import main_routes
from app.controllers.main_routes import departmentPortal
from app.controllers.main_routes import laborStatusForm
from app.controllers.main_routes import laborHistory
from app.controllers.main_routes import alterLSF
Expand Down
172 changes: 167 additions & 5 deletions app/controllers/main_routes/departmentPortal.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
from flask import render_template, g
from flask import abort, g, jsonify, redirect, render_template, request, url_for
from peewee import DoesNotExist

from app.controllers.main_routes import main_bp
from app.logic.getPositions import getPositions
from peewee import DoesNotExist
from app.logic.getSupervisors import buildSupervisorDisplay, getSupervisorDepartments
from app.logic.manageMembers import attachPositionCounts, getActivePendingPositionCounts
from app.logic.search import searchPerson
from app.models.department import Department
from app.models.supervisor import Supervisor
from app.models.supervisorDepartment import SupervisorDepartment


@main_bp.route('/department/<org>/<account>/members', methods=['GET'])
def manageMembers(org=None, account=None):
"""Generates the Manage Members page."""
currentUser = g.currentUser

if not currentUser.supervisor:
Comment thread
MImran2002 marked this conversation as resolved.
return redirect(url_for('main.laborhistory', id=currentUser.student.ID))

dept = Department.get_or_none(Department.ORG == org, Department.ACCOUNT == account)

if not dept:
abort(404)

departmentMembers = getSupervisorDepartments(dept)
supervisorDeptRecord = SupervisorDepartment.get_or_none(supervisor=currentUser.supervisor, department=dept)

if not ( currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or supervisorDeptRecord):
return render_template('errors/403.html'), 403

activePendingPositionCounts = getActivePendingPositionCounts(dept, g.currentYear)
departmentMembers = attachPositionCounts(departmentMembers, activePendingPositionCounts)

return render_template(
'main/manageMembers.html',
members=departmentMembers,
department=dept,
)

@main_bp.route('/department/<org>/<account>/positions', methods=['GET'])
Comment thread
BhushanSah marked this conversation as resolved.
def managePositions(org, account):
try:
Expand All @@ -22,7 +56,135 @@ def managePositions(org, account):
positions = getPositions(dept)

return render_template('main/managePositions.html',
department = dept,
department_name = dept.DEPT_NAME,
positions = positions
department=dept,
department_name=dept.DEPT_NAME,
positions=positions
)


@main_bp.route('/members/search/<query>', methods=['GET'])
def searchMember(query=None):
Comment thread
MImran2002 marked this conversation as resolved.
"""Search supervisors by name or B-number."""
currentUser = g.currentUser

if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or currentUser.supervisor ):
return render_template('errors/403.html'), 403

supervisors = (
searchPerson(Supervisor, query)
.order_by(Supervisor.LAST_NAME.asc())
.limit(10)
)

supervisors = [buildSupervisorDisplay(supervisor) for supervisor in supervisors]
supervisors = [supervisor for supervisor in supervisors if supervisor is not None]

return jsonify(supervisors)


@main_bp.route('/members/update_coordinator', methods=['POST'])
def updateCoordinator():
"""Assigns or unassigns a supervisor as a Labor Coordinator."""
currentUser = g.currentUser

supervisorID = request.form.get("supervisorID")
departmentID = request.form.get("departmentID")
isCoordinator = request.form.get("isCoordinator") == "true"

if not supervisorID or not departmentID:
return "", 400

supervisorDeptRecord = None

if currentUser.supervisor:
supervisorDeptRecord = SupervisorDepartment.get_or_none( (SupervisorDepartment.supervisor == currentUser.supervisor) & (SupervisorDepartment.department == departmentID))

if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or supervisorDeptRecord ):
return render_template('errors/403.html'), 403

member = SupervisorDepartment.get((SupervisorDepartment.supervisor == supervisorID) & (SupervisorDepartment.department == departmentID))

member.isCoordinator = isCoordinator
member.save()

return "", 200


@main_bp.route('/members/update_eligibility', methods=['POST'])
def updateEligibility():
"""Updates a supervisor's eligibility status."""
currentUser = g.currentUser

if not ( currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or currentUser.supervisor):
return render_template('errors/403.html'), 403

supervisorID = request.form.get("supervisorID")

if not supervisorID:
return "", 400

member = Supervisor.get(Supervisor.ID == supervisorID)
member.isBanned = not member.isBanned
member.save()
Comment thread
BhushanSah marked this conversation as resolved.

return "", 200


@main_bp.route('/members/remove', methods=['DELETE'])
def removeMember():
"""Removes a staff member from a department."""
currentUser = g.currentUser

if not ( currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or currentUser.supervisor):
return render_template('errors/403.html'), 403

supervisorID = request.form.get("supervisorID")
departmentID = request.form.get("departmentID")

if not supervisorID or not departmentID:
return "", 400

member = SupervisorDepartment.get((SupervisorDepartment.supervisor == supervisorID) & (SupervisorDepartment.department == departmentID))

member.delete_instance()

return "", 200


@main_bp.route('/members/add', methods=['POST'])
def addUserToDept():
"""Adds a user to a department."""
currentUser = g.currentUser
supervisorID = request.form.get("supervisorID")
departmentID = request.form.get("departmentID")

if not supervisorID or not departmentID:
return jsonify(success=False, message="Missing supervisor or department."), 400

if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent or currentUser.supervisor):
return render_template('errors/403.html'), 403

try:
supervisor = Supervisor.get_or_none(Supervisor.ID == supervisorID)

if not supervisor:
return jsonify(success=False, message="Supervisor not found."), 404

supervisorDeptRecord = SupervisorDepartment.get_or_none( supervisor=supervisor, department=departmentID )

if supervisorDeptRecord:
return jsonify(
success=False,
message="Supervisor already exists in this department."
), 200

SupervisorDepartment.create( supervisor=supervisor, department=departmentID)

return jsonify(success=True, message="Supervisor added to department."), 200

except Exception as e:
print(f'Could not add user to department: {e}')
return jsonify(
success=False,
message="Could not add supervisor to department."
), 500
10 changes: 7 additions & 3 deletions app/controllers/main_routes/main_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ def supervisorPortal():
@main_bp.route('/department/<org>/<account>', methods=['GET'])
def departmentPortal(org=None,account=None):
currentUser = g.currentUser
try:
dept = Department.get(Department.ORG == org, Department.ACCOUNT == account)
except (NameError, DoesNotExist):
if org and account:
try:
dept = Department.get(Department.ORG == org, Department.ACCOUNT == account)
except (NameError, DoesNotExist):
dept = None
else:
dept = None


if currentUser.isLaborAdmin:
departments = list(Department.select().order_by(Department.isActive.desc(), Department.DEPT_NAME.asc()))
Expand Down
80 changes: 51 additions & 29 deletions app/logic/getSupervisors.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
from app.logic.search import usernameFromEmail
from app.models.supervisor import Supervisor
from app.models.supervisorDepartment import SupervisorDepartment
from peewee import fn



def buildSupervisorDisplay(supervisor):
try:
firstName = supervisor.FIRST_NAME
lastName = supervisor.LAST_NAME
if not (firstName and lastName):
raise NameError("First name and last name are required")
"""Build supervisor data used by portal displays and search results."""
firstName = (
supervisor.preferred_name or
supervisor.legal_name or
""
).strip()
lastName = (supervisor.LAST_NAME or "").strip()
email = supervisor.EMAIL.strip() if supervisor.EMAIL else None

except NameError as e:
if not firstName or not lastName:
return None

return {
"name": f"{firstName} {lastName}".strip(),
"email": supervisor.EMAIL
"name": f"{firstName} {lastName}",
"email": email,
"username": usernameFromEmail(email) if email else None,
"firstName": firstName,
"lastName": lastName,
"bnumber": supervisor.ID.strip(),
"department": (supervisor.DEPT_NAME or "").strip(),
"type": "Supervisor"
}
def getSupervisors(dept):


def getSupervisorDepartments(dept):
"""Return supervisor-department records for a department."""
if dept is None:
return []

return list(
SupervisorDepartment
.select(SupervisorDepartment, Supervisor)
.join(Supervisor)
.where(SupervisorDepartment.department == dept)
.order_by(Supervisor.LAST_NAME.asc())
)


def getSupervisors(dept):
laborCoordinators = []
supervisors = []

# Avoid querying department members unless the selected department exists.
if dept is not None:
supervisorDepartments = (SupervisorDepartment.select().join(Supervisor).where(SupervisorDepartment.department == dept)
.order_by(Supervisor.LAST_NAME.asc()))
for supervisorDepartment in supervisorDepartments:
supervisor = supervisorDepartment.supervisor
if supervisor is None:
continue

supervisorDisplay = buildSupervisorDisplay(supervisor)
if not supervisorDisplay:
continue

if supervisorDepartment.isCoordinator:
laborCoordinators.append(supervisorDisplay)
else:
supervisors.append(supervisorDisplay)

for supervisorDepartment in getSupervisorDepartments(dept):
supervisor = supervisorDepartment.supervisor

if supervisor is None:
continue

supervisorDisplay = buildSupervisorDisplay(supervisor)

if not supervisorDisplay:
continue

if supervisorDepartment.isCoordinator:
laborCoordinators.append(supervisorDisplay)
else:
supervisors.append(supervisorDisplay)

return supervisors, laborCoordinators
Loading