diff --git a/app/controllers/admin/graduationManagement.py b/app/controllers/admin/graduationManagement.py index 8f59165ef..c1e819212 100644 --- a/app/controllers/admin/graduationManagement.py +++ b/app/controllers/admin/graduationManagement.py @@ -2,7 +2,7 @@ from app.controllers.admin import admin_bp from app.logic.bonner import getBonnerCohorts -from app.logic.graduationManagement import setGraduatedStatus, getGraduationManagementUsers +from app.logic.graduationManagement import setGraduatedStatus, getGraduationManagementUsers, updateHideGraduatedStudents @admin_bp.route('/admin/graduationManagement', methods=['GET']) @@ -19,10 +19,6 @@ def graduationManagement(): @admin_bp.route('//setGraduationStatus/', methods=['POST']) def setGraduationStatus(username): - """ - This function - username: unique value of a user to correctly identify them - """ if not g.current_user.isAdmin: abort(403) @@ -30,3 +26,13 @@ def setGraduationStatus(username): setGraduatedStatus(username, status) return "" + +@admin_bp.route("/admin/hideGraduatedStudents/", methods=["POST"]) +def hideGraduatedStudents(username): + if g.current_user.isStudent: + abort(403) + + checked = request.form["checked"] + updateHideGraduatedStudents(username, checked) + + return "" diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index ca944b7af..008340d1c 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -116,8 +116,14 @@ def userManagement(): currentPrograms = currentPrograms.group_by(Program.id) - currentAdmins = list(User.select().where(User.isCeltsAdmin)) - currentStudentStaff = list(User.select().where(User.isCeltsStudentStaff)) + # hide graduated students if the user has indicated it + hideGraduatedStudents = User.get(username=g.current_user).hideGraduatedStudents + hideGraduatedStudentsWhere = True + if hideGraduatedStudents: + hideGraduatedStudentsWhere = (User.hasGraduated == False) + + currentAdmins = list(User.select().where(User.isCeltsAdmin, hideGraduatedStudentsWhere)) + currentStudentStaff = list(User.select().where(User.isCeltsStudentStaff, hideGraduatedStudentsWhere)) if g.current_user.isCeltsAdmin or g.current_user.isProgramManager: return render_template('admin/userManagement.html', terms = terms, diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index f9d17d1c4..5cef91cd6 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -581,14 +581,15 @@ def updateTranscript(username, program_id): @main_bp.route('/searchUser/', methods = ['GET']) def searchUser(query): - category= request.args.get("category") + category = request.args.get("category") + alwaysShowGraduatedStudents = int(request.args.get("alwaysShowGraduatedStudents", 0)) '''Accepts user input and queries the database returning results that matches user search''' try: query = query.strip() search = query.upper() splitSearch = search.split() - searchResults = searchUsers(query,category) + searchResults = searchUsers(query, category, alwaysShowGraduatedStudents) return searchResults except Exception as e: print(e) diff --git a/app/logic/graduationManagement.py b/app/logic/graduationManagement.py index f05478b81..9a7ce4d61 100644 --- a/app/logic/graduationManagement.py +++ b/app/logic/graduationManagement.py @@ -6,7 +6,7 @@ from app.models.bonnerCohort import BonnerCohort from app.logic.minor import getMinorProgress -def getGraduationManagementUsers(): +def getGraduationManagementUsers(alwaysShowGraduatedStudents=False): """ Function to fetch all senior students along with their CCE Minor Progress and Bonner Status """ @@ -15,7 +15,7 @@ def getGraduationManagementUsers(): .join(BonnerCohort, JOIN.LEFT_OUTER, on=(BonnerCohort.user == User.username)) .where((User.rawClassLevel == 'Senior') | (User.rawClassLevel == "Graduating") | (User.hasGraduated == True))) - cceStudents = set([user["username"] for user in getMinorProgress()]) + cceStudents = set([user["username"] for user in getMinorProgress(alwaysShowGraduatedStudents)]) graduationManagementUsers = [] for user in eligibleUsers: @@ -39,4 +39,13 @@ def setGraduatedStatus(username, status): gradStudent.hasGraduated = int(status) gradStudent.save() - \ No newline at end of file + +def updateHideGraduatedStudents(username, checked): + user = User.get(User.username == username) + + # it is necessary we cast this to an int instead of a bool because the + # status is passed as a string and if we cast it to a bool it will always be True + user.hideGraduatedStudents = int(checked) + + user.save() + diff --git a/app/logic/minor.py b/app/logic/minor.py index 955a124bc..702701262 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -18,11 +18,11 @@ from app.models.individualRequirement import IndividualRequirement from app.models.certificationRequirement import CertificationRequirement from app.models.cceMinorProposal import CCEMinorProposal +from app.models.attachmentUpload import AttachmentUpload from app.logic.createLogs import createActivityLog from app.logic.fileHandler import FileHandler from app.logic.serviceLearningCourses import deleteCourseObject -from app.models.attachmentUpload import AttachmentUpload - +from app.logic.utils import getHideGraduatedStudentsWhereClause def createSummerExperience(username, formData): """ @@ -74,20 +74,24 @@ def getMinorInterest() -> List[Dict]: """ Get all students that have indicated interest in the CCE minor and return a list of dicts of all interested students """ + hideGraduatedStudentsWhere = getHideGraduatedStudentsWhereClause(g.current_user) + interestedStudents = (User.select(User) .join(IndividualRequirement, JOIN.LEFT_OUTER, on=(User.username == IndividualRequirement.username)) - .where(User.isStudent & User.minorInterest & ~User.declaredMinor & IndividualRequirement.username.is_null(True))) + .where(User.isStudent & User.minorInterest & ~User.declaredMinor & IndividualRequirement.username.is_null(True), hideGraduatedStudentsWhere)) interestedStudentList = [model_to_dict(student) for student in interestedStudents] return interestedStudentList -def getMinorProgress(): +def getMinorProgress(alwaysShowGraduatedStudents=True): """ Get all the users who have an IndividualRequirement record under the CCE certification which and returns a list of dicts containing the student, how many engagements they have completed, and if they have completed the summer experience. """ + hideGraduatedStudentsWhere = getHideGraduatedStudentsWhereClause(g.current_user) if not alwaysShowGraduatedStudents else (True) + summerCase = Case(None, [(CCEMinorProposal.proposalType == "Summer Experience", 1)], 0) engagedStudentsWithCount = ( @@ -97,7 +101,7 @@ def getMinorProgress(): .join(IndividualRequirement, on=(User.username == IndividualRequirement.username)) .join(CertificationRequirement, on=(IndividualRequirement.requirement_id == CertificationRequirement.id)) .switch(User).join(CCEMinorProposal, JOIN.LEFT_OUTER, on= (User.username == CCEMinorProposal.student)) - .where(CertificationRequirement.certification_id == Certification.CCE) + .where(CertificationRequirement.certification_id == Certification.CCE, hideGraduatedStudentsWhere) .group_by(User.firstName, User.lastName, User.username) .order_by(SQL("engagementCount").desc()) ) @@ -186,7 +190,8 @@ def getDeclaredMinorStudents(): """ Get a list of the students who have declared minor """ - declaredStudents = User.select().where(User.isStudent & User.declaredMinor) + hideGraduatedStudentsWhere = getHideGraduatedStudentsWhereClause(g.current_user) + declaredStudents = User.select().where(User.isStudent & User.declaredMinor, hideGraduatedStudentsWhere) interestedStudentList = [model_to_dict(student) for student in declaredStudents] diff --git a/app/logic/searchUsers.py b/app/logic/searchUsers.py index 92ee2c76a..8c56c7426 100644 --- a/app/logic/searchUsers.py +++ b/app/logic/searchUsers.py @@ -1,6 +1,7 @@ from playhouse.shortcuts import model_to_dict +from flask import g from app.models.user import User -def searchUsers(query, category=None): +def searchUsers(query, category=None, alwaysShowGraduatedStudents=False): ''' Search the User table based on the search query and category @@ -11,6 +12,12 @@ def searchUsers(query, category=None): firstName = splitSearch[0] + "%" lastName = " ".join(splitSearch[1:]) +"%" + excludeGraduatedStudentsWhere = True + # this is necessary if they just recently changed this and it hasn't updated yet, maybe? + currentUser = User.get(username=g.current_user) + if (currentUser.hideGraduatedStudents) and (not alwaysShowGraduatedStudents): + excludeGraduatedStudentsWhere = (User.hasGraduated == False) + if len(splitSearch) == 1: # search for query in first OR last name searchWhere = (User.firstName ** firstName | User.lastName ** firstName | User.username ** splitSearch) else: # search for first AND last name @@ -30,6 +37,6 @@ def searchUsers(query, category=None): userWhere = (User.isStudent) # Combine into query - searchResults = User.select().where(searchWhere, userWhere) + searchResults = User.select().where(searchWhere, userWhere, excludeGraduatedStudentsWhere) return { user.username : model_to_dict(user) for user in searchResults } diff --git a/app/logic/utils.py b/app/logic/utils.py index 87e5c3f0e..d4ad934af 100644 --- a/app/logic/utils.py +++ b/app/logic/utils.py @@ -5,6 +5,7 @@ from peewee import DoesNotExist from app.models.term import Term +from app.models.user import User def selectSurroundingTerms(currentTerm, prevTerms=2, summerOnly=False): """ @@ -98,3 +99,12 @@ def setRedirectTarget(target): """ session["redirectTarget"] = target +def getHideGraduatedStudentsWhereClause(username): + + # hide graduated students if the user has indicated it + hideGraduatedStudents = User.get(username=username).hideGraduatedStudents + hideGraduatedStudentsWhere = True + if hideGraduatedStudents: + hideGraduatedStudentsWhere = (User.hasGraduated == False) + + return hideGraduatedStudentsWhere \ No newline at end of file diff --git a/app/models/user.py b/app/models/user.py index d901d26b0..5fc89855e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -19,6 +19,7 @@ class User(baseModel): minorInterest = BooleanField(null=True) hasGraduated = BooleanField(default=False) declaredMinor = BooleanField(default=False) + hideGraduatedStudents = BooleanField(default=False) # override BaseModel's __init__ so that we can set up an instance attribute for cache def __init__(self,*args, **kwargs): diff --git a/app/static/js/bonnerManagement.js b/app/static/js/bonnerManagement.js index 19130df95..c51271cec 100644 --- a/app/static/js/bonnerManagement.js +++ b/app/static/js/bonnerManagement.js @@ -28,7 +28,7 @@ function downloadSpreadsheet(blob, fileName) { function addSearchCapabilities(inputElement){ $(inputElement).on("input", function(){ let year = $(this).data('year'); - searchUser(this.id, student => cohortRequest(year, "add", student.username), false, null, "student"); + searchUser(this.id, student => cohortRequest(year, "add", student.username), false, false, null, "student"); }); } diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 1a734a73c..0c4741302 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -801,7 +801,7 @@ function handleTimeFormatting(timeArray){ } $("#eventFacilitator").on('input', function () { - searchUser("eventFacilitator", callback, true, undefined, "instructor"); + searchUser("eventFacilitator", callback, false, true, undefined, "instructor"); }); $("#facilitatorTable").on("click", "#remove", function () { diff --git a/app/static/js/manageVolunteers.js b/app/static/js/manageVolunteers.js index b0f7e7814..540f9a9e1 100644 --- a/app/static/js/manageVolunteers.js +++ b/app/static/js/manageVolunteers.js @@ -127,7 +127,7 @@ $(document).ready(function() { }); $("#addVolunteerInput").on("input", function() { - searchUser("addVolunteerInput", callback, true, "addVolunteerModal"); + searchUser("addVolunteerInput", callback, false, true, "addVolunteerModal"); }); diff --git a/app/static/js/minorAdminPage.js b/app/static/js/minorAdminPage.js index ac238e5a3..12e9d627d 100644 --- a/app/static/js/minorAdminPage.js +++ b/app/static/js/minorAdminPage.js @@ -146,6 +146,6 @@ $("#addInterestedStudentsModal").on("shown.bs.modal", function() { }); $("#addStudentInput").on("input", function() { -searchUser("addStudentInput", callback, true, "addInterestedStudentsModal"); +searchUser("addStudentInput", callback, false, true, "addInterestedStudentsModal"); }); diff --git a/app/static/js/searchStudent.js b/app/static/js/searchStudent.js index 93062f6de..b5b70a194 100644 --- a/app/static/js/searchStudent.js +++ b/app/static/js/searchStudent.js @@ -4,7 +4,7 @@ function callback(selected) { } $(document).ready(function() { $("#searchStudentsInput").on("input", function() { - searchUser("searchStudentsInput", callback); + searchUser("searchStudentsInput", callback, true); }); $("#searchIcon").click(function (e) { diff --git a/app/static/js/searchUser.js b/app/static/js/searchUser.js index 5ebf57c70..db91cf860 100644 --- a/app/static/js/searchUser.js +++ b/app/static/js/searchUser.js @@ -1,4 +1,11 @@ -export default function searchUser(inputId, callback, clear=false, parentElementId=null, category = null) +export default function searchUser( + inputId, + callback, + alwaysShowGraduatedStudents=false, + clear=false, + parentElementId=null, + category=null, +) { var query = $(`#${inputId}`).val() let columnDict = {}; @@ -10,7 +17,7 @@ export default function searchUser(inputId, callback, clear=false, parentElement url: `/searchUser/${query}`, type: "GET", dataType: "json", - data:{"category":category}, + data:{"category":category, "alwaysShowGraduatedStudents": alwaysShowGraduatedStudents ? 1 : 0}, success: function(searchResults) { response(Object.entries(searchResults).map( (item) => { return { diff --git a/app/static/js/slcManagement.js b/app/static/js/slcManagement.js index b62f72784..196f6eccd 100644 --- a/app/static/js/slcManagement.js +++ b/app/static/js/slcManagement.js @@ -52,7 +52,7 @@ $(document).ready(function() { }); $("#courseInstructor").on('input', function() { - searchUser("courseInstructor", createNewRow, true, null, "instructor"); + searchUser("courseInstructor", createNewRow, false, true, null, "instructor"); setTimeout(function() { $(".ui-autocomplete").css("z-index", 9999); }, 500); diff --git a/app/static/js/slcNewProposal.js b/app/static/js/slcNewProposal.js index 0c94561e3..fd1e438fa 100644 --- a/app/static/js/slcNewProposal.js +++ b/app/static/js/slcNewProposal.js @@ -144,7 +144,7 @@ $(document).ready(function(e) { }) $("#courseInstructor").on('input', function() { - searchUser("courseInstructor", createNewRow, true, null, "instructor"); + searchUser("courseInstructor", createNewRow, false, true, null, "instructor"); }); $("#courseInstructor").popover({ diff --git a/app/static/js/userManagement.js b/app/static/js/userManagement.js index 2e5e91998..72e541419 100644 --- a/app/static/js/userManagement.js +++ b/app/static/js/userManagement.js @@ -29,13 +29,13 @@ function callbackProgramManager(selected, action = 'add') { $(document).ready(function(){ // Admin Management $("#searchCeltsAdminInput").on("input", function(){ - searchUser("searchCeltsAdminInput", callbackAdmin, false, null, "celtsLinkAdmin") + searchUser("searchCeltsAdminInput", callbackAdmin, false, false, null, "celtsLinkAdmin") }); $("#searchCeltsStudentStaffInput").on("input", function(){ - searchUser("searchCeltsStudentStaffInput", callbackStudentStaff, false, null, "student") + searchUser("searchCeltsStudentStaffInput", callbackStudentStaff, false, false, null, "student") }); $("#searchProgramManagersInput").on("input", function() { - searchUser("searchProgramManagersInput", callbackProgramManager, true, "parentManager", "all"); + searchUser("searchProgramManagersInput", callbackProgramManager, false, true, "parentManager", "all"); }); $("#addNewTerm").on("click",function(){ addNewTerm(); diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index 7fe6a2c8b..d79c198ce 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -57,6 +57,27 @@ $(document).ready(function(){ }); }); + $('#hideGraduatedStudents').click(function() { + let checked = $(this).is(":checked") + var username = $(this).data('username'); + $.ajax({ + data: {checked: checked ? 1 : 0}, + method: "POST", + url: `/admin/hideGraduatedStudents/${username}`, + success: function(response) { + if (checked == true) { + msgFlash(`Graduated students hidden on CELTS Link for ${username}.`, "success", 1000) + } else { + msgFlash(`Graduated students shown on CELTS Link for ${username}.`, "success", 1000) + } + }, + error: function(request, status, error) { + console.error("Error hiding graduated students:", error); + msgFlash(`Error hiding graduated students for ${username}.`) + } + }); + }) + $('.onTranscriptCheckbox').click(function() { var onTranscript = $(this).is(':checked'); var username = $(this).data('username'); diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 6fee84932..162732ffd 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -36,19 +36,32 @@

