diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 8077422ff..5fc6ee7be 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -90,7 +90,7 @@ def templateSelect(): @admin_bp.route('/eventTemplates///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 @@ -198,7 +198,7 @@ def createEvent(templateid, programid): @admin_bp.route('/event//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, @@ -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) diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index a7bfa7496..37fe14916 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -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 @@ -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(): @@ -76,7 +81,7 @@ def deleteProgramFile(): @admin_bp.route('/admin/updateProgramInfo/', 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') @@ -92,7 +97,7 @@ def updateProgramInfo(programID): @admin_bp.route('/admin/getProgramInfo/', 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) @@ -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, diff --git a/app/controllers/admin/volunteers.py b/app/controllers/admin/volunteers.py index 7df4e649f..0b23a9455 100644 --- a/app/controllers/admin/volunteers.py +++ b/app/controllers/admin/volunteers.py @@ -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 ------- @@ -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) @@ -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.") @@ -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'] @@ -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 diff --git a/app/logic/searchUsers.py b/app/logic/searchUsers.py index 92ee2c76a..a4bc829a3 100644 --- a/app/logic/searchUsers.py +++ b/app/logic/searchUsers.py @@ -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: diff --git a/app/logic/userManagement.py b/app/logic/userManagement.py index 4eb75855a..59155d968 100644 --- a/app/logic/userManagement.py +++ b/app/logic/userManagement.py @@ -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 @@ -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) @@ -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, @@ -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) @@ -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 [] diff --git a/app/models/user.py b/app/models/user.py index 211acadfa..ad4696cc1 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -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) @@ -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): @@ -112,5 +116,3 @@ def isProgramManager(self): self._isProgramManagerCache = ProgramManager.select().where(ProgramManager.user == self).exists() return self._isProgramManagerCache - - diff --git a/app/static/js/userManagement.js b/app/static/js/userManagement.js index f5dbd111c..274453dd9 100644 --- a/app/static/js/userManagement.js +++ b/app/static/js/userManagement.js @@ -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 @@ -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(); diff --git a/app/templates/admin/userManagement.html b/app/templates/admin/userManagement.html index dc29fb655..fe97b48d4 100644 --- a/app/templates/admin/userManagement.html +++ b/app/templates/admin/userManagement.html @@ -97,13 +97,27 @@

- + + + {% for studentStaff in currentStudentStaff %} + {% endfor %} diff --git a/app/templates/events/eventNav.html b/app/templates/events/eventNav.html index fb480c46a..e695ac243 100644 --- a/app/templates/events/eventNav.html +++ b/app/templates/events/eventNav.html @@ -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 %}
{% endif %} - {% if volunteer.isCeltsStudentStaff %} + {% if volunteer.isCeltsStudentStaff and not volunteer.isCeltsOperationsTeam %}
{{volunteer.firstName}} {{volunteer.lastName}} is the current manager of:
    @@ -310,6 +310,17 @@
    {{volunteer.firstName}} {{volunteer.lastName}} is the current manager of: {% endif %} + {% if volunteer.isCeltsOperationsTeam %} +
    +
    Operations Team Member:
    +

    {{volunteer.firstName}} {{volunteer.lastName}} is currently an Operations Team member who manages all programs.

    +
    +
    + {% if g.current_user.isCeltsAdmin %} + Update Program Managers + {% endif %} +
    + {% endif %}
@@ -332,7 +343,7 @@

- {% if g.current_user.isCeltsAdmin %} + {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %} @@ -342,7 +353,7 @@

{% 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 %} @@ -356,7 +367,7 @@
{{bgType.description}}
{{ bgStatus.backgroundCheckStatus }}: {{ bgStatus.dateCompleted.strftime("%m/%d/%Y") }} - {% if g.current_user.isCeltsAdmin %} + {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %} - {% 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 %} @@ -443,7 +454,7 @@

{{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}} {% endif %}

