From 07ffe59aaf57ea01f8637346a318a77c3bfe1819 Mon Sep 17 00:00:00 2001 From: murraydr Date: Wed, 11 Oct 2023 14:39:58 -0400 Subject: [PATCH] Cleaned up and expanded commenting --- driver.py | 11 +--- helperFunctions.py | 113 +++++++++++++++++++++++----------- lastUpdatedToken.txt | 2 +- sectionsDatabase.csv | 2 +- substitutionApprovalWindow.py | 62 ++++++++++--------- substitutionHistoryWindow.py | 34 +++++----- substitutionManualWindow.py | 37 ++++++----- substitutionRequestForm.csv | 26 ++++++++ 8 files changed, 182 insertions(+), 105 deletions(-) diff --git a/driver.py b/driver.py index 245f250..1b7ef7e 100644 --- a/driver.py +++ b/driver.py @@ -6,10 +6,9 @@ import helperFunctions as HF import scheduledPayrollWindow as PWin -#TODO workflow with git +#TODO demonstrate workflow with git #TODO search section/date (show curr and orig people assigned) -#TODO code comment #TODO document how to setup for next semester (staff and section changes) #TODO document how to get through the cert expiration every week @@ -49,7 +48,6 @@ stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) #remove leading/trailing whitespace secD = secD.apply(lambda x: x.str.strip()) stfD = stfD.apply(lambda x: x.str.strip()) -#stfD = stfD.apply(lambda x: x.str.lower()) #rewrite to file (each GUI function reads/writes on its own so that the state on disk matches what the user expects while the program is running) secD.to_csv(sectionDatabaseFilename,index=False,index_label=False) @@ -81,9 +79,6 @@ df2 = pd.read_csv(sectionDatabaseFilename[:-4]+"_Orig.csv",dtype=str) df1.fillna(value="",inplace=True) df2.fillna(value="",inplace=True) -#diff=df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))] -#diff=pd.concat([df1,df2]).drop_duplicates(keep=False) - diff = df1.merge(df2, indicator=True, how='outer') diff=diff.loc[diff['_merge'] != 'both'] @@ -91,8 +86,4 @@ print("\n"+"-"*200+"\nTo-Date difference from default shifts:") print(diff) print("-"*200) - - #PWin.scheduledPayWin(sectionDatabaseFilename,RegHoursFilename,defaultFont) - -#HF.sendEmail("murraydr","CSE102 Substitution","This is a test message") diff --git a/helperFunctions.py b/helperFunctions.py index ff826bd..32b94e0 100644 --- a/helperFunctions.py +++ b/helperFunctions.py @@ -20,7 +20,8 @@ sectionDatabaseFilename="sectionsDatabase.csv" staffDatabaseFilename="staffDatabase.csv" -#Converts full names to nedIDs and then back again based on the staff database +#Converts full name to nedID based on the staff database +#Uses a mildly 'fuzzy' search where the searched-for string must only occur *somewhere* in the actual full name def nameToID(searchName,getAllMatches=False): if searchName == "": return "" @@ -34,6 +35,8 @@ def nameToID(searchName,getAllMatches=False): return ret else: return ret[0] + +#Converts netID to full name for GUI human-readability def IDToName(searchID): if searchID=="": return "" @@ -43,6 +46,7 @@ def IDToName(searchID): name+="*"#append asterisk to name return name +#Tests if two requests overlap i.e. have at least one day/time combo in common def isRequestOverlap(req1,req2): print(req1) print(req2) @@ -59,37 +63,49 @@ def isRequestOverlap(req1,req2): overlap=True return overlap +#Get the time that a section takes place during def getTimeFromSection(section): secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) return(secD.loc[secD['Section']==section]['Time'].iloc[0]) +#Get the location that a section takes place in def getLocationFromSection(section): secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) return(secD.loc[secD['Section']==section]['Location'].iloc[0]) -def isAssigned(netID,period,date): - secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) - ret= False - filt = (secD.loc[(secD['Section']==period) & (secD['Date']==date)].apply(lambda r: r.astype('string').str.contains(netID).any(), axis=1)) - if filt.any(): #if the requestor is not assigned to that section - ret=True - return ret - -def reassign(date,period,oldID,newID): - secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) - secD.loc[(secD['Section']==period) & (secD['Date']==date)]=secD.loc[(secD['Section']==period) & (secD['Date']==date)].replace(oldID,newID) - secD.to_csv(sectionDatabaseFilename,index=False,index_label=False) - -def getSectionTitles(): - secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) - sections=list(secD.drop_duplicates(subset=["Section"])["Section"].values) - return sections - +#Get all dates that a section takes place in def getDatesFromSection(sec): secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) dates = list(secD.loc[secD["Section"]==sec]["Date"]) return dates +#Tests if a netID is currently assigned to a given section on a given date +def isAssigned(netID,period,date): + secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) + ret= False + filt = (secD.loc[(secD['Section']==period) & (secD['Date']==date)].apply(lambda r: r.astype('string').str.contains(netID).any(), axis=1)) + if filt.any(): #if the requestor is among the names in that section-date + ret=True + return ret + +#Overwrite a netID in one row of the section database with another +def reassign(date,period,oldID,newID): + secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) + secD.loc[(secD['Section']==period) & (secD['Date']==date)]=secD.loc[(secD['Section']==period) & (secD['Date']==date)].replace(oldID,newID) + secD.to_csv(sectionDatabaseFilename,index=False,index_label=False) + +#Get the strings of all section titles currently in the database (dropping duplicates) +def getAllSectionTitles(): + secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) + sections=list(secD.drop_duplicates(subset=["Section"])["Section"].values) + return sections + +#Get the strings of all names currently in the staff database +def getAllNames(): + stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) + return list(stfD["Name"].values) + +#Add or subtract from the count of a given person's sub request history in one category (e.g. rejections) def incrementSubCount(netID,category,amount=1): stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) if category==0 or category=="APP": @@ -108,8 +124,8 @@ def incrementSubCount(netID,category,amount=1): stfD.loc[stfD['NetID']==netID,columnName]=str(float(stfD.loc[stfD['NetID']==netID,columnName])+amount) stfD.to_csv(staffDatabaseFilename,index=False,index_label=False) +#Get an array of the counts of each category of sub history (Approved, Accepted, Rejected, Cancelled, Fulfilled) for a given person def getSubCount(netID): - stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) APP = stfD.loc[(stfD["NetID"]==netID)]["Approved Substitutions"].values[0] ACC = stfD.loc[(stfD["NetID"]==netID)]["Accepted Substitutions"].values[0] @@ -118,6 +134,9 @@ def getSubCount(netID): FUL = stfD.loc[(stfD["NetID"]==netID)]["Fulfilled Substitutions"].values[0] return (APP,ACC,REJ,CAN,FUL) +#Get a "high score" list or a "low score" list of names/scores based on a numeric string showing which categories to count +#0: Approved, 1: Accepted, 2: Rejected, 3: Cancelled, 4: Fulfilled +#E.g. "12" gives the ULAs with the most total accepted and rejected requests thusfar. def getTopSubs(categoryStr,fewest=False,num=1): if categoryStr=="": return "" @@ -142,7 +161,7 @@ def getTopSubs(categoryStr,fewest=False,num=1): stfD[header]+=(stfD[name].astype(float)) return stfD.sort_values(header,axis=0,ascending = fewest).head(num)[["Name",header]].to_string(index=False) - +#Test if a given single shift exists, i.e. if that section meets on that date. def shiftExists(period,date): secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) try: @@ -152,6 +171,7 @@ def shiftExists(period,date): return False return True +#Get a list of all names currently assigned to a given section on a given date def getAllNamesFromSection(periods,dates): secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) others=[] @@ -183,7 +203,7 @@ def dateBeforeOrEqual(date1, date2): else: return False -#create string files for easy pasting into google forms +#create string files for easy pasting into Google forms def createStrings(): stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False) @@ -208,10 +228,12 @@ def createStrings(): seen=set() f.write('\n'.join([x for x in vals if not (x in seen or seen.add(x))])) - +#Create the content of form-letter emails to be sent based on the request details and the chosen approval status def generateEmails(requestList): emails=[] for req in requestList: + + #Unpack the request into named variables (for readability in the string formatting below) timestamp = req[0] requestor = IDToName(req[1]).strip("*") section = req[2] @@ -221,28 +243,27 @@ def generateEmails(requestList): status = req[6] statusReason = req[7] + #Check if a replacement was specified at all if replacement!="": replaced=True else: replaced=False + #Unpack sections and dates to always be a list if ";" in section: sections = section.split(";") else: sections=[section] - if ";" in date: dates = date.split(";") else: dates=[date] - - times=[] locations=[] - if status=="APP" or status=="ACC": #For now, don't tell the ULAs that their acceptance was begrudging. We can change this later if desired. + if status=="APP" or status=="ACC": #For now, don't tell the ULAs that their acceptance was begrudging so they don't try and game the system by lying about the real reason. We can change this later if desired. status="approved" - changed=True + changed=True #Our database entries were changed for s in sections: times.append(getTimeFromSection(s)) locations.append(getLocationFromSection(s)) @@ -251,7 +272,7 @@ def generateEmails(requestList): status="rejected" else: status="cancelled" - changed=False + changed=False #Our database entries were changed for s in sections: try: times.append(getTimeFromSection(s)) @@ -329,6 +350,7 @@ def generateEmails(requestList): emails.append([recipient,subject,message]) + #Send emails (or print to terminal if debugging and actuallySend == False) sendEmails(emails,actuallySend=False) @@ -336,7 +358,12 @@ def generateEmails(requestList): #duumyParam is to make really sure that the author of the code that calls this functions really intends to actually send emails (by forcing them to use the keyword) -def sendEmails(emails,dummyParam=False,actuallySend=False): +def sendEmails(emails,dummyParam="DUMMY",actuallySend=False): + + #Catch incorrect use of this method by someone who doesn't understand the args. + if dummyParam!="DUMMY": + print("DON'T USE THE helperFunctions.sendEmails() method unless you know what you're doing! This can send actual emails to people's inbox!") + return if actuallySend: #https://www.geeksforgeeks.org/send-mail-gmail-account-using-python/ @@ -368,8 +395,13 @@ def sendEmails(emails,dummyParam=False,actuallySend=False): print("\n"+"-"*80+"\nEmail would be sent to "+email[0]+":\nSubject: "+email[1]+"\nMessage:\n"+email[2]+"\n"+"-"*80) - +#Get the raw data from the Google Forms API +#FYI: The first part of this code dealing with connection/authentication was not written by me and is largely a black box. +#This function only adds requests that are more recent than the last time it was run, so that the original data in the Google cloud never needs to be cleared def getForms(subRequestsFilename): + + #BLACKBOX AUTHENTICATION MAGIC + #-------------------------------------------------------------------------- #Requires some installation/setup https://developers.google.com/forms/api/quickstart/python SCOPES = ["https://www.googleapis.com/auth/forms.responses.readonly"] @@ -401,21 +433,25 @@ def getForms(subRequestsFilename): form_id = '1x-8fkuMAcQlTl36SdsbCG0tfClKAcvNshnV8L_Hl904' result = service.forms().responses().list(formId=form_id).execute() + #END OF BLACKBOX AUTHENTICATION MAGIC + #-------------------------------------------------------------------------- subs = pd.read_csv(subRequestsFilename,dtype=str) - + #Check when the last time the data was downloaded with open("lastUpdatedToken.txt",'r') as f: line = f.readline() if line != "": prevTime=datetime.strptime(line,"%Y-%m-%d %H:%M:%S.%f") else: - prevTime=datetime.strptime("1975-01-01 01:01:01.000000","%Y-%m-%d %H:%M:%S.%f") + prevTime=datetime.strptime("1975-01-01 01:01:01.000000","%Y-%m-%d %H:%M:%S.%f")#If the file was blank, (such as by just being created) use an aribtrary very early date + data=result["responses"] + #Unpack the ugly data structure that Google Forms returns for req in data: try: reason=req["answers"]["22a5ae9b"]["textAnswers"]["answers"][0]["value"] - except KeyError: + except KeyError:#No reason specified reason = "" requestor=req["answers"]["7bb6a9dd"]["textAnswers"]["answers"][0]["value"] @@ -435,11 +471,18 @@ def getForms(subRequestsFilename): sections=sections[:-1] timeStr = req["createTime"][:-1].replace("T"," ")+"000" + + #The timestamp needs formatting adjustment and a time-zone shift to EST timeStamp=datetime.strptime(timeStr, '%Y-%m-%d %H:%M:%S.%f')-timedelta(hours=4,minutes=0) + + #If the request is more recent than our last download, then our database doesn't yet 'know' about it and it needs to be added. if timeStamp>prevTime: reqDict={"Timestamp": [timeStr], "Requestor": [requestor], "Section": [sections], "Dates": [dates], "Replacement": [replacement], "Reason": [reason]} subs=pd.concat([subs,pd.DataFrame(reqDict)],ignore_index=True) subs.sort_values(by=["Timestamp"],inplace=True) + + #Write the updated request list to file subs.to_csv(subRequestsFilename,index=False,index_label=False) + #Write the timestamp to the token for checking when this function was last run with open("lastUpdatedToken.txt",'w') as f: - f.write(str(datetime.now())) \ No newline at end of file + f.write(str(datetime.now())) diff --git a/lastUpdatedToken.txt b/lastUpdatedToken.txt index 58fa9ca..9df1bdd 100644 --- a/lastUpdatedToken.txt +++ b/lastUpdatedToken.txt @@ -1 +1 @@ -2022-10-04 15:15:51.231447 \ No newline at end of file +2023-10-11 14:08:24.903282 \ No newline at end of file diff --git a/sectionsDatabase.csv b/sectionsDatabase.csv index 4bc8598..300af45 100644 --- a/sectionsDatabase.csv +++ b/sectionsDatabase.csv @@ -1714,4 +1714,4 @@ Sec 56,13,11/21,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,, Sec 56,14,11/28,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,, Sec 56,14,11/30,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,, Sec 56,15,12/05,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,, -Sec 56,15,12/07,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,, \ No newline at end of file +Sec 56,15,12/07,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,, diff --git a/substitutionApprovalWindow.py b/substitutionApprovalWindow.py index cc9b868..d71ca83 100644 --- a/substitutionApprovalWindow.py +++ b/substitutionApprovalWindow.py @@ -10,20 +10,16 @@ pd.set_option('display.min_rows', 20) pd.set_option('display.expand_frame_repr', False) pd.set_option('max_colwidth', 20) - - +#This is the main window for the substitution requests interface def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequestsArchiveFilename,defaultFont=("Courier",11)): - #read in sub requests from the provided csv filepath subRequests=[] with open(subRequestsFilename) as f: headerLine=f.readline() reader = csv.reader(f) for row in reader: - subRequests.append(row+[""]) - - + subRequests.append(row+[""]) #format fields is Requester, Period, Date, Replacement, Reason, Times, CurrAssigned, Errors columnWidths=[20,16,16,20,30,50,50,20] @@ -35,8 +31,9 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests row.append(sg.Text(headerVals[i],expand_x=True,expand_y=True,size=(columnWidths[i],1))) layout=[row] - #track how many requests are for a specific timeslot - datetimeFreq={} + #-------------------------------------------------------------------------- + #Basic error checking before opening display (determines which requests are in red) + datetimeFreq={} #track how many requests are for a specific timeslot for i in range(len(subRequests)): oldID=subRequests[i][1] periods=subRequests[i][2].split(';') @@ -50,12 +47,16 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests else: datetimeFreq[datetime]=1 if not HF.shiftExists(period,date): - subRequests[i][6]+="No such shift\n" + subRequests[i][6]+="No such shift\n" #Add to the list of errors with this request else: if not HF.isAssigned(oldID,period,date): - subRequests[i][6]+=oldID+" not assigned\n" + subRequests[i][6]+=oldID+" not assigned\n" #Add to the list of errors with this request if newID != "" and HF.isAssigned(newID,period,date): - subRequests[i][6]+=newID+" already assigned\n" + subRequests[i][6]+=newID+" already assigned\n" #Add to the list of errors with this request + + + #-------------------------------------------------------------------------- + #Formatting the text for the main window for i in range(len(subRequests)): request=subRequests[i] netID=request[1] @@ -86,7 +87,6 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests others=str(others)[1:-1] others=others.replace("'","") - #others=others.replace("],","]\n"+" "*(sum(columnWidths[:6])+16)+"|") textVals=[HF.IDToName(netID),*request[2:4],newSubName,request[5],timeStr,others,request[6].strip("\n")] @@ -100,6 +100,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests layout.append(row) layout.append([sg.Text('-'*(sum(columnWidths)+27))]) + #Adding buttons layout.append([sg.Button('Ok'), sg.Button('Cancel'), sg.Button('See Sub History'), sg.Button('Make Manual Changes')]) layout=[[sg.Column(layout,scrollable=True,expand_y=True,expand_x=True)]] @@ -112,6 +113,8 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests event=-1 while event != sg.WIN_CLOSED and completed==False: event, values = window.read() + + #values.values() is an array of the input from all window elements, we use strides to get the checkboxes as "columns" instead of "rows" approveValues=list(values.values())[::5] acceptValues=list(values.values())[1::5] rejectValues=list(values.values())[2::5] @@ -122,22 +125,22 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests nAcc = sum([1 for d in acceptValues if True == d]) nRej = sum([1 for d in rejectValues if True == d]) nCan = sum([1 for d in cancelValues if True == d]) - - - + #event is the most recent "click" from the user if event == "See Sub History": - SHW.subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont) + SHW.subHisWin(subRequestsArchiveFilename,defaultFont) #open history window if event == "Make Manual Changes": - window['Ok'].Update(disabled=True) #This is to prevent undefined behavior where manual changes are not represented in the error-checking of the main window - SMW.subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont) - event = "Cancel" - if event == 'Ok': + window['Ok'].Update(disabled=True) #This is to prevent undefined behavior as a result of manual changes not being represented in the main window + SMW.subManualWin(subRequestsArchiveFilename,defaultFont) #open manual change window + event = "Cancel" #after we return from the manual change window, just close the whole program + + if event == 'Ok': + valid=True dialogShown = False #Do not show multiple error dialogues even if there are multiple problems. - #If two responses are checked in ANY single line, then it is not valid input + #If two responses are both checked in ANY single line, then it is not valid input if True in [True for i, j, k, l in zip(approveValues, acceptValues, rejectValues, cancelValues) if ((i and j) or (i and k) or (j and k) or (i and l) or (j and l) or (k and l))]: valid=False if not dialogShown: @@ -154,6 +157,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests otherReqCache={} for i in range(len(subRequests)): if approveValues[i] or acceptValues[i]: + #If any of the app/acc lines have an error, then it is not valid input if subRequests[i][6] != "": valid=False if not dialogShown: @@ -212,9 +216,10 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests else: amount = 1 if approveValues[i] or acceptValues[i]: #If this request was checked "approved" or "accepted" - HF.reassign(date,period,oldID,newID) + HF.reassign(date,period,oldID,newID) #Actually make the changes in the database + #HF.incrementSubCount is for tracking how many subs this person requested and what their outcome was if newID !="": - HF.incrementSubCount(newID,4,amount=amount) + HF.incrementSubCount(newID,4,amount=amount)#Also track how many sub requests were filled by each person if approveValues[i]: HF.incrementSubCount(oldID,0,amount=amount) if acceptValues[i]: @@ -224,7 +229,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests if cancelValues[i]: HF.incrementSubCount(oldID,3,amount=amount) if (not approveValues[i] and not acceptValues[i] and not rejectValues[i] and not cancelValues[i]): - trimmedSubrequests.append(subRequests[i][:-1]) + trimmedSubrequests.append(subRequests[i][:-1])#Trimmed requests is the list of requests that had no action taken and thus must be re-added to the list of pending requests else: if approveValues[i]: result="APP" @@ -234,12 +239,13 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests result="REJ" elif cancelValues[i]: result="CAN" - resolvedSubrequests.append(subRequests[i][:-1]+[result]+[values["-INPUT_REASON_"+str(i)+"-"]]) + resolvedSubrequests.append(subRequests[i][:-1]+[result]+[values["-INPUT_REASON_"+str(i)+"-"]])#Resolved requests is kept to be appended to the archived requests database - # If the program crashes during email sending (such as with invalid login) it will deliberately NOT reach the code where it writes to databases + #If the program crashes during email sending (such as with invalid login) it will deliberately NOT reach the code where it writes to databases + #KEEP THIS LINE BEFORE THE WRITING STEPS HF.generateEmails(resolvedSubrequests) - + #Write to the respective databases with open(subRequestsFilename,'w', newline='') as f: f.write(headerLine) writer = csv.writer(f) @@ -253,4 +259,4 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests elif event == 'Cancel': completed=True - window.close() \ No newline at end of file + window.close() diff --git a/substitutionHistoryWindow.py b/substitutionHistoryWindow.py index fd81cfd..7255daf 100644 --- a/substitutionHistoryWindow.py +++ b/substitutionHistoryWindow.py @@ -3,7 +3,7 @@ import csv import pandas as pd import helperFunctions as HF -def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): +def subHisWin(subRequestsArchiveFilename,defaultFont): history = pd.read_csv(subRequestsArchiveFilename,dtype=str) @@ -22,6 +22,7 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): while event != sg.WIN_CLOSED and event != "Close Window": event, values = window.read() + #If event is the pressing of an incrementing button AND the textbox search returns exactly one name if event in ["+APP","-APP","+ACC","-ACC","+REJ","-REJ","+CAN","-CAN","+FUL","-FUL"] and netID != -1 and len(netIDs)<=1: if event[0]=="+": change = 1 @@ -33,16 +34,6 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): event = "Find History" if event=="Find History": - window['+APP'].Update(disabled=False) - window['-APP'].Update(disabled=False) - window['+ACC'].Update(disabled=False) - window['-ACC'].Update(disabled=False) - window['+REJ'].Update(disabled=False) - window['-REJ'].Update(disabled=False) - window['+CAN'].Update(disabled=False) - window['-CAN'].Update(disabled=False) - window['+FUL'].Update(disabled=False) - window['-FUL'].Update(disabled=False) name = values[0] netIDs=HF.nameToID(name,getAllMatches=True) if not hasattr(netIDs,'__len__') or len(netIDs)==0: @@ -58,10 +49,21 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): names = [HF.IDToName(i) for i in netIDs[:]] window['OUTPUT'].update(value="Multiple matches found: ["+", ".join(names)+"]") else: - + #Enable the incrementing buttons once a single valid name is found + window['+APP'].Update(disabled=False) + window['-APP'].Update(disabled=False) + window['+ACC'].Update(disabled=False) + window['-ACC'].Update(disabled=False) + window['+REJ'].Update(disabled=False) + window['-REJ'].Update(disabled=False) + window['+CAN'].Update(disabled=False) + window['-CAN'].Update(disabled=False) + window['+FUL'].Update(disabled=False) + window['-FUL'].Update(disabled=False) + #Get the number of previous requests in each category for that ULA APP,ACC,REJ,CAN,FUL=HF.getSubCount(netID) - #Printing options for Pandas + #Printing options for the Pandas dataframe pd.set_option('display.min_rows', 20) pd.set_option('display.expand_frame_repr', False) pd.set_option('max_colwidth', 30) @@ -71,9 +73,11 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): output="No history" else: output=str(output.to_string(index=False)) + #Change the textbox to include the relevant data window['OUTPUT'].update(value=name+"; "+netID+"\n"+"APP:"+APP+" ACC:"+ACC+" REJ:"+REJ+" CAN:"+CAN+" FUL:"+FUL+"\n"+output) if event=="Hall of Fame": + #Disable the incrementing buttons while in "Hall of Fame" mode since there is no single name attached. window['+APP'].Update(disabled=True) window['-APP'].Update(disabled=True) window['+ACC'].Update(disabled=True) @@ -84,9 +88,12 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): window['-CAN'].Update(disabled=True) window['+FUL'].Update(disabled=True) window['-FUL'].Update(disabled=True) + #Printing options for Pandas pd.set_option('display.expand_frame_repr', False) pd.set_option('max_colwidth', 30) + + #Display instructions while the inputStr is not a valid input for the Hall of Fame output='To customize which categories to add (App,Acc,Rej,Can,Ful):\nuse 0,1,2,3,4 or any combination thereof in the text box then click "Hall of Fame" again\n(e.g. "12" yields Accepted plus Rejected)\n' inputStr=values[0] if inputStr=="" or not inputStr.isnumeric(): @@ -97,4 +104,3 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont): window.close() - \ No newline at end of file diff --git a/substitutionManualWindow.py b/substitutionManualWindow.py index 38dbf33..b38c639 100644 --- a/substitutionManualWindow.py +++ b/substitutionManualWindow.py @@ -4,13 +4,13 @@ import pandas as pd import helperFunctions as HF from datetime import datetime, timedelta -def subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont): +def subManualWin(subRequestsArchiveFilename,defaultFont): - stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False) - - allnames=list(stfD["Name"].values) - sections=HF.getSectionTitles() + #Pull all names and section titles for the poopulation of the drop-down boxes + allnames=HF.getAllNames() + sections=HF.getAllSectionTitles() + #Define the window's layout layout=[[sg.Text("Email?| Sec | Date | Replacee | Replacement")], [sg.Checkbox('',size=(3,1),key='-EMAILCHECK-'),sg.Combo(sections,size=(6,1),key="-SECTION-",readonly=True, enable_events=True),sg.Combo([""],size=(6,1),key='-DATE-',readonly=True, enable_events=True),sg.Combo([""],size=(30,1),key="-REPLACEE-", enable_events=True,readonly=True),sg.Combo(allnames,size=(30,1),key="-REPLACEMENT-", enable_events=True,readonly=True)], [sg.Text("Reason for manual change: "),sg.Input(key='-REASON-')], @@ -22,45 +22,50 @@ def subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,d #Event Loop to process "events" and get the "values" of the inputs #Cease running if they close the window event=-1 - while event != sg.WIN_CLOSED and event != "Close Window": event, values = window.read() + #If the 'section' box is interacted with and is not blank (i.e. an option was selected from the dropdown box) if event=="-SECTION-" and values['-SECTION-']!="": sec=values['-SECTION-'] dates=HF.getDatesFromSection(sec) - window["-DATE-"].update(values=dates) + window["-DATE-"].update(values=dates)#update the dropdown box of dates with the appropriate values for this section window["-REPLACEE-"].update(values=[""]) + #If the 'date' box is interacted with and is not blank (i.e. an option was selected from the dropdown box) if event=="-DATE-" and values['-DATE-']!="": date=values["-DATE-"] names=list(HF.getAllNamesFromSection(sec,date))[0] - window["-REPLACEE-"].update(values=names) + window["-REPLACEE-"].update(values=names)#update the dropdown box of names with the appropriate values for this section/date combo - + #Button was pressed to process the manual change if event == "Make Change": sec=values['-SECTION-'] date=values["-DATE-"] replacee=HF.nameToID(values["-REPLACEE-"]) replacement=HF.nameToID(values["-REPLACEMENT-"]) - timeStamp=str(datetime.now()) - request=[timeStamp,replacee,sec,date,replacement,values["-REASON-"],"APP","MANUALLY CHANGED BY COURSE ADMINS"] + timeStamp=str(datetime.now())#Consider the submission time to be right now, when the button was pressed. + request=[timeStamp,replacee,sec,date,replacement,values["-REASON-"],"APP","MANUALLY CHANGED BY COURSE ADMINS"]#create a request in the style of the Google form, for archiving purposes - HF.reassign(date,sec,replacee,replacement) + HF.reassign(date,sec,replacee,replacement)#Actually perform the replacement + #Send the email confirmations out to the relevant parties + #NOTE: This is BEFORE the writing step so that if there's an error in emails (such as failed to authenticate) the code aborts BEFORE writing chnages to the database. + if values['-EMAILCHECK-']: + HF.generateEmails([request]) + + #write to the archive database with open(subRequestsArchiveFilename,'a', newline='') as f: writer = csv.writer(f) writer.writerows([request]) - + #Reset the dropdowns to the original options window["-SECTION-"].update(values=sections) window["-DATE-"].update(values=[""]) window["-REPLACEE-"].update(values=[""]) window["-REPLACEMENT-"].update(values=allnames) - if values['-EMAILCHECK-']: - HF.generateEmails([request]) - window.close() \ No newline at end of file + window.close() diff --git a/substitutionRequestForm.csv b/substitutionRequestForm.csv index c302847..6697163 100644 --- a/substitutionRequestForm.csv +++ b/substitutionRequestForm.csv @@ -1 +1,27 @@ Timestamp,Requestor,Section,Dates,Replacement,Reason +2023-09-19 13:57:17.596000,sipahiog,Sec 46,09/19,haggart3,Sickness +2023-09-19 18:45:55.239000,sebalyma,Sec 40,10/10,ngvivian,will be in ohio for beginning of that week +2023-09-19 22:32:06.420000,mcgui186,Sec 6,09/21,schul769,Funeral +2023-09-20 17:12:04.654000,austi143,Sec 11,09/20,,"Grandpa had stroke, have to go home" +2023-09-20 17:12:30.949000,gautamya,Sec 21,09/20,,Sick +2023-09-20 17:29:31.529000,upadhy19,Sec 4,09/20,,Strong Stomachache +2023-09-21 13:22:29.001000,sipahiog,Sec 46,09/21,,Sickness +2023-09-21 18:25:24.758000,mcgui186,R_HR2,09/21,sipahiog,Funeral +2023-09-21 18:26:11.335000,mcgui186,R_HR1,09/21,haggart3,Funeral +2023-09-23 14:25:11.349000,borekmi1,Sec 1,09/25,sebalyma,Problem with the transportation to MSU +2023-09-25 17:05:16.919000,burgejae,Sec 46,09/26,aggarw75,Important meeting came up +2023-09-25 17:05:25.520000,anindhos,M_HR1;M_HR2,09/25,bhardw41,Mandatory project meeting +2023-09-25 21:37:26.276000,aggarw75,Sec 11,09/27,lnumehak, +2023-09-26 01:51:03.453000,john7531,S_HR1;S_HR2,10/02,bhardw41,Laptop died. Replacement may not come before my shift. +2023-09-26 14:52:30.307000,darshanv,W_HR1;W_HR2;Sec 20,09/27,micksoph,Visiting the SSN office for on campus registration and receiving SSN +2023-09-26 16:25:19.977000,sipahiog,T_HR1;T_HR2,09/26,haggart3,Something came up with a class +2023-09-28 14:58:15.450000,sonarsoh,Sec 17,10/02;10/04,tagaychr,broken arm +2023-10-03 19:14:01.917000,sipahiog,Sec 30,10/09,john7531,out of town +2023-10-04 19:47:39.642000,schne542,Sec 19,10/09,aggarw75,I have a doctors appointment on 10/9 back home. +2023-10-04 20:02:13.852000,mcgui186,R_HR1;R_HR2,10/05,haggart3,I need to go to helproom for a project of mine +2023-10-04 20:07:34.501000,sipahiog,Sec 46,10/10,haggart3,Out of town +2023-10-09 17:51:15.406000,austi143,Sec 11,10/09,bhardw41,I’m sick +2023-10-11 01:06:38.157000,pittend2,Sec 25,10/11,kumararn,Sick +2023-10-11 16:49:39.541000,murraydr,M_HR1;M_HR2,10/09,,test1 +2023-10-11 16:50:11.365000,murraydr,M_HR1,10/02;10/09,,test2 +2023-10-11 17:24:59.666000,austi143,Sec 11,10/11,lnumehak,I’m sick