{{volunteer.firstName}} {{volunteer.lastName}}

{%endif%} - -
diff --git a/tests/code/test_graduationManagement.py b/tests/code/test_graduationManagement.py index fcfc6ef6d..da1c0e204 100644 --- a/tests/code/test_graduationManagement.py +++ b/tests/code/test_graduationManagement.py @@ -1,7 +1,9 @@ import pytest +from flask import g -from app.logic.graduationManagement import setGraduatedStatus, getGraduationManagementUsers - +from app import app +from app.logic.graduationManagement import setGraduatedStatus, getGraduationManagementUsers, updateHideGraduatedStudents +from app.logic.utils import getHideGraduatedStudentsWhereClause from app.models import mainDB from app.models.eventRsvp import EventRsvp from app.models.user import User @@ -126,7 +128,7 @@ def test_getGraduationManagementUsers(): IndividualRequirement.create(**sustainedEngagement) - actualResult = getGraduationManagementUsers() + actualResult = getGraduationManagementUsers(True) # testUser4 is not a senior, graduating so they should not be shown. assert len(actualResult) == 4 @@ -158,3 +160,15 @@ def test_getGraduationManagementUsers(): assert expectedResult == actualResult transaction.rollback() + +@pytest.mark.integration +def test_updateHideGraduatedStudents(): + with mainDB.atomic() as transaction: + updateHideGraduatedStudents("ramsayb2", True) + transaction.rollback() + +@pytest.mark.integration +def test_getHideGraduatedStudentsWhereClause(): + with mainDB.atomic() as transaction: + getHideGraduatedStudentsWhereClause("ramsayb2") + transaction.rollback() \ No newline at end of file