Current Student StaffCurrent Student StaffOperations Team
{{studentStaff.firstName}} {{studentStaff.lastName}} + +
DescriptionStatus Date
- {% 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 %} {% else %} diff --git a/app/templates/sidebar.html b/app/templates/sidebar.html index e1dd71e24..bd3bbf031 100644 --- a/app/templates/sidebar.html +++ b/app/templates/sidebar.html @@ -29,7 +29,7 @@ {% endif %} - {% 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 %}
  • Create Event @@ -90,7 +90,7 @@
  • {% endif %} - {% if g.current_user.isProgramManager %} + {% if g.current_user.isProgramManager or g.current_user.isCeltsOperationsTeam %} diff --git a/database/test_data.py b/database/test_data.py index 034fe5bc3..bf368e113 100644 --- a/database/test_data.py +++ b/database/test_data.py @@ -46,6 +46,7 @@ "isStaff": True, "isCeltsAdmin": True, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "dietRestriction": "Diary", "major": None, "rawClassLevel": None, @@ -63,6 +64,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer Science", "rawClassLevel": "Senior", "minorInterest": 1, @@ -78,6 +80,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": True, + "isCeltsOperationsTeam": False, "major": "Psychology", "rawClassLevel": "Sophomore", "minorInterest": 0, @@ -94,6 +97,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer and Information Science", "rawClassLevel": "Junior", "minorInterest": None, @@ -109,6 +113,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Chemistry", "rawClassLevel": "Junior", "minorInterest": None, @@ -125,6 +130,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": None, "rawClassLevel": None, "minorInterest": None, @@ -164,6 +170,7 @@ "isStudent": True, "phoneNumber": "(859)463-1159", "isCeltsStudentStaff": True, + "isCeltsOperationsTeam": False, "major": None, "rawClassLevel": None, "minorInterest": None, @@ -179,6 +186,7 @@ "isCeltsAdmin": False, "isFaculty": True, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "isStaff": True, "major": None, "rawClassLevel": None, @@ -194,6 +202,7 @@ "isCeltsAdmin": True, "isFaculty": True, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "isStaff": True, "major": None, "rawClassLevel": None, @@ -211,6 +220,7 @@ "isStaff": True, "isCeltsAdmin": True, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": None, "rawClassLevel": None, "minorInterest": None, @@ -225,6 +235,7 @@ "isCeltsAdmin": False, "isFaculty": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "isStaff": True, "minorInterest": None, }, @@ -239,6 +250,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer Science", "rawClassLevel": "Senior", "minorInterest": None, @@ -254,6 +266,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer Science", "rawClassLevel": "Junior", "minorInterest": None, @@ -269,6 +282,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer Science", "rawClassLevel": "Senior", "minorInterest": None, @@ -284,6 +298,7 @@ "isFaculty": False, "isCeltsAdmin": False, "isCeltsStudentStaff": False, + "isCeltsOperationsTeam": False, "major": "Computer Science", "rawClassLevel": "Senior", "minorInterest": None, diff --git a/tests/code/test_userManagement.py b/tests/code/test_userManagement.py index 723ba0c73..d6f44756d 100644 --- a/tests/code/test_userManagement.py +++ b/tests/code/test_userManagement.py @@ -81,6 +81,30 @@ def test_modifyCeltsStudentStaff(): transaction.rollback() +@pytest.mark.integration +def test_modifyCeltsOperationsTeam(): + with mainDB.atomic() as transaction: + + username = "neillz" + userInTest = User.get(User.username == username) + + assert userInTest.isCeltsStudentStaff + assert userInTest.isCeltsOperationsTeam == False + + with app.app_context(): + g.current_user = "ramsayb2" + + addCeltsOperationsTeam(userInTest) + + userInTest = User.get(User.username == username) + assert userInTest.isCeltsOperationsTeam == True + + removeCeltsOperationsTeam(userInTest) + + userInTest = User.get(User.username == username) + assert userInTest.isCeltsOperationsTeam == False + + transaction.rollback() @pytest.mark.integration def test_changeProgramInfo():