Skip to content

Commit

Permalink
Merge pull request #1111 from BCStudentSoftwareDevTeam/CreateCoursePa…
Browse files Browse the repository at this point in the history
…rticipantUI_#1059

Creation of UI for Imported Courses
  • Loading branch information
AndersonStettner committed May 7, 2024
2 parents a4a6908 + 8608074 commit ef692d0
Show file tree
Hide file tree
Showing 14 changed files with 610 additions and 178 deletions.
1 change: 1 addition & 0 deletions app/config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ contributors:
- name: "Mercy Eze"
role: "Software Engineer"
- name: "Michel Moncada"
role: "Software Engineer"
- name: "Stevenson Michel"
role: "Software Engineer"
- name: "Lawrence Hoerst"
Expand Down
69 changes: 66 additions & 3 deletions app/controllers/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from app.models.eventRsvp import EventRsvp
from app.models.eventParticipant import EventParticipant
from app.models.user import User
from app.models.course import Course
from app.models.courseInstructor import CourseInstructor
from app.models.courseParticipant import CourseParticipant
from app.models.eventTemplate import EventTemplate
from app.models.adminLog import AdminLog
from app.models.eventRsvpLog import EventRsvpLog
Expand All @@ -32,7 +35,7 @@
from app.logic.minor import getMinorInterest
from app.logic.fileHandler import FileHandler
from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort
from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getInstructorCourses
from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getImportedCourses, getInstructorCourses, editImportedCourses

from app.controllers.admin import admin_bp

Expand Down Expand Up @@ -342,6 +345,7 @@ def deleteRoute(eventId):
except Exception as e:
print('Error while canceling event:', e)
return "", 500

@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
def deleteEventAndAllFollowingRoute(eventId):
try:
Expand All @@ -352,6 +356,7 @@ def deleteEventAndAllFollowingRoute(eventId):
except Exception as e:
print('Error while canceling event:', e)
return "", 500

@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
def deleteAllRecurringEventsRoute(eventId):
try:
Expand Down Expand Up @@ -421,6 +426,7 @@ def addCourseFile():
@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
def manageServiceLearningCourses(term=None):

"""
The SLC management page for admins
"""
Expand All @@ -435,16 +441,35 @@ def manageServiceLearningCourses(term=None):
manageTerm = Term.get_or_none(Term.id == term) or g.current_term

setRedirectTarget(request.full_path)

# retrieve and store the courseID of the imported course from a session variable if it exists.
# This allows us to export the courseID in the html and use it.
courseID = session.get("alterCourseId")

if courseID:
# delete courseID from the session if it was retrieved, for storage purposes.
session.pop("alterCourseId")
return render_template('/admin/manageServiceLearningFaculty.html',
courseInstructors = getInstructorCourses(),
unapprovedCourses = unapprovedCourses(manageTerm),
approvedCourses = approvedCourses(manageTerm),
importedCourses = getImportedCourses(manageTerm),
terms = selectSurroundingTerms(g.current_term),
term = manageTerm,
cpPreview = session.get('cpPreview', {}),
cpPreviewErrors = session.get('cpErrors', []),
courseID = courseID
)

return render_template('/admin/manageServiceLearningFaculty.html',
courseInstructors = getInstructorCourses(),
unapprovedCourses = unapprovedCourses(manageTerm),
approvedCourses = approvedCourses(manageTerm),
importedCourses = getImportedCourses(manageTerm),
terms = selectSurroundingTerms(g.current_term),
term = manageTerm,
cpPreview= session.get('cpPreview',{}),
cpPreviewErrors = session.get('cpErrors',[])
)
)

@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
def getSidebarInformation() -> str:
Expand All @@ -467,6 +492,44 @@ def removeFromSession():

return ""

@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
def alterImportedCourse(courseID):
"""
This route handles a GET and a POST request for the purpose of imported courses.
The GET request provides preexisting information of an imported course in a modal.
The POST request updates a specific imported course (course name, course abbreviation,
hours earned on completion, list of instructors) in the database with new information
coming from the imported courses modal.
"""
if request.method == 'GET':
try:
targetCourse = Course.get_by_id(courseID)
targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)

try:
serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
except IndexError: # If a course has no participant, IndexError will be raised
serviceHours = 20

courseData = model_to_dict(targetCourse, recurse=False)
courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
courseData['hoursEarned'] = serviceHours

return jsonify(courseData)

except DoesNotExist:
flash("Course not found")
return jsonify({"error": "Course not found"}), 404

if request.method == 'POST':
# Update course information in the database
courseData = request.form.copy()
editImportedCourses(courseData)
session['alterCourseId'] = courseID

return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))


@admin_bp.route("/manageBonner")
def manageBonner():
if not g.current_user.isCeltsAdmin:
Expand Down
51 changes: 50 additions & 1 deletion app/logic/serviceLearningCourses.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def getSLProposalInfoForUser(user: User) -> Dict[int, Dict[str, Any]]:

courseDict[course.id] = {"id":course.id,
"creator":f"{course.createdBy.firstName} {course.createdBy.lastName}",
"name":course.courseName,
"name":course.courseName if course.courseName else course.courseAbbreviation,
"faculty": faculty,
"term": course.term,
"status": course.status.status}
Expand Down Expand Up @@ -115,6 +115,21 @@ def approvedCourses(termId: int) -> List[Course]:

