From 0c033757c9d58db8732336f5330775b24a9a2322 Mon Sep 17 00:00:00 2001 From: munsakad Date: Fri, 19 Jun 2026 17:00:52 -0400 Subject: [PATCH 01/22] Add CCE Minor Note tag to user profile Notes section --- app/templates/main/userProfile.html | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 13fafda69..0f75a56e1 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -429,16 +429,17 @@

{{row.note.noteContent}} {% set bonner = "Bonner " if row.isBonnerNote else "" %} + {% set cceMinor = "CCE Minor " if row.isCCEMinorNote else "" %} {% if row.viewTier == 3 %} - {{bonner}}Admins + {{cceMinor}}{{bonner}}Admins {% elif row.viewTier == 2 %} - {{bonner}}Admins/Student Staff + {{cceMinor}}{{bonner}}Admins/Student Staff {% else %} - {{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}} + {{cceMinor}}{{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}} {% endif %} {% if (g.current_user == row.note.createdBy) or g.current_user.isCeltsAdmin%} - + {% else %} @@ -582,6 +583,12 @@

{% endif %} + {% if volunteer.minorInterest %} +
+ + +
+ {% endif %}
From 6bfc7fc1cf12a080ded2b13b2c31dafb9f702836 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Mon, 3 Aug 2026 14:58:04 -0400 Subject: [PATCH 02/22] Added CCE Minor support to profile notes --- app/controllers/main/routes.py | 67 ++++- app/logic/users.py | 59 +++- app/models/profileNote.py | 6 +- app/static/js/userProfile.js | 318 +++++++++++++-------- app/templates/main/userProfile.html | 409 ++++++++++++++++++++++------ database/reset_database.sh | 4 +- 6 files changed, 639 insertions(+), 224 deletions(-) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 6f83f32dd..08b9d2bde 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -37,7 +37,7 @@ from app.logic.landingPage import getManagerProgramDict, getActiveEventTab from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent -from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo +from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo, updateProfileNote @main_bp.route('/logout', methods=['GET']) def redirectToLogout(): @@ -378,21 +378,64 @@ def eventTravelForm(eventID): userList = userList, ) -@main_bp.route('/profile/addNote', methods=['POST']) +@main_bp.route("/profile/addNote", methods=["POST"]) def addNote(): - """ - This function adds a note to the user's profile. - """ postData = request.form + + noteTextbox = postData.get("noteTextbox", "").strip() + + if not noteTextbox: + return "Note cannot be empty", 400 + + addProfileNote( + visibility=postData.get("visibility", 1), + bonner=postData.get("bonner") == "yes", + cceMinor=postData.get("cceMinor") == "yes", + noteTextbox=noteTextbox, + username=postData["username"], + ) + + return "success" + +@main_bp.route("//editNote", methods=["POST"]) +def editProfileNote(username): + profileNoteID = request.form.get("id") + noteTextbox = request.form.get("noteTextbox", "").strip() + visibility = request.form.get("visibility", 1) + bonner = request.form.get("bonner") == "yes" + cceMinor = request.form.get("cceMinor") == "yes" + + if not profileNoteID: + return "Missing profile note ID", 400 + + if not noteTextbox: + return "Note cannot be empty", 400 + try: - note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"]) - flash("Successfully added profile note", "success") - return redirect(url_for("main.viewUsersProfile", username=postData["username"])) - except Exception as e: - print("Error adding note", e) - flash("Failed to add profile note", "danger") - return "Failed to add profile note", 500 + profileNote = ProfileNote.get_by_id(profileNoteID) + except ProfileNote.DoesNotExist: + return "Profile note not found", 404 + + # Prevent editing a note belonging to another profile. + if profileNote.user.username != username: + abort(403) + # Only the creator or a CELTS administrator may edit the note. + if ( + profileNote.note.createdBy != g.current_user + and not g.current_user.isCeltsAdmin + ): + abort(403) + + updateProfileNote( + profileNoteID=profileNoteID, + visibility=visibility, + bonner=bonner, + cceMinor=cceMinor, + noteTextbox=noteTextbox, + ) + + return "success" @main_bp.route('//deleteNote', methods=['POST']) def deleteNote(username): diff --git a/app/logic/users.py b/app/logic/users.py index 36b9ba2bf..93fe5d85f 100644 --- a/app/logic/users.py +++ b/app/logic/users.py @@ -124,19 +124,56 @@ def getUserBGCheckHistory(username): bgHistory[row.type_id].append(row) return bgHistory -def addProfileNote(visibility, bonner, noteTextbox, username): +def addProfileNote( + visibility, + bonner, + cceMinor, + noteTextbox, + username, +): if bonner: - visibility = 1 # bonner notes are always admins and the student + # Bonner notes are always visible to admins and the student. + visibility = 1 - noteForDb = Note.create(createdBy = g.current_user, - createdOn = datetime.datetime.now(), - noteContent = noteTextbox, - noteType = "profile") - createProfileNote = ProfileNote.create(user = User.get(User.username == username), - note = noteForDb, - isBonnerNote = bonner, - viewTier = visibility) - return createProfileNote + noteForDb = Note.create( + createdBy=g.current_user, + createdOn=datetime.datetime.now(), + noteContent=noteTextbox, + noteType="profile", + ) + + profileNote = ProfileNote.create( + user=User.get(User.username == username), + note=noteForDb, + isBonnerNote=bonner, + isCCEMinorNote=cceMinor, + viewTier=int(visibility), + ) + + return profileNote +def updateProfileNote( profileNoteID, visibility, bonner, cceMinor, noteTextbox, ): + """ + Update an existing profile note without deleting and recreating it. + """ + + profileNote = ProfileNote.get_by_id(profileNoteID) + + # Bonner notes always use the Bonner visibility level. + if bonner: + visibility = 1 + + # Update the actual note text. + note = profileNote.note + note.noteContent = noteTextbox + note.save() + + # Update the ProfileNote information. + profileNote.viewTier = int(visibility) + profileNote.isBonnerNote = bonner + profileNote.isCCEMinorNote = cceMinor + profileNote.save() + + return profileNote def deleteProfileNote(noteId): return ProfileNote.delete().where(ProfileNote.id == noteId).execute() diff --git a/app/models/profileNote.py b/app/models/profileNote.py index 7c8924ab2..597fe8ab1 100644 --- a/app/models/profileNote.py +++ b/app/models/profileNote.py @@ -2,8 +2,12 @@ from app.models.user import User from app.models.note import Note + class ProfileNote(baseModel): user = ForeignKeyField(User) note = ForeignKeyField(Note, null=False) + isBonnerNote = BooleanField(default=False) - viewTier = IntegerField(default=3) + isCCEMinorNote = BooleanField(default=False) + + viewTier = IntegerField(default=3) \ No newline at end of file diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index 61af76787..5fef6cd1a 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -157,136 +157,234 @@ $(document).ready(function(){ }); }); - /* - * Note Functionality - */ - function bonnerNoteOff() { - $("#bonnerInput").prop("checked", false); - $("#noteDropdown").show() - $("#bonnerStatement").hide() - $("#visibilityLabel").show() - } +/* + * Note Functionality + */ + +function bonnerNoteOff() { + $("#bonnerInput").prop("checked", false); + $("#noteDropdown").show(); + $("#bonnerStatement").hide(); + $("#visibilityLabel").show(); +} + +function bonnerNoteOn() { + $("#bonnerInput").prop("checked", true); + $("#noteDropdown").hide(); + $("#bonnerStatement").show(); + $("#visibilityLabel").hide(); +} + +function resetNoteModal() { + bonnerNoteOff(); + + $("#cceMinorInput").prop("checked", false); + $("#addNoteTextArea").val(""); + $("#noteDropdown").val("1"); + + $("#notesSaveButton").data("mode", "add"); + $("#notesSaveButton").data("noteid", null); + $("#notesSaveButton").prop("disabled", false); +} + +/* + * Open the modal for a normal new note. + */ +$("#addNoteButton").click(function () { + resetNoteModal(); + $("#noteModal").modal("toggle"); +}); + +/* + * Open the modal from the Bonner Notes area. + * Bonner is selected by default, but CCE Minor stays independent. + */ +$("#addBonnerNoteButton").click(function () { + resetNoteModal(); + bonnerNoteOn(); + $("#noteModal").modal("toggle"); +}); - function bonnerNoteOn() { - $("#bonnerInput").prop("checked", true); - $("#noteDropdown").hide() - $("#bonnerStatement").show() - $("#visibilityLabel").hide() +/* + * Show or hide visibility whenever Bonner changes. + */ +$("#bonnerInput").on("change", function () { + if ($(this).is(":checked")) { + bonnerNoteOn(); + } else { + bonnerNoteOff(); } +}); - $("#addNoteButton").click(function() { - bonnerNoteOff() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle") - }); +/* + * Add or update a note. + */ +$("#addNoteForm").submit(function (event) { + event.preventDefault(); - $("#addVisibility").click(function() { - var bonnerChecked = $("input[name='bonner']:checked").val() + const saveButton = $("#notesSaveButton"); - if (bonnerChecked == 'on') { - bonnerNoteOn() - } else { - bonnerNoteOff() - } - }); + const username = saveButton.data("username"); + const mode = saveButton.data("mode"); + const noteid = saveButton.data("noteid"); - $("#addBonnerNoteButton").click(function() { - bonnerNoteOn() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle"); - }); + const noteTextbox = $("#addNoteTextArea").val().trim(); + const visibility = $("#noteDropdown").val(); - $('#addNoteForm').submit(function(event) { + const isBonner = $("#bonnerInput").is(":checked"); + const isCCEMinor = $("#cceMinorInput").is(":checked"); - event.preventDefault() - let username = $("#notesSaveButton").data('username') - let isBonner = $("#bonnerInput").is(":checked") - let mode = $("#notesSaveButton").data('mode') - let noteid = $("#notesSaveButton").data('noteid') + if (!noteTextbox) { + $("#addNoteTextArea").focus(); + return; + } - // If we're editing, delete the old note first - if (mode === 'edit') { - $.ajax({ - method: "POST", - url: "/" + username + "/deleteNote", - data: { "id": noteid } - }) + const requestData = { + username: username, + visibility: visibility, + noteTextbox: noteTextbox, + bonner: isBonner ? "yes" : "no", + cceMinor: isCCEMinor ? "yes" : "no" + }; + + let requestURL = "/profile/addNote"; + let successMessage = "Successfully added note"; + + if (mode === "edit") { + requestURL = "/" + username + "/editNote"; + requestData.id = noteid; + successMessage = "Successfully updated note"; } - $.ajax({ - method: "POST", - url: "/profile/addNote", - data: {"username": username, - "visibility": $("#noteDropdown").val(), - "noteTextbox": $("#addNoteTextArea").val(), - "bonner": isBonner ? "yes" : "no"}, - success: function(response) { - target = isBonner ? "bonner" : "notes" - msgFlash("Successfully added a note", "success", 1300, true); - location.reload() - }, - error: function(error) { - console.log("error") - } - }); - }); - $(".deleteNoteButton").click(function() { - $("#confirmDeleteNote").data('username', $(this).data('username')) - $("#confirmDeleteNote").data('noteid', $(this).data('noteid')) - $("#deleteNoteWarning").modal("show") - - }); + saveButton.prop("disabled", true); - $("#confirmDeleteNote").click(function() { - let username = $(this).data('username') - let noteid = $(this).data('noteid') $.ajax({ - method: "POST", - url: "/" + username + "/deleteNote", - data: {"id": noteid}, - success: function(response) { - msgFlash("Successfully deleted note", "success", 1300, true) - reloadWithAccordion("notes") - } + method: "POST", + url: requestURL, + data: requestData, + + success: function () { + msgFlash( + successMessage, + "success", + 1300, + true + ); + + location.reload(); + }, + + error: function (xhr) { + console.error( + "Unable to save note:", + xhr.responseText + ); + + saveButton.prop("disabled", false); + } }); - }); - - $(".editNoteButton").click(function() { - let noteText = $(this).data('notetext') - let visibility = $(this).data('visibility') - let isBonner = $(this).data('bonner') - let noteid = $(this).data('noteid') - - - $("#addNoteTextArea").val(noteText) - $("#noteDropdown").val(visibility) +}); - - if (isBonner === 'yes') { - bonnerNoteOn() +/* + * Open an existing note for editing. + */ +$(document).on("click", ".editNoteButton", function () { + const noteText = $(this).data("notetext"); + const visibility = String($(this).data("visibility")); + const noteid = $(this).data("noteid"); + + const isBonner = + String($(this).data("bonner")) === "yes"; + + const isCCEMinor = + String($(this).data("cceminor")) === "yes"; + + $("#addNoteTextArea").val(noteText); + $("#noteDropdown").val(visibility); + + if (isBonner) { + bonnerNoteOn(); } else { - bonnerNoteOff() + bonnerNoteOff(); + $("#noteDropdown").val(visibility); } - - $("#notesSaveButton").data('noteid', $(this).data('noteid')) - $("#notesSaveButton").data('mode', 'edit') - - $("#noteModal").modal("toggle") + /* + * This is the part that restores the CCE Minor toggle. + */ + $("#cceMinorInput").prop( + "checked", + isCCEMinor + ); + + $("#notesSaveButton").data( + "noteid", + noteid + ); + + $("#notesSaveButton").data( + "mode", + "edit" + ); + + $("#notesSaveButton").prop( + "disabled", + false + ); + + $("#noteModal").modal("toggle"); }); + +/* + * Open the delete confirmation. + */ +$(document).on("click", ".deleteNoteButton", function () { + $("#confirmDeleteNote").data( + "username", + $(this).data("username") + ); + + $("#confirmDeleteNote").data( + "noteid", + $(this).data("noteid") + ); + + $("#deleteNoteWarning").modal("show"); +}); + +/* + * Confirm note deletion. + */ +$("#confirmDeleteNote").click(function () { + const username = $(this).data("username"); + const noteid = $(this).data("noteid"); + $.ajax({ - method: "POST", - url: "/" + username + "/editNote", - data: {"id": noteid}, - success: function(response) { - reloadWithAccordion("notes") - } + method: "POST", + url: "/" + username + "/deleteNote", + data: { + id: noteid + }, + + success: function () { + msgFlash( + "Successfully deleted note", + "success", + 1300, + true + ); + + reloadWithAccordion("notes"); + }, + + error: function (xhr) { + console.error( + "Unable to delete note:", + xhr.responseText + ); + } }); - }); +}); /* * Background Check Functionality */ @@ -412,7 +510,7 @@ $(document).ready(function(){ typingTimer = setTimeout(saveDiet, saveInterval); }); }); - // end document.ready() +}); // end document.ready() // Update program manager status diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 0f75a56e1..5c606ba23 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -394,73 +394,175 @@
{{bgType.description}}
-
-

- {% set focus = "open" if visibleAccordion == "notes" else "collapsed" %} - -

- {% set show = "show" if visibleAccordion == "notes" else "" %} -
-
- +
+ {% set notesOpen = visibleAccordion == "notes" %} + +

+ +

+ +
+
+ + {% if g.current_user.isCeltsAdmin %} + {% set userTier = 3 %} + {% elif g.current_user.isCeltsStudentStaff %} + {% set userTier = 2 %} + {% else %} + {% set userTier = 1 %} + {% endif %} + + {% set note = namespace(count=0) %} + +
+
- + + - {% if g.current_user.isCeltsAdmin %} - {% set userTier = 3 %} - {% elif g.current_user.isCeltsStudentStaff %} - {% set userTier = 2 %} - {% else %} - {% set userTier = 1 %} - {% endif %} - {% set note = namespace(count=0) %} {% for row in profileNotes %} - {% if userTier >= row.viewTier and (not row.isBonnerNote or g.current_user.isBonnerScholar) %} + + {% set canViewBonnerNote = + not row.isBonnerNote + or g.current_user.isBonnerScholar + or g.current_user.isCeltsAdmin + or g.current_user.isCeltsStudentStaff + %} + + {% if userTier >= row.viewTier and canViewBonnerNote %} - - - + + + + + + + + + {% set note.count = note.count + 1 %} {% endif %} {% endfor %} + + {% if note.count == 0 %} + + + + {% endif %}
Date Creator Note Visible ToActions
{{row.note.createdOn.strftime('%m/%d/%Y')}}{{row.note.createdBy.firstName+ " "+ row.note.createdBy.lastName}}{{ " (you)" if row.note.createdBy == g.current_user else ""}}{{row.note.noteContent}} - {% set bonner = "Bonner " if row.isBonnerNote else "" %} - {% set cceMinor = "CCE Minor " if row.isCCEMinorNote else "" %} + {% if row.note.createdOn is string %} + {% set dateParts = row.note.createdOn[:10].split('-') %} + {{ dateParts[1] }}/{{ dateParts[2] }}/{{ dateParts[0] }} + {% else %} + {{ row.note.createdOn.strftime('%m/%d/%Y') }} + {% endif %} + + {{ row.note.createdBy.firstName }} + {{ row.note.createdBy.lastName }} + + {% if row.note.createdBy == g.current_user %} + (you) + {% endif %} + + {{ row.note.noteContent }} + {% if row.viewTier == 3 %} - {{cceMinor}}{{bonner}}Admins + Admins {% elif row.viewTier == 2 %} - {{cceMinor}}{{bonner}}Admins/Student Staff + Admins/Student Staff + {% elif row.isBonnerNote %} + Bonner Scholars {% else %} - {{cceMinor}}{{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}} + Everyone {% endif %} - - {% if (g.current_user == row.note.createdBy) or g.current_user.isCeltsAdmin%} - - + + {% if + g.current_user == row.note.createdBy + or g.current_user.isCeltsAdmin + %} + + + {% else %} - - + + + {% endif %}
+ There are no notes yet. +
- {% if note.count == 0 %} - There are no notes yet - {% endif %} -
-
-
+ +
+ @@ -512,7 +614,14 @@
Notes
data-noteid="{{row.note.id}}" data-username="{{volunteer.username}}" style="cursor:pointer"> -
{{row.note.createdBy.fullName}} {{row.note.createdOn.strftime('%m/%d/%Y')}} +
{{row.note.createdBy.fullName}} + + {% if row.note.createdOn is string %} + {% set dateParts = row.note.createdOn[:10].split('-') %} + {{ dateParts[1] }}/{{ dateParts[2] }}/{{ dateParts[0] }} + {% else %} + {{ row.note.createdOn.strftime('%m/%d/%Y') }} + {% endif %} {% endfor %} {% else %} @@ -543,70 +652,192 @@
Requirement Progress
- - + {% if zeroHourEvents %} + {% set ns = namespace(filtered=[]) %} + + {% for event in zeroHourEvents %} + {% if event.program.programName != "Bonner Scholars" %} + {% set _ = ns.filtered.append(event) %} + {% endif %} + {% endfor %} + + {% if ns.filtered %} +
+
+
Education & Training
+
+ +
+ {% for event in ns.filtered %} +
+
{{ event.name }}
+ + + {{ event.term.description }} — Attendance recorded, 0 service hours + + + {% if event.program.programName != "CELTS Sponsored Events" %} +

{{ event.program.description }}

+ More Information + {% endif %} +
+ {% endfor %} +
+
+ {% endif %} + {% endif %} + {% if slCourses.exists() %}
From b98d30ce9eaaee4ca0bb44b4253d6cb089a934cf Mon Sep 17 00:00:00 2001 From: BhushanSah Date: Thu, 16 Jul 2026 11:35:50 -0400 Subject: [PATCH 07/22] Initial commit --- app/templates/admin/graduationManagement.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/admin/graduationManagement.html b/app/templates/admin/graduationManagement.html index a84a658e8..764a1b1fc 100644 --- a/app/templates/admin/graduationManagement.html +++ b/app/templates/admin/graduationManagement.html @@ -1,6 +1,6 @@ {% set title = "Graduation Management" %} {% extends "base.html" %} - + {% block scripts %} {{super()}} From f92d2cca46dee77be2f4dc81b99a0c61d0cfb0f2 Mon Sep 17 00:00:00 2001 From: BhushanSah Date: Thu, 16 Jul 2026 12:59:45 -0400 Subject: [PATCH 08/22] Changed the dropdown to individual buttons --- app/templates/admin/graduationManagement.html | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/templates/admin/graduationManagement.html b/app/templates/admin/graduationManagement.html index 764a1b1fc..22e753f6e 100644 --- a/app/templates/admin/graduationManagement.html +++ b/app/templates/admin/graduationManagement.html @@ -1,6 +1,6 @@ {% set title = "Graduation Management" %} {% extends "base.html" %} - + {% block scripts %} {{super()}} @@ -19,15 +19,10 @@

Graduation Management

-
- - +
+ + +
From 34e5421babb7754f8e8fda7626f2c3d8a5df7b9d Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 16 Jul 2026 11:49:55 -0400 Subject: [PATCH 10/22] changed the title of 'Edit Volunteer Information' -> 'Edit Personal...' --- app/templates/main/userProfile.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 5c606ba23..379a529fb 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -38,7 +38,7 @@

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

-
- - - +
+
From 89f5edd0c4ce0caa7dc4e9eddd405684127633d4 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 11:26:47 -0400 Subject: [PATCH 17/22] fixed the format of the code --- app/controllers/main/routes.py | 18 +----------------- app/logic/users.py | 18 ++---------------- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 64a02bd62..c64a0de0a 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -388,22 +388,16 @@ def getProfileNoteData(include_username=False, include_id=False): if not noteData["noteTextbox"]: raise ValueError("Note cannot be empty") - if include_username: noteData["username"] = request.form.get("username") - if not noteData["username"]: raise ValueError("Missing username") - if include_id: noteData["profileNoteID"] = request.form.get("id") - if not noteData["profileNoteID"]: raise ValueError("Missing profile note ID") - return noteData - @main_bp.route("/profile/addNote", methods=["POST"]) def addNote(): try: @@ -412,17 +406,13 @@ def addNote(): except ValueError as error: return str(error), 400 - except User.DoesNotExist: return "User not found", 404 - except Exception as error: print("Error adding profile note:", error) return "Failed to add profile note", 500 - return "success" - @main_bp.route("//editNote", methods=["POST"]) def editProfileNote(username): try: @@ -440,19 +430,13 @@ def editProfileNote(username): if profileNote.user.username != username: abort(403) - if ( - profileNote.note.createdBy != g.current_user - and not g.current_user.isCeltsAdmin - ): + if ( profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin ): abort(403) - try: updateProfileNote(**noteData) - except Exception as error: print("Error updating profile note:", error) return "Failed to update profile note", 500 - return "success" @main_bp.route('//deleteNote', methods=['POST']) diff --git a/app/logic/users.py b/app/logic/users.py index bbf5e71b8..7f0ed3051 100644 --- a/app/logic/users.py +++ b/app/logic/users.py @@ -130,28 +130,14 @@ def addProfileNote(visibility, bonner, cceMinor, noteTextbox, username): if bonner: visibility = 1 - noteForDb = Note.create( - createdBy=g.current_user, - createdOn=datetime.datetime.now(), - noteContent=noteTextbox, - noteType="profile", - ) - - profileNote = ProfileNote.create( - user=user, - note=noteForDb, - isBonnerNote=bonner, - isCCEMinorNote=cceMinor, - viewTier=int(visibility), - ) - + noteForDb = Note.create( createdBy=g.current_user, createdOn=datetime.datetime.now(), noteContent=noteTextbox, noteType="profile", ) + profileNote = ProfileNote.create( user=user, note=noteForDb, isBonnerNote=bonner, isCCEMinorNote=cceMinor, viewTier=int(visibility), ) return profileNote def updateProfileNote( profileNoteID, visibility, bonner, cceMinor, noteTextbox): """ Update an existing profile note without deleting and recreating it. """ - profileNote = ProfileNote.get_by_id(profileNoteID) # Bonner notes always use the Bonner visibility level. From 36cd7d04b528145f8087f2317fc2376c60bafcba Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 11:44:44 -0400 Subject: [PATCH 18/22] fixed the layout of the page --- app/templates/main/userProfile.html | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index ad0bd4c2e..5997c7f92 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -478,22 +478,11 @@

or g.current_user.isCeltsAdmin %} - {% else %} - {% endif %} @@ -626,7 +615,6 @@

Requirement Progress
if g.current_user.isCeltsStudentStaff or g.current_user.isCeltsAdmin else "d-none" %} - {% if vis != "" %}

This note will only be visible to you, CELTS Student Staff, and Administrators.

{% endif %} @@ -642,12 +630,10 @@
Requirement Progress
{% if g.current_user.isCeltsAdmin %} {% endif %} - {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff %} {% endif %} -
@@ -668,7 +654,6 @@
Requirement Progress
{% endif %} -
@@ -682,7 +667,6 @@
Requirement Progress
- From c7099d019a0f50975892fa6b56ef548a7ceb5439 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 13:30:16 -0400 Subject: [PATCH 19/22] Revert local database reset changes --- database/reset_database.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/reset_database.sh b/database/reset_database.sh index 9166d3f4e..2e6928816 100755 --- a/database/reset_database.sh +++ b/database/reset_database.sh @@ -39,10 +39,10 @@ mysql -u root -proot --execute="CREATE DATABASE IF NOT EXISTS \`celts\`; CREATE rm -rf migrations rm -rf migrations.json +echo -n "Creating database objects" if [ $BACKUP -eq 1 ]; then echo " from backup" mysql -u root -proot celts < prod-backup.sql - else echo " empty" ./migrate_db.sh no-backup From 190c30e5fd5aa68d8914df7954424ee5763ab7c6 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 15:50:17 -0400 Subject: [PATCH 20/22] Address profile note review feedback and restore flash messages --- app/controllers/main/routes.py | 54 +++++++++++----------------------- app/logic/users.py | 37 ++++++++++++++--------- app/static/js/userProfile.js | 11 ++----- tests/code/test_users.py | 16 ++++++++-- 4 files changed, 56 insertions(+), 62 deletions(-) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 283f8463c..5f9fb0b40 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -37,7 +37,7 @@ from app.logic.landingPage import getManagerProgramDict, getActiveEventTab from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent -from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo, updateProfileNote +from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory,addProfileNote, deleteProfileNote, updateDietInfo, updateProfileNote, getProfileNoteData @main_bp.route('/logout', methods=['GET']) def redirectToLogout(): @@ -378,64 +378,44 @@ def eventTravelForm(eventID): userList = userList, ) -def getProfileNoteData(include_username=False, include_id=False): - noteData = { - "visibility": request.form.get("visibility", 1), - "bonner": request.form.get("bonner") == "yes", - "cceMinor": request.form.get("cceMinor") == "yes", - "noteTextbox": request.form.get("noteTextbox", "").strip(), - } - - if not noteData["noteTextbox"]: - raise ValueError("Note cannot be empty") - if include_username: - noteData["username"] = request.form.get("username") - if not noteData["username"]: - raise ValueError("Missing username") - if include_id: - noteData["profileNoteID"] = request.form.get("id") - if not noteData["profileNoteID"]: - raise ValueError("Missing profile note ID") - return noteData - @main_bp.route("/profile/addNote", methods=["POST"]) def addNote(): try: - noteData = getProfileNoteData(include_username=True) + noteData = getProfileNoteData(request.form, includeUsername=True, ) addProfileNote(**noteData) - + flash("Successfully added profile note", "success") except ValueError as error: + flash(str(error), "danger") return str(error), 400 except User.DoesNotExist: + flash("User not found", "danger") return "User not found", 404 except Exception as error: print("Error adding profile note:", error) + flash("Failed to add profile note", "danger") return "Failed to add profile note", 500 return "success" @main_bp.route("//editNote", methods=["POST"]) def editProfileNote(username): try: - noteData = getProfileNoteData(include_id=True) - profileNote = ProfileNote.get_by_id( - noteData["profileNoteID"] - ) - + noteData = getProfileNoteData( request.form,includeId=True, ) + profileNote = ProfileNote.get_by_id( noteData["profileNoteID"] ) + if profileNote.user.username != username: + abort(403) + if ( profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin ): + abort(403) + updateProfileNote(**noteData) + flash("Successfully updated profile note", "success") except ValueError as error: + flash(str(error), "danger") return str(error), 400 - except ProfileNote.DoesNotExist: + flash("Profile note not found", "danger") return "Profile note not found", 404 - - if profileNote.user.username != username: - abort(403) - - if ( profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin ): - abort(403) - try: - updateProfileNote(**noteData) except Exception as error: print("Error updating profile note:", error) + flash("Failed to update profile note", "danger") return "Failed to update profile note", 500 return "success" diff --git a/app/logic/users.py b/app/logic/users.py index 7f0ed3051..f15e6ede0 100644 --- a/app/logic/users.py +++ b/app/logic/users.py @@ -123,38 +123,47 @@ def getUserBGCheckHistory(username): for row in allBackgroundChecks: bgHistory[row.type_id].append(row) return bgHistory +def getProfileNoteData(formData, includeUsername=False, includeId=False): + noteData = { + "visibility": int(formData.get("visibility", 1)), + "bonner": formData.get("bonner") == "yes", + "cceMinor": formData.get("cceMinor") == "yes", + "noteTextbox": formData.get("noteTextbox", "").strip(), + } + if not noteData["noteTextbox"]: + raise ValueError("Note cannot be empty") + if includeUsername: + noteData["username"] = formData.get("username") + if not noteData["username"]: + raise ValueError("Missing username") + if includeId: + noteData["profileNoteID"] = formData.get("id") + if not noteData["profileNoteID"]: + raise ValueError("Missing profile note ID") + return noteData def addProfileNote(visibility, bonner, cceMinor, noteTextbox, username): user = User.get(User.username == username) - + visibility = int(visibility) if bonner: visibility = 1 - noteForDb = Note.create( createdBy=g.current_user, createdOn=datetime.datetime.now(), noteContent=noteTextbox, noteType="profile", ) - profileNote = ProfileNote.create( user=user, note=noteForDb, isBonnerNote=bonner, isCCEMinorNote=cceMinor, viewTier=int(visibility), ) + noteForDb = Note.create( createdBy=g.current_user, createdOn=datetime.datetime.now(), noteContent=noteTextbox, noteType="profile" ) + profileNote = ProfileNote.create( user=user, note=noteForDb, isBonnerNote=bonner, isCCEMinorNote=cceMinor, viewTier=visibility, ) return profileNote def updateProfileNote( profileNoteID, visibility, bonner, cceMinor, noteTextbox): - """ - Update an existing profile note without deleting and recreating it. - """ profileNote = ProfileNote.get_by_id(profileNoteID) - - # Bonner notes always use the Bonner visibility level. + visibility = int(visibility) if bonner: visibility = 1 - - # Update the actual note text. note = profileNote.note note.noteContent = noteTextbox note.save() - - # Update the ProfileNote information. - profileNote.viewTier = int(visibility) + profileNote.viewTier = visibility profileNote.isBonnerNote = bonner profileNote.isCCEMinorNote = cceMinor profileNote.save() - return profileNote def deleteProfileNote(noteId): diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index f040f4151..e5ef7e29e 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -247,15 +247,8 @@ $("#addNoteForm").submit(function (event) { method: "POST", url: requestURL, data: requestData, - - success: function () { msgFlash( successMessage, "success", 1300, true ); - - location.reload(); - }, - - error: function (xhr) { console.error( "Unable to save note:", xhr.responseText ); - saveButton.prop("disabled", false); - } + success: function () {location.reload();}, + error: function (xhr) { const errorMessage = xhr.responseText || "Unable to save profile note"; msgFlash( errorMessage, "danger", 3000, true ); saveButton.prop("disabled", false);} }); }); diff --git a/tests/code/test_users.py b/tests/code/test_users.py index e978e3f2c..e2955925f 100644 --- a/tests/code/test_users.py +++ b/tests/code/test_users.py @@ -14,7 +14,7 @@ from app.models.programManager import ProgramManager from app.models.backgroundCheck import BackgroundCheck from app.models.event import Event -from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, getBannedUsers, isBannedFromEvent, updateDietInfo +from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, getBannedUsers, isBannedFromEvent, updateDietInfo, getProfileNoteData from app.logic.volunteers import addUserBackgroundCheck, deleteUserBackgroundCheck @pytest.mark.integration @@ -136,7 +136,19 @@ def test_removeUserInterestt(): assert result == True transaction.rollback() - +@pytest.mark.integration +def test_getProfileNoteData(): + formData = { "visibility": "3","bonner": "yes", "cceMinor": "no", "noteTextbox": " Test profile note ", "username": "ramsayb2", "id": "12", } + noteData = getProfileNoteData( formData, includeUsername=True, includeId=True, ) + assert noteData == {"visibility": 3,"bonner": True, "cceMinor": False,"noteTextbox": "Test profile note", "username": "ramsayb2", "profileNoteID": "12", } + invalidData = [ + ({}, "Note cannot be empty"), + ( {"noteTextbox": "Test note"}, "Missing username" ), + ( {"noteTextbox": "Test note", "username": "ramsayb2", }, "Missing profile note ID", ), + ] + for formData, errorMessage in invalidData: + with pytest.raises(ValueError, match=errorMessage): + getProfileNoteData(formData, includeUsername=True, includeId=True,) @pytest.mark.integration def test_addUserProfileNote(): with mainDB.atomic() as transaction: From 0f42d62892c4b7a44feaf3097a248fb97a5775d9 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 16:19:50 -0400 Subject: [PATCH 21/22] pulled origin into my branch --- app/templates/minor/profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/minor/profile.html b/app/templates/minor/profile.html index a10b7f28a..9b694bb4e 100644 --- a/app/templates/minor/profile.html +++ b/app/templates/minor/profile.html @@ -32,7 +32,7 @@

{{ user.firstName }} {{ user.lastName }}'s CCE Minor Profile

From e903a81c5d2711e61cd302d1372d88c8c7f43ff3 Mon Sep 17 00:00:00 2001 From: feitsopb Date: Tue, 4 Aug 2026 16:31:35 -0400 Subject: [PATCH 22/22] resolved merge conflict --- app/templates/admin/graduationManagement.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/admin/graduationManagement.html b/app/templates/admin/graduationManagement.html index 58d3fa8a6..22e753f6e 100644 --- a/app/templates/admin/graduationManagement.html +++ b/app/templates/admin/graduationManagement.html @@ -19,7 +19,7 @@

Graduation Management

-
+