Skip to content

Commit

Permalink
Merge pull request #940 from BCStudentSoftwareDevTeam/buttonRegistrar
Browse files Browse the repository at this point in the history
a button to email the registrar requesting Student SL Course participation
  • Loading branch information
BrianRamsay authored Jul 25, 2023
2 parents 50851e5 + bf37c77 commit 7cd1a73
Show file tree
Hide file tree
Showing 21 changed files with 771 additions and 315 deletions.
30 changes: 25 additions & 5 deletions app/controllers/admin/routes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from flask import request, render_template, url_for, g, Flask, redirect
from flask import request, render_template, url_for, g, redirect
from flask import flash, abort, jsonify, session, send_file
from peewee import DoesNotExist, fn, IntegrityError
from playhouse.shortcuts import model_to_dict, dict_to_model
from playhouse.shortcuts import model_to_dict
import json
from datetime import datetime, date
from datetime import datetime
import os

from app import app
from app.models.program import Program
Expand All @@ -21,13 +22,14 @@
from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates
from app.logic.createLogs import createAdminLog
from app.logic.certification import getCertRequirements, updateCertRequirements
from app.logic.volunteers import getEventLengthInHours
from app.logic.utils import selectSurroundingTerms, getFilesFromRequest
from app.logic.events import deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCountsForTerm
from app.logic.participants import getEventParticipants, getUserParticipatedTrainingEvents, checkUserRsvp, checkUserVolunteer
from app.logic.participants import getUserParticipatedTrainingEvents, checkUserRsvp
from app.logic.fileHandler import FileHandler
from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort
from app.controllers.admin import admin_bp
from app.logic.serviceLearningCoursesData import parseUploadedFile, courseParticipantPreviewSessionCleaner



@admin_bp.route('/switch_user', methods=['POST'])
Expand Down Expand Up @@ -337,6 +339,24 @@ def deleteEventFile():
eventfile.deleteFile(fileData["fileId"])
return ""

@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
def addCourseFile():
fileData = request.files['addCourseParticipants']
filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
fileData.save(filePath)
(session['errorFlag'], session['courseParticipantPreview'], session['previewCourseDisplayList']) = parseUploadedFile(filePath)
os.remove(filePath)
return redirect(url_for("main.getAllCourseInstructors", showPreviewModal = True))

@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
def deleteCourseFile():
try:
courseParticipantPreviewSessionCleaner()
except KeyError:
pass

return ""

@admin_bp.route("/manageBonner")
def manageBonner():
if not g.current_user.isCeltsAdmin:
Expand Down
6 changes: 2 additions & 4 deletions app/controllers/admin/userManagement.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from flask import Flask, make_response, render_template,request, flash, g, json, abort, redirect, url_for
from flask import render_template,request, flash, g, abort, redirect, url_for
import re
from app.controllers.admin import admin_bp
from app.models.user import User
from app.models.program import Program
from app.logic.userManagement import addCeltsAdmin,addCeltsStudentStaff,removeCeltsAdmin,removeCeltsStudentStaff
from app.logic.userManagement import changeCurrentTerm
from app.logic.userManagement import changeProgramInfo
from app.logic.utils import selectSurroundingTerms
from app.logic.userManagement import addNextTerm
from app.models.term import Term
from app.logic.term import addNextTerm, changeCurrentTerm

@admin_bp.route('/admin/manageUsers', methods = ['POST'])
def manageUsers():
Expand Down
26 changes: 23 additions & 3 deletions app/controllers/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from app.logic.courseManagement import unapprovedCourses, approvedCourses
from app.logic.utils import selectSurroundingTerms
from app.logic.certification import getCertRequirementsWithCompletion
from app.logic.serviceLearningCoursesData import saveCourseParticipantsToDatabase,courseParticipantPreviewSessionCleaner
from app.logic.createLogs import createRsvpLog, createAdminLog

@main_bp.route('/logout', methods=['GET'])
Expand Down Expand Up @@ -378,25 +379,44 @@ def getAllCourseInstructors(term=None):
"""
This function selects all the Instructors Name and the previous courses
"""
showPreviewModal = request.args.get('showPreviewModal', default=False, type=bool)

if showPreviewModal and 'courseParticipantPreview' in session:
courseParticipantPreview = session['courseParticipantPreview']
else:
courseParticipantPreview = []

errorFlag = session.get('errorFlag')
previewParticipantDisplayList = session.get('previewCourseDisplayList')

if g.current_user.isCeltsAdmin:
setRedirectTarget(request.full_path)
courseDict = getCourseDict()

term = Term.get_or_none(Term.id == term) or g.current_term

unapproved = unapprovedCourses(term)
approved = approvedCourses(term)
terms = selectSurroundingTerms(g.current_term)