return approvedCourses

def getImportedCourses(termId: int) -> List[Course]:
"""
Queries the database to get all the necessary information for
imported courses.
"""
importedCourses: List[Course] = list(Course.select(Course, Term, CourseStatus, fn.GROUP_CONCAT(" " ,User.firstName, " ", User.lastName).alias('instructors'))
.join(CourseInstructor, JOIN.LEFT_OUTER)
.join(User, JOIN.LEFT_OUTER).switch(Course)
.join(CourseStatus).switch(Course)
.join(Term)
.where(Term.id == termId, Course.status == CourseStatus.IMPORTED)
.group_by(Course, Term, CourseStatus))

return importedCourses

def getInstructorCourses() -> Dict[User, str]:
"""
This function queries all of the course instructors and their classes and maps
Expand Down Expand Up @@ -251,6 +266,40 @@ def updateCourse(courseData, attachments=None) -> Union[Course, bool]:
transaction.rollback()
return False

def editImportedCourses(courseData):
"""
This function will take in courseData for the SLC proposal page and a dictionary
of instructors assigned to the imported course after that one is edited
and update the information in the db.
"""

with mainDB.atomic() as transaction:
try:
course = Course.get_by_id(courseData["courseId"])

Course.update(courseName=courseData["courseName"]).where(Course.id == course.id).execute()

(CourseParticipant.update(hoursEarned=courseData["hoursEarned"])
.where(CourseParticipant.course_id == course.id).execute())

instructorList = []
CourseInstructor.delete().where(CourseInstructor.course == course).execute()

if 'instructor[]' in courseData:
instructorList = courseData.getlist('instructor[]')

for instructor in instructorList:
# Checks that empty string is not added as a course instructor because some keys in the dictionary are empty string.
if instructor:
CourseInstructor.create(course=course, user=instructor)

return Course.get_by_id(course.id)

except Exception as e:
print(e)
transaction.rollback()
return False

########### Course Actions ###########

def parseUploadedFile(filePath):
Expand Down
4 changes: 4 additions & 0 deletions app/static/css/manageServiceLearningFaculty.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ li {
.course-container.even {
background-color: #f2f2f2; /* from the table striping odd color */
}

#flash_container {
z-index: 9999;
}
51 changes: 51 additions & 0 deletions app/static/js/instructorTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Instructor manipulation functions
// -------------------------------------

export function getRowUsername(element) {
return $(element).closest("tr").data("username")
}

export function createNewRow(selectedInstructor) {
let instructor = `${selectedInstructor["firstName"]} ${selectedInstructor["lastName"]} (${selectedInstructor["email"]})`;
let username = selectedInstructor["username"];
let phone = selectedInstructor["phoneNumber"];
let tableBody = $("#instructorTable").find("tbody");

if(tableBody.prop('outerHTML').includes(instructor)){
msgFlash("Instructor is already added.", "danger");
return;
}
// Create new table row and update necessary attributes
let lastRow = tableBody.find("tr:last");
let newRow = lastRow.clone();

let instructorName = newRow.find("td:eq(0) p")
instructorName.text(instructor);

let phoneInput = newRow.find("td:eq(0) input")
phoneInput.val(phone);
phoneInput.attr("id", `inputPhoneNumber-${username}`);
$(phoneInput).inputmask('(999)-999-9999');

let removeButton = newRow.find("td:eq(1) button")
let editLink = newRow.find("td:eq(0) a")
editLink.attr("id", `editButton-${username}`);

editLink.attr("data-username", username)
newRow.prop("hidden", false);
lastRow.after(newRow);

phoneInput.data("value", phone)
var editSelector = `#editButton-${username}`
var inputSelector = `#inputPhoneNumber-${username}`
if (username){
setupPhoneNumber(editSelector, inputSelector)
}

$("#instructorTableNames").append(`<input hidden name="instructor[]" value="${username}"/>`)
}

export function getCourseInstructors() {
// get usernames out of the table rows
return $("#instructorTableNames input").map((i,el) => $(el).val())
}
14 changes: 8 additions & 6 deletions app/static/js/manageServiceLearningFaculty.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@


$(document).ready( function () {

/******** Faculty Table Management **************/
/************** Faculty Table Management **************/
var table = $('#SLCFacultyTable').DataTable({
"fnDrawCallback": function(oSettings) {
$('.dataTables_length').hide();
Expand Down Expand Up @@ -37,12 +39,12 @@ $(document).ready( function () {
});


/******** Preview Events **************/
$('#modalPreview button[data-bs-dismiss="modal"]').click(function () {
$('#modalPreview').removeClass('show d-block');
/************** Preview Events **************/
$('#previewImportedCourses button[data-bs-dismiss="modal"]').click(function () {
$('#previewImportedCourses').removeClass('show d-block');
});

$('#modalSubmit').on('hidden.bs.modal', function () {
$('#submitImportedCourses').on('hidden.bs.modal', function () {
$('#addCourseParticipant').val('');
})

Expand All @@ -64,7 +66,7 @@ $(document).ready( function () {

/************** Course Participant Logic **************/
$("#modalCourseParticipant").on("click", function () {
$("#modalSubmit").modal("toggle");
$("#submitImportedCourses").modal("toggle");
});

$('#closeAddCourseParticipants').on('click', function () {
Expand Down
Loading

0 comments on commit ef692d0

Please sign in to comment.