diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 996de6b45..64ae34764 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -333,7 +333,7 @@ def adminLogs(): @admin_bp.route("/deleteEventFile", methods=["POST"]) def deleteEventFile(): fileData= request.form - eventfile=FileHandler(eventId=fileData["eventId"]) + eventfile=FileHandler(eventId=fileData["databaseId"]) eventfile.deleteFile(fileData["fileId"]) return "" diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index cdd544eb1..a20661b9f 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -379,7 +379,7 @@ def getAllCourseInstructors(term=None): This function selects all the Instructors Name and the previous courses """ if g.current_user.isCeltsAdmin: - setRedirectTarget("/manageServiceLearning") + setRedirectTarget(request.full_path) courseDict = getCourseDict() term = Term.get_or_none(Term.id == term) or g.current_term diff --git a/app/controllers/serviceLearning/routes.py b/app/controllers/serviceLearning/routes.py index 407bcc2ea..eabb3d075 100644 --- a/app/controllers/serviceLearning/routes.py +++ b/app/controllers/serviceLearning/routes.py @@ -90,12 +90,21 @@ def slcCreateCourse(): return redirect(url_for('serviceLearning.slcEditProposal', courseID = course.id)) + +@serviceLearning_bp.route('/serviceLearning/exit', methods=['GET']) +def slcExitView(): + if getRedirectTarget(): + return redirect(getRedirectTarget(True)) + else: + return redirect("/serviceLearning/courseManagement") + + @serviceLearning_bp.route('/serviceLearning/saveExit', methods=['POST']) @serviceLearning_bp.route('/serviceLearning/saveProposal', methods=['POST']) def slcSaveContinue(): """Will update the the course proposal and return an empty string since ajax request needs a response Also, it updates the course status as 'in progress'""" - course = updateCourse(request.form.copy()) + course = updateCourse(request.form.copy(), attachments=getFilesFromRequest(request)) if not course: flash("Error saving changes", "danger") @@ -103,7 +112,11 @@ def slcSaveContinue(): course.status = CourseStatus.IN_PROGRESS course.save() flash(f"Proposal has been saved.", "success") - return "" + if request.path == "/serviceLearning/saveExit": + if getRedirectTarget(): + return redirect(getRedirectTarget(True)) + return redirect("/serviceLearning/courseManagement") + return redirect(f'/serviceLearning/editProposal/{request.form["courseID"]}?tab=2') @serviceLearning_bp.route('/serviceLearning/newProposal', methods=['GET', 'POST']) def slcCreateOrEdit(): @@ -260,7 +273,7 @@ def uploadCourseFile(): @serviceLearning_bp.route("/deleteCourseFile", methods=["POST"]) def deleteCourseFile(): fileData= request.form - courseFile=FileHandler(courseId=fileData["courseId"]) + courseFile=FileHandler(courseId=fileData["databaseId"]) courseFile.deleteFile(fileData["fileId"]) return "" diff --git a/app/logic/courseManagement.py b/app/logic/courseManagement.py index 20ae4069d..243469aa2 100644 --- a/app/logic/courseManagement.py +++ b/app/logic/courseManagement.py @@ -7,6 +7,7 @@ from app.models.courseStatus import CourseStatus from app.logic.createLogs import createAdminLog from app.logic.fileHandler import FileHandler +from app.logic.utils import getFilesFromRequest from app.models.course import Course from app.models.term import Term from app.models.user import User @@ -53,7 +54,7 @@ def createCourse(creator="No user provided"): return course -def updateCourse(courseData, attachment=None): +def updateCourse(courseData, attachments=None): """ This function will take in courseData for the SLC proposal page and a dictionary of instuctors assigned to the course and update the information in the db. @@ -89,11 +90,11 @@ def updateCourse(courseData, attachment=None): for instructor in instructorList: CourseInstructor.create(course=course, user=instructor) createAdminLog(f"Saved SLC proposal: {courseData['courseName']}") - if attachment: - addFile= FileHandler(attachment, courseId=course.id) + if attachments: + addFile= FileHandler(attachments, courseId=course.id) addFile.saveFiles() return Course.get_by_id(course.id) except Exception as e: print(e) transaction.rollback() - return False + return False \ No newline at end of file diff --git a/app/static/js/base.js b/app/static/js/base.js index 0b06e2fc6..9b13d8991 100644 --- a/app/static/js/base.js +++ b/app/static/js/base.js @@ -124,3 +124,77 @@ function setCharacterLimit(textboxId, labelId){ $(labelId).text("Remaining Characters: " + 0); } } + +function hasUniqueFileName(fileName){ + return $(".fileName[data-filename='" + fileName + "']").length == 0; +} + +function getSelectedFiles(){ + let _fileHolder = new DataTransfer(); + $(".fileHolder").each(function(){ + _fileHolder.items.add($(this).data("file")); + }); + return _fileHolder.files; +} + +function handleFileSelection(fileInputId){ + var fileBoxId = "#" + fileInputId + var attachedObjectContainerId = fileInputId + "Container" + $(fileBoxId).after(`
`) + var objectContainerId = "#" + attachedObjectContainerId + $(fileBoxId).on('change', function() { + const selectedFiles = $(fileBoxId).prop('files'); + for (let i = 0; i < selectedFiles.length; i++){ + const file = selectedFiles[i]; + if (hasUniqueFileName(file.name)){ + let fileName = (file.name.length > 25) ? file.name.slice(0,10) + '...' + file.name.slice(-10) : file.name; + let fileExtension = file.name.split(".").pop(); + let iconClass = ''; + switch(fileExtension) { + case 'jpg': + case 'png': + case 'jpeg': + iconClass = "bi-file-image"; + break + case 'pdf': + iconClass = 'bi-filetype-pdf'; + break + case 'docx': + iconClass = 'bi-filetype-docx'; + break + case 'xlsx': + iconClass = 'bi-filetype-xlsx'; + break + default: + iconClass = 'bi-file-earmark-arrow-up'; + } + let trashNum = ($(objectContainerId+ " .row").length) + var fullTrashId = "#trash" + trashNum + $(objectContainerId).append(" \ +
\ + \ +
" + fileName + "
\ +
\ +
\ + \ +
\ +
\ +
") + $(fullTrashId).data("file", file); + $(fullTrashId).data("file-container-id", attachedObjectContainerId); + $(fullTrashId).on("click", function() { + let elementFileNum = $(this).data('filenum'); + let attachedObjectContainerId = $(this).data('file-container-id'); + $("#"+ attachedObjectContainerId + " #attachedFilesRow" + elementFileNum).remove(); + $(fileBoxId).prop('files', getSelectedFiles()); + }) + $(fileBoxId).data("file-num", $(fileBoxId).data("file-num") + 1) + } + else{ + msgToast("File with filename '" + file.name + "' has already been added to this event") + } + } + $(fileBoxId).prop('files', getSelectedFiles()); + }); + +} \ No newline at end of file diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 8265c2d2a..fb16d0d6a 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -61,22 +61,6 @@ function format24to12HourTime(timeStr){ } - -function getSelectedFiles(){ - let _fileHolder = new DataTransfer(); - $(".fileHolder").each(function(){ - _fileHolder.items.add($(this).data("file")); - }); - return _fileHolder.files; -} - - -function hasUniqueFileName(fileName){ - return $(".fileName[data-filename='" + fileName + "']").length == 0; -} - - - /* * Run when the webpage is ready for javascript */ @@ -85,58 +69,7 @@ $(document).ready(function() { calculateRecurringEventFrequency(); } - var fileNum = 0; - $("#attachmentObject").on('change', function() { - const selectedFiles = $("#attachmentObject").prop('files'); - for (let i = 0; i < selectedFiles.length; i++) { - const file = selectedFiles[i]; - if (hasUniqueFileName(file.name)){ - let fileName = (file.name.length > 25) ? file.name.slice(0,10) + '...' + file.name.slice(-10) : file.name; - let fileExtension = file.name.split(".").pop(); - let iconClass = ''; - switch(fileExtension) { - case 'jpg': - case 'png': - case 'jpeg': - iconClass = "bi-file-image"; - break - case 'pdf': - iconClass = 'bi-filetype-pdf'; - break - case 'docx': - iconClass = 'bi-filetype-docx'; - break - case 'xlsx': - iconClass = 'bi-filetype-xlsx'; - break - default: - iconClass = 'bi-file-earmark-arrow-up'; - } - $("#attachedObjectContainer").append("
\ - \ -
" + fileName + "
\ -
\ -
\ - \ -
\ -
\ -
") - $("#trash"+fileNum).data("file", file); - $("#trash"+fileNum).on("click", function() { - let elementFileNum = $(this).data('filenum'); - $("#attachedFilesRow" + elementFileNum).remove(); - $("#attachmentObject").prop('files', getSelectedFiles()); - }) - fileNum++; - } - else{ - msgToast("File with filename '" + file.name + "' has already been added to this event") - } - } - $("#attachmentObject").prop('files', getSelectedFiles()); - }); - - + handleFileSelection("attachmentObject") $("#checkRSVP").on("click", function() { if ($("#checkRSVP").is(":checked")) { @@ -274,25 +207,6 @@ $(document).ready(function() { $("#hiddenFacilitatorArray").attr("value", facilitatorArray); $(this).closest("tr").remove(); }); - $(".removeAttachment").on("click", function(){ - - let fileId= $(this).data("id") - let fileData = {fileId : fileId, - eventId:this.id} - $.ajax({ - type:"POST", - url: "/deleteEventFile", - data: fileData, //get the startDate, endDate and name as a dictionary - success: function(){ - msgFlash("Attachment removed successfully") - $("#attachment_"+fileId).remove() - - }, - error: function(error){ - msgFlash(error) - } - }); - }); $("#endDatePicker").change(function(){ updateDate(this) }); diff --git a/app/static/js/displayFilesMacro.js b/app/static/js/displayFilesMacro.js new file mode 100644 index 000000000..10f767273 --- /dev/null +++ b/app/static/js/displayFilesMacro.js @@ -0,0 +1,21 @@ +$(document).ready(function(){ + $(".removeAttachment").on("click", function(){ + let fileId= $(this).data("id") + let deleteLink = $(this).data("delete-url") + let fileData = {fileId : fileId, + databaseId:$(this).data('database-id')} + $.ajax({ + type:"POST", + url: deleteLink, + data: fileData, //get the startDate, endDate and name as a dictionary + success: function(){ + msgFlash("Attachment removed successfully") + $("#attachment_"+fileId).remove() + + }, + error: function(error){ + msgFlash(error) + } + }); + }); +}) diff --git a/app/static/js/slcNewProposal.js b/app/static/js/slcNewProposal.js index 40041a4ce..9cdce1831 100644 --- a/app/static/js/slcNewProposal.js +++ b/app/static/js/slcNewProposal.js @@ -3,45 +3,12 @@ import searchUser from './searchUser.js' var currentTab = 0; // Current tab is set to be the first tab (0) $(document).ready(function(e) { - disableSyllabusUploadFile() - $(".removeAttachment").on("click", function(){ - - let fileId= $(this).data("id") - let fileData = {fileId : fileId, - courseId:this.id} - $.ajax({ - type:"POST", - url: "/deleteCourseFile", - data: fileData, //get the startDate, endDate and name as a dictionary - success: function(){ - $("#modalAttachment_"+fileId).remove() - $("#pageAttachment_"+fileId).remove() - currentTab = 1; - - }, - error: function(error){ - msgFlash(error) - } - }); - }); - - - $("#attachmentObject").on('fileloaded', function() { - enableSyllabusUploadFile() - }) - - $("#syllabusUploadButton").on("click", function() { - saveCourseData('/serviceLearning/saveProposal', function() {}) - $("#syllabusUploadModal").modal("toggle") - }) - - $("#attachmentObject").fileinput({ - allowedFileExtensions:["pdf","jpg","png","gif", "csv", "docx", "jpg", "jpeg", "jfif"] - }) + handleFileSelection("attachmentObject") // set up the current tab and button state - if(window.location.href.includes("upload")) { - currentTab = 1 + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get('tab')){ + currentTab = Number(urlParams.get('tab')); } showTab(currentTab); @@ -114,6 +81,7 @@ $(document).ready(function(e) { }); $("#saveContinue").on("click", function() { + if(readOnly()) { let allTabs = $(".tab"); displayCorrectTab(1) @@ -124,30 +92,21 @@ $(document).ready(function(e) { // TODO nothing? } } - - //this will save the change from the current page and move to the next page - let allTabs = $(".tab"); - if (currentTab == (allTabs.length - 2)) { - - saveCourseData("/serviceLearning/saveContinue", function() { - displayCorrectTab(1); - }) + else{ + if (!validateForm()) return; + $('#slcNewProposal').attr("action", "/serviceLearning/saveProposal") + $('#slcNewProposal').submit() } - else if (currentTab == (allTabs.length - 1)){ - saveCourseData("/serviceLearning/saveExit", function() { - window.location.replace("/serviceLearning/courseManagement"); - }); - } }); - $("#saveExit").on("click", function() { - saveCourseData("/serviceLearning/saveExit", function() { - window.location.replace("/serviceLearning/courseManagement"); - }); + $('#saveExit').on("click", function(){ + if (!validateForm()) return; + $('#slcNewProposal').attr("action", "/serviceLearning/saveExit") + $('#slcNewProposal').submit() }) $("#exitButton").on("click", function() { - window.location.replace("/serviceLearning/courseManagement") + window.location.replace('/serviceLearning/exit') }) if(!readOnly()) { @@ -161,7 +120,9 @@ $(document).ready(function(e) { // Add course instructor event handlers // ----------------------------------------- $("#instructorTable").on("click", "#remove", function() { - $(this).closest("tr").remove(); + let closestRow = $(this).closest("tr") + $("#instructorTableNames input[value="+closestRow.data('username')+"]").remove() + closestRow.remove(); }); $("#courseInstructor").on('input', function() { searchUser("courseInstructor", createNewRow, true, null, "instructor"); @@ -228,7 +189,6 @@ function displayCorrectTab(navigateTab) { if (currentTab >= allTabs.length) { $("#nextButton").prop("disabled", true) - addInstructorsToForm() $("#slcNewProposal").submit(); return false; } @@ -274,8 +234,7 @@ function showTab(currentTab) { $("#submitAndApproveButton").show(); $("#nextButton").text("Submit Proposal"); $("#nextButton").show(); - $("#saveContinue").text("Save and Exit"); - $("#saveExit").hide() + $("#saveContinue").hide(); $("#exitButton").hide() if(readOnly()) { $("#nextButton").text("Next"); @@ -309,12 +268,12 @@ function saveCourseData(url, successCallback) { } function validateForm() { - // This function ensures our form fields are valid - // Returns true if we are just viewing a form - // TODO: Generalize form validation to include textareas and selects + // This function ensures our form fields are valid + // Returns true if we are just viewing a form + // TODO: Generalize form validation to include textareas and selects - if (readOnly()) - return true; + if (readOnly()) + return true; let valid = true; @@ -348,12 +307,6 @@ function validateForm() { // Instructor manipulation functions // ------------------------------------- // -function addInstructorsToForm() { - var form = $("#slcNewProposal"); - $.each(getCourseInstructors(), function(idx,username) { - form.append($("")); - }); -} function getRowUsername(element) { return $(element).closest("tr").data("username") @@ -384,7 +337,6 @@ function createNewRow(selectedInstructor) { let editLink = newRow.find("td:eq(0) a") editLink.attr("id", "editButton-" + username); - newRow.attr("data-username", username) editLink.attr("data-username", username) newRow.prop("hidden", false); lastRow.after(newRow); @@ -395,13 +347,13 @@ function createNewRow(selectedInstructor) { if (username){ setupPhoneNumber(edit, input) } + + $("#instructorTableNames").append('') } function getCourseInstructors() { // get usernames out of the table rows into an array - return $("#instructorTable tr") - .map((i,el) => $(el).data('username')).get() - .filter(val => (val)) + return $("#instructorTableNames input").map((i,el) => $(el).val()) } function disableSyllabusUploadFile() { diff --git a/app/templates/admin/createEvent.html b/app/templates/admin/createEvent.html index 772862284..a0d0b1c58 100644 --- a/app/templates/admin/createEvent.html +++ b/app/templates/admin/createEvent.html @@ -22,6 +22,7 @@ {% block scripts %} {{super()}} + {% endblock %} @@ -235,31 +236,12 @@
-
{% if filepaths %}
- - - {% for key, value in filepaths.items() %} - - - - - {% endfor %} -
- {% set filename = key[:8]+ "..." +key[-10:] if key| length > 25 else key %} - {{filename}} - - -
-
+ {% from 'displayFilesMacro.html' import displayFiles %} + {{ displayFiles(filepaths, 'Event Attachments', '/deleteEventFile', '{{course.id}}') }} + {% endif %} diff --git a/app/templates/displayFilesMacro.html b/app/templates/displayFilesMacro.html new file mode 100644 index 000000000..048e61522 --- /dev/null +++ b/app/templates/displayFilesMacro.html @@ -0,0 +1,23 @@ +{% macro displayFiles(filepaths, titleName, deleteLink, databaseId) %} + + + {% for key, value in filepaths.items() %} + + + + + {% endfor %} +
+ {% set filename = key[:8]+ "..." +key[-10:] if key| length > 25 else key %} + {{filename}} + + +
+{% endmacro %} diff --git a/app/templates/serviceLearning/slcNewProposal.html b/app/templates/serviceLearning/slcNewProposal.html index 44e843b56..065c24ef1 100644 --- a/app/templates/serviceLearning/slcNewProposal.html +++ b/app/templates/serviceLearning/slcNewProposal.html @@ -3,9 +3,9 @@ {% block scripts %} {{super()}} + - {% endblock %} {% block styles %} @@ -15,7 +15,7 @@ {% endblock %} {% block app_content %} -
+
{% include "serviceLearning/slcGuidelines.html" %}
@@ -47,53 +47,4 @@
- - {% endblock %} diff --git a/app/templates/serviceLearning/slcProposal.html b/app/templates/serviceLearning/slcProposal.html index 4c76d336c..d461e3d39 100644 --- a/app/templates/serviceLearning/slcProposal.html +++ b/app/templates/serviceLearning/slcProposal.html @@ -25,7 +25,7 @@

New Proposal For Service-Learning Course

- Edit + Edit @@ -37,7 +37,7 @@

New Proposal For Service-Learning Course

{{instructor.user.firstName}} {{instructor.user.lastName}} ({{instructor.user.email}})

- Edit + Edit @@ -48,6 +48,11 @@

New Proposal For Service-Learning Course

+
+ {% for instructor in courseInstructor %} + + {% endfor %} +
New Proposal For Service-Learning Course -
- +
+
+ +
+ + {% if filepaths %}
- - - {% for key, value in filepaths.items() %} - - - - - {% endfor %} -
- {{key}} - - -
+ {% from 'displayFilesMacro.html' import displayFiles %} + {{ displayFiles(filepaths, 'Course Attachments', '/deleteCourseFile', course.id) }}
{% endif %} + +