if request.method =='POST' and "submitParticipant" in request.form:
saveCourseParticipantsToDatabase(session['courseParticipantPreview'])
courseParticipantPreviewSessionCleaner()
flash('File saved successfully!', 'success')
return redirect(url_for('main.getAllCourseInstructors'))

return render_template('/main/manageServiceLearningFaculty.html',
courseInstructors = courseDict,
unapprovedCourses = unapproved,
approvedCourses = approved,
terms = terms,
term = term,
CourseStatus = CourseStatus)
CourseStatus = CourseStatus,
previewParticipantsErrorFlag = errorFlag,
courseParticipantPreview= courseParticipantPreview,
previewParticipantDisplayList = previewParticipantDisplayList
)
else:
abort(403)
abort(403)

def getRedirectTarget(popTarget=False):
"""
Expand Down
129 changes: 119 additions & 10 deletions app/logic/serviceLearningCoursesData.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from flask import session, g
import re as regex
from openpyxl import load_workbook
from app.models.course import Course
from app.models.user import User
from app.models.term import Term
Expand All @@ -12,6 +15,7 @@
from app.models import DoesNotExist
from app.logic.createLogs import createAdminLog
from app.logic.fileHandler import FileHandler
from app.logic.term import addPastTerm

def getServiceLearningCoursesData(user):
"""Returns dictionary with data used to populate Service-Learning proposal table"""
Expand All @@ -29,13 +33,12 @@ def getServiceLearningCoursesData(user):
faculty = [f"{instructor.user.firstName} {instructor.user.lastName}" for instructor in otherInstructors]


courseDict[course.id] = {
"id":course.id,
"creator":f"{course.createdBy.firstName} {course.createdBy.lastName}",
"name":course.courseName,
"faculty": faculty,
"term": course.term,
"status": course.status.status}
courseDict[course.id] = {"id":course.id,
"creator":f"{course.createdBy.firstName} {course.createdBy.lastName}",
"name":course.courseName,
"faculty": faculty,
"term": course.term,
"status": course.status.status}
return courseDict

def withdrawProposal(courseID):
Expand All @@ -57,9 +60,9 @@ def withdrawProposal(courseID):
courseName = course.courseName
questions = CourseQuestion.select().where(CourseQuestion.course == course)
notes = list(Note.select(Note.id)
.join(QuestionNote)
.where(QuestionNote.question.in_(questions))
.distinct())
.join(QuestionNote)
.where(QuestionNote.question.in_(questions))
.distinct())
course.delete_instance(recursive=True)
for note in notes:
note.delete_instance()
Expand Down Expand Up @@ -90,3 +93,109 @@ def renewProposal(courseID, term):
user=instructor.user)

return newCourse

def parseUploadedFile(filePath):
excelData = load_workbook(filename=filePath)
excelSheet = excelData.active
errorFlag = False
courseParticipantPreview= {}
previewTerm = ''
previewCourse = ''
studentValue= ''
cellRow = 1
previewCourseDisplayList = []


for row in excelSheet.iter_rows():
cellVal = row[0].value
displayRow = ''
termReg = r"\b[a-zA-Z]{3,}( [AB])? \d{4}\b" # Checks for 3 or more letters followed by zero or one space and A or B followed by a single space character followed by 4 digits. For Example: Fall 2020 or Spring B 2021
courseReg = r"\b[A-Z]{2,4} \d{3}\b" # Checks for between 2-4 capital letters followed by a single space followed by 3 digits. For Example: CSC 226
bnumberReg = r"\b[B]\d{8}\b" # Checks for a capital B followed by 8 digits. For Example B00123456

if regex.search(termReg, str(cellVal)):
if "Spring A" in cellVal or "Spring B" in cellVal:
cellVal = "Spring " + cellVal.split()[-1]
if "Fall A" in cellVal or "Fall B" in cellVal:
cellVal = "Fall " + cellVal.split()[-1]

if cellVal.split()[0] not in ["Summer", "Spring", "Fall", "May"]:
previewTerm = f"ERROR: {cellVal} is not valid."
errorFlag = True
displayRow = f"ERROR-{previewTerm}"
else:
previousTerm = list(Term.select().order_by(Term.termOrder))[-1].termOrder > Term.convertDescriptionToTermOrder(cellVal)
hasTerm = Term.get_or_none(Term.description == cellVal)
if hasTerm or previousTerm:
previewTerm = cellVal
displayRow = f"TERM-{previewTerm}"
else:
previewTerm = f"ERROR: The term {cellVal} does not exist and cannot be automatically created."
errorFlag = True
displayRow = f"ERROR-{previewTerm}"
courseParticipantPreview[previewTerm]= {}

elif regex.search(courseReg, str(cellVal)):
hasCourse = Course.get_or_none(Course.courseAbbreviation == cellVal)
previewCourse = f'{cellVal} will be created'
if hasCourse and hasCourse.courseName:
previewCourse = f"{cellVal} matched to the course {hasCourse.courseName}"
if not courseParticipantPreview.get(previewTerm):
courseParticipantPreview[previewTerm]= {}
courseParticipantPreview[previewTerm][cellVal]=[]
displayRow = f"COURSE-{previewCourse}"

elif regex.search(bnumberReg, str(cellVal)):
hasUser = User.get_or_none(User.bnumber == cellVal)
if hasUser:
studentValue = f"{hasUser.firstName} {hasUser.lastName}"
displayRow = f"STUDENT-{studentValue}"
else:
studentValue = f'ERROR: {row[1].value} with B# "{row[0].value}" does not exist.'
errorFlag = True
displayRow = f"ERROR-{studentValue}"
if not courseParticipantPreview.get(previewTerm):
courseParticipantPreview[previewTerm]= {}
if not courseParticipantPreview[previewTerm].get(previewCourse):
courseParticipantPreview[previewTerm][previewCourse]=[]
courseParticipantPreview[previewTerm][previewCourse].append([studentValue, cellVal])

elif cellVal != '' and cellVal != None:
errorText = f'ERROR: {cellVal} in row {cellRow} of the Excel document is not a valid value.'
errorFlag = True
displayRow = f"ERROR-{errorText}"

if displayRow:
previewCourseDisplayList.append(displayRow)

cellRow += 1

return errorFlag, courseParticipantPreview, previewCourseDisplayList

def saveCourseParticipantsToDatabase(courseParticipantPreview):
for term in courseParticipantPreview:
termObj = Term.get_or_none(description = term) or addPastTerm(term)

for course in courseParticipantPreview[term]:
courseObj = Course.get_or_create(courseAbbreviation = course,
term = termObj,
defaults = {"CourseName" : "",
"sectionDesignation" : "",
"courseCredit" : "1",
"term" : termObj,
"status" : 4,
"createdBy" : g.current_user,
"serviceLearningDesignatedSections" : "",
"previouslyApprovedDescription" : "" })

for student, bNumber in courseParticipantPreview[term][course]:
userObj = User.get(User.bnumber == bNumber)
CourseParticipant.get_or_create(user = userObj,
course = courseObj[0],
defaults = {"course" : courseObj[0]})


def courseParticipantPreviewSessionCleaner():
session.pop('errorFlag')
session.pop('courseParticipantPreview')
session.pop('previewCourseDisplayList')
62 changes: 62 additions & 0 deletions app/logic/term.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from flask import session
from playhouse.shortcuts import model_to_dict
from app.logic.createLogs import createAdminLog
from app.models.term import Term

def addNextTerm():
newSemesterMap = {"Spring":"Summer",
"Summer":"Fall",
"Fall":"Spring"}
terms = list(Term.select().order_by(Term.termOrder))
prevTerm = terms[-1]
prevSemester, prevYear = prevTerm.description.split()

newYear = int(prevYear) + 1 if prevSemester == "Fall" else int(prevYear)

newDescription = newSemesterMap[prevSemester] + " " + str(newYear)
newAY = prevTerm.academicYear
if prevSemester == "Summer ": # we only change academic year when the latest term in the table is Summer
year1, year2 = prevTerm.academicYear.split("-")
newAY = year2 + "-" + str(int(year2)+1)

semester = newDescription.split()[0]
summer= "Summer" in semester
newTerm = Term.create(description=newDescription,
year=newYear,
academicYear=newAY,
isSummer= summer,
termOrder=Term.convertDescriptionToTermOrder(newDescription))
newTerm.save()

return newTerm

def addPastTerm(description):
semester, year = description.split()
if 'May' in semester:
semester = "Summer"
if semester == "Fall":
academicYear = year + "-" + str(int(year) + 1)
elif semester == "Summer" or "Spring":
academicYear= str(int(year) - 1) + "-" + year

isSummer = "Summer" in semester
newDescription=f"{semester} {year}"
orderTerm = Term.convertDescriptionToTermOrder(newDescription)

createdOldTerm = Term.create(description= newDescription,
year=year,
academicYear=academicYear,
isSummer=isSummer,
termOrder=orderTerm)
createdOldTerm.save()
return createdOldTerm

def changeCurrentTerm(term):
oldCurrentTerm = Term.get_by_id(g.current_term)
oldCurrentTerm.isCurrentTerm = False
oldCurrentTerm.save()
newCurrentTerm = Term.get_by_id(term)
newCurrentTerm.isCurrentTerm = True
newCurrentTerm.save()
session["current_term"] = model_to_dict(newCurrentTerm)
createAdminLog(f"Changed Current Term from {oldCurrentTerm.description} to {newCurrentTerm.description}")
Loading

0 comments on commit 7cd1a73

Please sign in to comment.