From 0bddc4a3e4a3b4254909f0315128c34a9b9e4344 Mon Sep 17 00:00:00 2001 From: Peter Howell Date: Thu, 24 Jul 2025 10:53:43 -0700 Subject: [PATCH] catchup --- courses.py | 37 +++++++--- degrees.py | 195 ++++++++++++++++++++++++++++++++++++++++++++----- localcache2.py | 90 +++++++++++++++++++++++ schedules.py | 6 +- semesters.py | 1 + ssb.py | 22 ++++-- tasks.py | 30 +++++--- users.py | 76 ++++++++++++++++--- 8 files changed, 398 insertions(+), 59 deletions(-) diff --git a/courses.py b/courses.py index 63d4233..f7b2ce9 100644 --- a/courses.py +++ b/courses.py @@ -247,7 +247,7 @@ def users_in_by_depts_live(depts=[], termid='181'): print("Getting enrollments for %s" % c['course_code']) if d in courses_by_by_dept: courses_by_by_dept[d].append(c) else: courses_by_by_dept[d] = [ c, ] - for u in course_enrollment_with_faculty(c['id'],1).values(): + for u in course_enrollment_with_faculty(c['id'],0).values(): #if u['type'] != "StudentEnrollment": continue if not (d in students_by_by_dept): students_by_by_dept[d] = set() @@ -829,9 +829,10 @@ def course_term_summary_3(): # check number of students and publish state of all shells in a term def all_semester_course_sanity_check(): - outputfile = 'cache/courses_checker.csv' - t = 287 - term = "sp25" + term = "su25" + target_start = "6-14" + outputfile = f'cache/courses_checker_{term}.csv' + t = 288 c = getCoursesInTerm(t,1,0) sched1 = requests.get(f"http://gavilan.cc/schedule/{term}_sched_expanded.json").json() sched = { x['crn']: x for x in sched1 } @@ -840,7 +841,7 @@ def all_semester_course_sanity_check(): #output.write( ",".join(['what','id','parent_course_id','sis_course_id','name']) + "\n" ) output2 = codecs.open(outputfile,'w','utf-8') output2.write( ",".join(['id','sis_course_id','name','state','mode','startdate','students']) + "\n" ) - htmlout = codecs.open('cache/courses_checker.html','w','utf-8') + htmlout = codecs.open(f'cache/courses_checker_{term}.html','w','utf-8') htmlout.write('\n') htmlout.write(f'\n') html_sections = [] @@ -859,10 +860,11 @@ def all_semester_course_sanity_check(): ctype = sched[crn]['type'] cstart = sched[crn]['start'] ts = sched[crn]['act'] + teacher = sched[crn]['teacher'] info = [ 'course', course['id'], '', course['sis_course_id'], course['name'], course['workflow_state'], ts ] info = list(map(str,info)) - info2 = [ course['id'], course['sis_course_id'], course['name'], course['workflow_state'], ctype, cstart, ts ] + info2 = [ course['id'], course['sis_course_id'], course['name'], course['workflow_state'], ctype, cstart, ts, teacher ] info2 = list(map(str,info2)) output2.write( ",".join(info2) + "\n" ) output2.flush() @@ -870,8 +872,8 @@ def all_semester_course_sanity_check(): #output.write( ",".join(info) + "\n" ) uu = f"https://ilearn.gavilan.edu/courses/{course['id']}" - if course["workflow_state"]=='unpublished' and ctype=='online' and cstart=="1-27": - html_sections.append(f'\n') + if course["workflow_state"]=='unpublished' and ctype=='online' and cstart==target_start: + html_sections.append(f'\n') #uu = url + '/api/v1/courses/%s/sections' % str(course['id']) #course['sections'] = fetch(uu) #s_info = [ [ 'section', y['id'], y['course_id'], y['sis_course_id'], y['name'], y['total_students'] ] for y in course['sections'] ] @@ -1047,7 +1049,7 @@ def enroll_id_list_to_shell(id_list, shell_id, v=0): # multiple semesters def enroll_stem_students_live(): - semesters = [286,287] + semesters = [288,289] for S in semesters: enroll_stem_students_live_semester(S) @@ -1249,8 +1251,11 @@ def enroll_gott_workshops(): # date, title, shell_id #['2025-02-23 16:00:00', 'GOTT 6: Intro to Synchronous Teaching (Sync/Hyflex)', 21835], - ['2025-03-14 17:00:00', 'GOTT 5: The Essentials of Blended Learning (Hybrid) ', '21886'], + #['2025-03-14 17:00:00', 'GOTT 5: The Essentials of Blended Learning (Hybrid) ', '21886'], #['2025-02-23 16:00:00', 'GOTT 1: Intro to Teaching Online (2 week, async)', 21874] + ['2025-05-26 17:00:00', 'GOTT 2: Introduction to Asynchronous Teaching and Learning', 23015], + ['2025-06-01 17:00:00', 'GOTT 1: Intro to Teaching Online', 23083], + ['2025-06-01 17:00:00', 'GOTT 4: Assessments in Digital Learning', 21898], ] #print(json.dumps(signups,indent=4)) #print(json.dumps(by_email,indent=4)) @@ -1271,6 +1276,11 @@ def enroll_gott_workshops(): 'lorrmay36@mac.com': 'llevy@gavilan.edu', 'gkalu1@gmail.com': 'gkalu@gavilan.edu', 'rpotter@gav.edu': 'rpotter@gavilan.edu', + 'ally162@qq.com': 'aao@gavilan.edu', + 'davidamancio791@gmail.com': 'damancio@gavilan.edu', + 'carissaamunoz83@gmail.com': 'amunoz@gavilan.edu', + 'jasonwcpa@yahoo.com': 'jwolowitz@gavilan.edu', + } for each_workshop in workshop_ids: @@ -1792,6 +1802,9 @@ def teacher_to_many_shells(): import os, pickle def create_sandboxes(): + + ## TODO: read all student names and determine ahead of time if initials conflict. deal with them + courses_to_sandbox = [ #(20567, ' Sandbox GOTT1 SU24'), #(20575, ' Sandbox GOTT2 SU24'), #(20600, ' Sandbox GOTT4 SU24'), @@ -1800,7 +1813,9 @@ def create_sandboxes(): #(20761, ' Sandbox GOTT1 FA24'), #(21770, ' Sandbox GOTT1 WI25'), #(21772, ' Sandbox GOTT2 WI25'), - (21874, ' Sandbox GOTT1 SP25'), + (23083, ' Sandbox GOTT1 SU25'), + (23015, ' Sandbox GOTT2 SU25'), + (21898, ' Sandbox GOTT4 SU25'), ] filepath = 'cache/sandbox_courses.pkl' diff --git a/degrees.py b/degrees.py index 0b609ad..b2f9954 100644 --- a/degrees.py +++ b/degrees.py @@ -10,6 +10,7 @@ import pandas as pd debug_out = codecs.open("cache/degrees_debug.txt", "w", "utf-8") def d(s): + global debug_out #if type(s) == tuple or type(s) == list: # debug_out.write(" ".join(str(s)) + "\n") debug_out.write(str(s) + "\n") @@ -112,6 +113,13 @@ class DSLTransformer(Transformer): d("\ncourse_declaration") d(name) d(units) + units_value = float(units.children[0]) # Handle "3", "3.0", etc. + #prereq_list = [p.value for p in prereqs if p is not None] + course = Course(name.value) + course.units = units_value + #course.prereqs = prereq_list + self.courses[name.value] = course + return course def take_all(self): d("\ntake_all") @@ -140,8 +148,11 @@ class DSLTransformer(Transformer): def course(self, c): d("\ncourse") - d(c) - return Course(c.value) + d(c) + name = c.value + if name not in self.courses: + self.courses[name] = Course(name) + return self.courses[name] def course_or(self, *items): d("\ncourse_or") @@ -330,32 +341,40 @@ rule_lookup = { 'Choose (one) or more', 'Choose (one|two|three) of the classes listed', 'Choose (one|two|three)', + 'Choose (4) classes (12 units) from the following list:', 'Choose ([\d\w]+) courses from', 'Choose (\w+) of the following', + 'Please select (ONE) course from the options below:', 'LIST [AB]: Select (\d)', 'Select (1)', 'Select (one) of the following REQUIRED CORE', 'SELECT (ONE|TWO) OF THE FOLLOWING', 'Select (one|two|three)', 'Select (\d+) courses', ], - 'take at least n units': [ '(\d+) units total', + 'take at least n units': [ '(\d+) units total', '(\d+) Unit Minimum', 'Any combination totaling (\d+) units', 'Choose (eight|\d+) units', 'Choose (\w+) units from classes listed', 'Choose a minimum of ([\w\d]+) units from', 'Choose a minimum of (\d+) units', + 'Choose at least ([\d|\.]+) units', + 'Choose eighteen \((18)\) units from the following list:', 'Choose any combination of courses for a minimum of ([\w\d]+) units\:?', + 'Choose any combination of the following courses for a minimum of (\d+) units:', 'Choose courses for at least ([\w\d]+) units', 'LIST A \((\d+) units\)', 'LIST B \((\d+) units\)', 'LIST C \- Any course .*\((\d+) units\)', 'Select (\d+) units', 'Select any (\d+)\-\d+ units from the following',], - 'electives': ['Electives', 'Recommended electives?:', ], - 'take_all': ['RN PROGRAM', 'REQUIRED CORE', 'CORE COURSES', 'ADDITIONAL REQUIREMENTS','REQUIREMENTS:', - 'Requirements', 'Core Requirements', + 'electives': ['Electives', 'Recommended electives?:', 'Recommended Elective'], + 'take_all': ['RN PROGRAM', 'REQUIRED CORE', 'CORE COURSES', 'CORE Courses', 'Core Major Courses', 'Core Courses :', + 'ADDITIONAL REQUIREMENTS', 'REQUIREMENTS:', 'REQUIREMENTS', 'Required:', + 'Requirements', 'Core Requirements', 'Required Courses', 'Required Core', 'REQUIRED', 'LVN PROGRAM', 'Student Teaching Practicum', '^LIST A:?$', '^LIST B:$', + 'Complete the following courses:', 'Complete the following required courses for 6 units:', 'Complete all of the following courses:', + 'Complete all of the following General Education (Cal-GETC) courses:', 'Program Requirements', 'Required Courses:', 'PROGRAM REQUIREMENTS (5 Units)', - 'PROGRAM REQUIREMENTS (162 Hours)', + 'PROGRAM REQUIREMENTS (162 Hours)', 'PROGRAM REQUIREMENTS (5 Units):', 'PROGRAM REQUIREMENTS (162 Hours):', ], } @@ -373,6 +392,7 @@ def lookup_rule(line): pass if verbose: print(f"line: {line} matched: {each} with {num}") return key,num + print(f"** failed to lookup rule: {line}") return None,None @@ -394,23 +414,153 @@ def num_units(s): +def examine5(li,award, verbose=0): + summary = [x[0] for x in li] + if summary[1] in ['and','or'] and summary[3] in ['and','or']: + if verbose: print(" - ", summary) + return summary[1] + return False + + def examine(li,award, verbose=0): summary = [x[0] for x in li] if summary[1] in ['and','or']: if verbose: print(" - ", summary) + return summary[1] + return False - +# given list of courses, pbd like: [c1, c2, c3, or, c4, c5, c6, or, c7, or, c8, c9], +# return [c1, c2, [or, c3, c4], c5, [or, c6, c7, c8], c9] +# given [c1, 'or', c2, 'or', c3, 'or', c4, 'and', c5, 'and', c6] +# return ['or', c1, c2, c3, [ 'and', c4, c5, c6 ]] def check_ands_ors_pbd(award, pbd, verbose=0): - verbose = 0 + if not pbd: + return [] + + i = 0 + or_group = [] + and_group = [] + + while i < len(pbd): + token = pbd[i][0] + + if token == 'or': + i += 1 # skip 'or', next item is part of or_group + continue # will be handled by next loop iteration + + elif token == 'and': + # Start or continue and_group + i += 1 + if i < len(pbd): + and_group.append(pbd[i]) + i += 1 + else: + # If previous token was not 'and', treat as part of or_group + if i == 0 or pbd[i-1] != 'and': + or_group.append(token) + i += 1 + + result = ['or'] + or_group + if and_group: + result.append(['and'] + and_group) + + return result + + +def check_ands_ors_pbd2(award, pbd, verbose=0): + summary = [x[0] for x in pbd] + #if verbose: print(" ", summary) + for i,x in enumerate(summary): + desc = '' + if x == 'course': desc = f"\t{pbd[i][2]['code']} {pbd[i][2]['name']}" + elif x == 'or': desc = '' + elif x == 'and': desc = '' + else: desc = pbd[i][2] + print(f"{x}\t{desc}") + result = [] + i = 0 + while i < len(pbd): + + #if pbd[i][1]=='21' and pbd[i][2]=='Political Science': + # print('here') + + if pbd[i][0] == 'or': + + # special case: math 233 or higher + if i < len(pbd) and pbd[i+1][2] == 'higher': + result[-1][2]['name'] += "OR HIGHER" + i += 2 + continue + + # Take last item from result + left = result.pop() + group = ['or', left, pbd[i+1]] + i += 2 + if i < len(pbd) and pbd[i][0] == 'or': + group.append(pbd[i+1]) + i += 2 + result.append(group) + + elif pbd[i][0] == 'and': + # Take last item from result + left = result.pop() + group = ['and', left, pbd[i+1]] + i += 2 + if i < len(pbd) and pbd[i][0] == 'and': + group.append(pbd[i+1]) + i += 2 + result.append(group) + + else: + result.append(pbd[i]) + i += 1 + #print(result) + return result + + +def check_ands_ors_pbd1(award, pbd, verbose=0): + verbose = 1 + return_list = [] if verbose: print(f"check_ands_ors_pbd({award}, ...)") summary = [x[0] for x in pbd] - if verbose: print(" ", summary) + #if verbose: print(" ", summary) + for i,x in enumerate(summary): + desc = '' + if x == 'course': desc = f"\t{pbd[i][2]['code']} {pbd[i][2]['name']}" + elif x == 'or': desc = '' + else: desc = pbd[i][2] + print(f"{x}\t{desc}") + + # iterate through in groups of 5, from 0/1/2/3/4 up to 6/7/8/9/10 + # (for length 1. length n: n-2, n-1, n) + skip = 0 + for i in range(len(pbd)-4): + if skip>0: + skip -= 1 + continue + cmd = examine5(pbd[i:i+5], award, verbose) + if cmd: + skip = 4 + return_list.append( [cmd, pbd[i], pbd[i+2], pbd[i+4]] ) + else: + return_list.append( pbd[i] ) + # iterate through in groups of 3, from 0/1/2 up to 3/4/5 # (for length 6. length n: n-2, n-1, n) + skip = 0 for i in range(len(pbd)-2): - examine(pbd[i:i+3], award, verbose) - if verbose: print() + if skip>0: + skip -= 1 + continue + cmd = examine(pbd[i:i+3], award, verbose) + if cmd: + skip = 2 + return_list.append( [cmd, pbd[i], pbd[i+2]] ) + else: + return_list.append( pbd[i] ) + #if verbose: print(return_list) + return return_list def build_program_rules(verbose=0): @@ -467,13 +617,21 @@ def build_program_rules(verbose=0): # Each 'program block definition' # 1st is dict with unit totals, rest are lists. - check_ands_ors_pbd( award, sorted( r[k][1:], key=lambda x: float(x[1])) ) - for each_r in sorted( r[k][1:], key=lambda x: float(x[1])): + requirements_inorder = sorted( r[k][1:], key=lambda x: float(x[1])) + reqs_with_or_blocks = check_ands_ors_pbd( award, requirements_inorder ) + for each_r in reqs_with_or_blocks: - if each_r[0] in ['and','or']: - #print(' ', each_r[0],each_r[1]) - pass - if isinstance(each_r, list): + if each_r[0] == 'or': + if not this_rule: + this_rule = "take_all from " + this_rule += ' or '.join( [x[2]['code'] for x in each_r[1:] ] ) # each_r[1][2]['code']} or {each_r[2][2]['code']}, " + + if each_r[0] == 'and': + if not this_rule: + this_rule = "take_all from " + this_rule += f"{each_r[1][2]['code']} and {each_r[2][2]['code']}, " + + elif isinstance(each_r, list): #print(each_r) if each_r[0] == 'h3' or (each_r[0]=='noncourse' and is_noncourse_new_section(each_r[2])): # This is a rule title @@ -533,6 +691,7 @@ def build_program_rules(verbose=0): if ok: okay.append(p) + verbose = 1 if verbose: print("\n\n\n\nThese programs are okay:") for p in okay: diff --git a/localcache2.py b/localcache2.py index 9cff7e3..5034af4 100644 --- a/localcache2.py +++ b/localcache2.py @@ -11,6 +11,7 @@ from os.path import exists, getmtime #from pipelines import sync_non_interactive, url, header, gp, dean from tabulate import tabulate +from semesters import short_to_sis from canvas_secrets import postgres_database, postgres_password, postgres_port, postgres_user, postgres_host ######### @@ -448,6 +449,94 @@ def courses_to_sched(): conn.close() +# Populate schedule table and correlate to courses table +def refresh_semester_schedule_db(term="fa25"): + + # TODO: fix units when they are variable... change to float in between range. round to 0.5 unit. + + EXECUTE = 1 + last_time = 0 + + sis_code = short_to_sis(term) + + conn,cur = db() + + drop_query = f"DELETE FROM canvas.schedule WHERE sem='{sis_code}'" + print(f"executing: {drop_query}") + cur.execute(drop_query) + + # get all ilearn courses + query = """SELECT c.id, c.workflow_state, c.sis_source_id, c.course_code, c.enrollment_term_id, t.name + FROM canvas.courses c + JOIN canvas.enrollment_terms t ON c.enrollment_term_id = t.id + ORDER BY c.sis_source_id, c.course_code;""" + + + cur.execute(query) + conn.commit() + + sis_to_sched = {} + for row in cur.fetchall(): + sis_source_id = row[2] # c.sis_source_id + sis_to_sched.setdefault(sis_source_id, []).append(row) + + + vals_cache = [] + i = 0 + + print(sis_code) + sched = 0 + try: + sched = requests.get(f"http://gavilan.cc/schedule/{term}_sched_expanded.json").json() + except Exception as e: + print(e) + + query = "INSERT INTO canvas.schedule (canvascourse, crn, code, units, teacher, start,\"end\", type, loc, site, partofday, cap, act, sem) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);" + + if sched: + for c in sched: + try: + pod = '' + if 'partofday' in c: pod = c['partofday'] + #print(c['cred']) + cred_match = re.search(r'(\d+\.\d+)\-(\d+\.\d+)',c['cred']) + if cred_match: + #print(f"matched range: {cred_match.groups}") + cred_start = float(cred_match.group(1)) + cred_end = float(cred_match.group(2)) + mid = float(int( (cred_end-cred_start)/2 + cred_start )) + c['cred'] = str(mid) + #print(f"middle cred is {c['cred']}") + + full_sis_code = sis_code+'-'+c['crn'] + + if full_sis_code in sis_to_sched: + print(c['cred']) + q = [sis_to_sched[full_sis_code][0][0], c['crn'], c['code'], c['cred'], c['teacher'], c['start'], c['end'], c['type'], c['loc'], c['site'], pod, int(c['cap']), int(c['act']), sis_code] + vals_cache.append( q ) # [ str(x) for x in q ] ) + #print(f"{i}: {q}") + i += 1 + if i % 100 == 0: + if EXECUTE: + cur.executemany(query, vals_cache) + conn.commit() + vals_cache = [] + t = time.process_time() + delta = t - last_time + last_time = t + print(f"Loop {i} - committed to db in %0.3fs. " % delta, flush=True) + else: + print(f"{full_sis_code} not in canvas courses.") + except Exception as e: + print(e) + + if EXECUTE: + cur.executemany(query, vals_cache) + conn.commit() + cur.close() + conn.close() + + def student_count(courseid): conn,cursor = db() q = f"""SELECT COUNT(u.id) AS student_count FROM canvas.courses AS c @@ -498,6 +587,7 @@ if __name__ == "__main__": 4: ['new students this semester', users_new_this_semester], 5: ['all semester courses + teachers', all_sem_courses_teachers], 6: ['Populate schedule table and correlate to courses table', courses_to_sched], + 7: ['refresh db schedule 1 semester', refresh_semester_schedule_db], } diff --git a/schedules.py b/schedules.py index b5a0b6a..1a64ba0 100644 --- a/schedules.py +++ b/schedules.py @@ -124,7 +124,7 @@ HTM,skill,cwp''' areas_csv = '''Area,AreaCode,DeanCode,DeanName Nursing and Allied Health,nah,et,[Dean: Nursing/Allied Health] -Career Education and Workforce Pathways,cwp,ss,Vins Chacko +Career Education and Workforce Pathways,cwp,vc,Vins Chacko Arts Humanities and Social Sciences,ahss,nl,[Dean: AHSS] Counseling,c,de,Diego Espinoza Student Support and Special Programs,sssp,de,Diego Espinoza @@ -1084,6 +1084,8 @@ def find_goo_for_name(name_dict,name): return '' # All teachers' teaching history +# need schedule files to exist and be current here: +# \canvasapp\cache\schedules def teachers_history(): # Let's just make a giant dict of names we know about -> G numbers @@ -1129,8 +1131,6 @@ def teachers_history(): all_sections_by_goo = defaultdict(list) - #non_matches_file = codecs.open('cache/nonmatches.txt','w','utf-8') - # Define the regex pattern pattern = re.compile(r"(\w\w\d\d)_sched_expanded\.json") diff --git a/semesters.py b/semesters.py index f33badb..1af2e36 100644 --- a/semesters.py +++ b/semesters.py @@ -24,6 +24,7 @@ def short_to_sis(s): return "20" + s[2:5] + s_to_n[season] + # go from sp20 to 2020spring def short_to_long(s): parts = re.search(r'(\w\w)(\d\d)', s) diff --git a/ssb.py b/ssb.py index 8f28e59..7d82fc1 100644 --- a/ssb.py +++ b/ssb.py @@ -337,6 +337,9 @@ def scrape_schedule(short_sem, semester_label): writepage(driver.page_source) print(driver.title) + # switch to new tab + driver.switch_to.window(driver.window_handles[1]) + driver.find_element(By.LINK_TEXT,"Registration").click() print('registration') driver.implicitly_wait(5) @@ -421,6 +424,7 @@ def scrape_schedule(short_sem, semester_label): except Exception as e: print("Couldn't Diff.") print("Got an exception: ", e) + return 2 # fname = pathlib.Path('test.py') # assert fname.exists(), f'No such file: {fname}' # check that the file exists # print(fname.stat()) @@ -438,14 +442,14 @@ def scrape_schedule(short_sem, semester_label): except Exception as e: print("Got an exception: ", e) #print("There was an error: " + e.args[0] + ". The line where the code failed was " + str(traceback.extract_stack())) + return 1 finally: driver.quit() + return 0 -def expanded(as_dict, short_sem): - #as_dict = scrape_schedule() - +def expanded(short_sem): course_to_gp, course_to_area, areacode_to_area, area_to_dean, course_to_dean, dean_code_to_name = schedules.campus_dept_hierarchy() expanded = list_latestarts(short_sem) @@ -582,9 +586,11 @@ with open('cache/to_scrape.json', 'r') as f: # Loop through each item and call the function for item in semesters: - as_dict = scrape_schedule(item['short_sem'], item['sem']) - ex = expanded(as_dict, item['short_sem']) - print(f"Done with {item['sem']}. Sleeping 45 seconds.") - time.sleep(45) - + result = scrape_schedule(item['short_sem'], item['sem']) + if result == 0: + ex = expanded(item['short_sem']) + print(f"Done with {item['sem']}. Sleeping 45 seconds.") + time.sleep(45) + else: + print(f"Stopped due to error: {result}") diff --git a/tasks.py b/tasks.py index 42f16f3..ae942e7 100644 --- a/tasks.py +++ b/tasks.py @@ -20,7 +20,7 @@ from collections import defaultdict from time import gmtime, strftime from time import mktime -from semesters import sem_to_short +from semesters import human_to_short from canvas_secrets import badgr_target, badgr_hd @@ -167,6 +167,7 @@ def send_email(fullname, firstname, addr, subj, content): def convert_to_pdf(name1, name2): wd = 'C:\\Users\\peter\\Documents\\gavilan\\canvasapp\\' wd = 'I:/canvasapp/' + wd = 'C:/Users/phowell/source/repos/canvasapp/' print( wd + name1 ) try: word = win32.DispatchEx("Word.Application") @@ -182,16 +183,27 @@ def convert_to_pdf(name1, name2): # Build (docx/pdf) certificates for gott graduates def certificates_gott_build(): + course = "gott_1_su25" + coursedate = "Summer 2025" + certificate = "gott 1 template.docx" + + #course = "gott_4_su25" + #certificate = "gott 4 template.docx" + i = 0 - for row in csv.reader( open('cache/completers_gott2_wi24.csv','r'), delimiter=','): + for row in csv.reader( open(f'cache/completers_{course}.csv','r'), delimiter=','): i += 1 - if i < 3: continue + if i < 2: continue print(row[0]) - lname,fname = row[0].split(",") - name = fname.strip() + " " + lname.strip() - doc = DocxTemplate("cache/certificates/gott 2 template.docx") - doc.render({ 'name' : row[0] }) - fn = "cache/certificates/gott_2_wi24%s." % re.sub('\s', '_', name.lower()) + try: + lname,fname = row[0].split(",") + name = fname.strip() + " " + lname.strip() + except Exception as e: + name = row[0].strip() + doc = DocxTemplate(f"cache/certificates/{certificate}") + doc.render({ 'name' : name, 'coursedate': coursedate }) + name_as_filename = re.sub('\s', '_', name.lower()) + fn = f"cache/certificates/{course}_{name_as_filename}." print(fn+'docx') doc.save(fn+'docx') convert_to_pdf(fn+'docx', fn+'pdf') @@ -1309,7 +1321,7 @@ if __name__ == "__main__": 15: ['create a week calendar in word (general purpose)', word_calendar_v2], 16: ['create GOTT certificates', certificates_gott_build], 20: ['build_quiz', build_quiz], - 21: ['certificates_gott_build, certificates_gott_build'] + #21: ['certificates_gott_build, certificates_gott_build'] } if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]): diff --git a/users.py b/users.py index eced3cf..3c996b1 100644 --- a/users.py +++ b/users.py @@ -1,4 +1,5 @@ +from importlib import reload import json, codecs, requests, re, pdb, csv, textdistance, collections import sys, csv, string, funcy, math, shutil, os import pytz, time @@ -20,6 +21,7 @@ from schedules import campus_dept_hierarchy from util import dept_from_name, most_common_item from os.path import exists, getmtime +from localcache2 import refresh_semester_schedule_db from canvas_secrets import url from dateutil import parser @@ -2206,10 +2208,11 @@ def cross_ref_training(): wb = load_workbook("C:/Users/phowell/Downloads/GOTT_Completion_masterlist 2023 DEC.xlsx") print(wb.sheetnames) + term = "202570" # Fetch from Canvas DB. Make sure its recently updated. # Also relies on schedule being in database. Run localcache2.courses_to_sched() - courses = all_2x_sem_courses_teachers('202550', '202570') # - #courses = all_sem_courses_teachers('202470') + #courses = all_2x_sem_courses_teachers('202550', '202570') # + courses = all_sem_courses_teachers(term) # report for email @@ -2220,6 +2223,13 @@ def cross_ref_training(): ask = input('download new list of teachers? (y/n) ') if ask.strip()=='y': RELOAD_TEACHERS = 1 + RELOAD_SCHEDULE = 0 + ask2 = input('refresh schedule db? (y/n) ') + if ask2.strip()=='y': RELOAD_SCHEDULE = 1 + + if RELOAD_SCHEDULE: + refresh_semester_schedule_db(term) + if RELOAD_TEACHERS: teacherRolesUpdateCache() @@ -2257,7 +2267,9 @@ def cross_ref_training(): if record[0]=='Name': continue print(f" {goo}") try: - if record[4]=="None": + if len(record)<3: + by_goo[goo][course] = "ok" + elif record[4]=="None": by_goo[goo][course] = "ok" else: by_goo[goo][course] = record[4] # record the end date @@ -2275,11 +2287,11 @@ def cross_ref_training(): for c in courses: print(c) try: - goo = c[8] - crn = c[4] + goo = c[6] ## is this changing? c[8] + crn = c[8] name = c[1] # full course name - teacher = c[6] # last, first - ctype = c[3] + teacher = c[4] # last, first + ctype = c[7] dept1 = re.search(r'([A-Z]+)(\d+)',c[2].split(' ')[0]).group(1) alldepts.add(dept1) d = list(c) @@ -2302,7 +2314,7 @@ def cross_ref_training(): deptfont = Font(bold=True) flagfont = PatternFill("solid", fgColor="00FFFFCC") - for thedean in ['et','nl','ss','jn', 'de']: + for thedean in ['et','nl','vc','jn', 'de']: sheet.cell(row=r, column=1).value = dean_code_to_name[thedean] sheet.cell(row=r, column=1).font = deptfont r += 2 @@ -2325,8 +2337,8 @@ def cross_ref_training(): waived = 0 sects = teachers[t] print(f"Sections for {t}: {sects}") - goo = sects[0][8] - course_mode = sects[0][3] + goo = sects[0][6] + course_mode = sects[0][7] print(t) sheet.cell(row=r, column=1).value = f"{t}" sheet.cell(row=r, column=2).value = f"{goo}" @@ -2573,6 +2585,48 @@ def get_port_pages(id=0): p = fetch(f"{url}/api/v1/eportfolios/{id}/pages") print(json.dumps(p, indent=2)) +def set_email_skip_confirm(): + user_id = '76741' + old_email = 'scalhoun@gavilan.edu' + new_email = 'scalhoun@hartnell.edu' + create_url = url + f'/api/v1/users/{user_id}/communication_channels' + print(create_url) + + list_channels = fetch(create_url) + print(json.dumps(list_channels,indent=2)) + for ch in list_channels: + id = ch['id'] + result = requests.delete(create_url+f"/{id}", headers=header) + print(f"deleting id {id}") + print(result.content) + + print('after deleting:') + list_channels = fetch(create_url) + print(json.dumps(list_channels,indent=2)) + print('\n\n') + + body = { + 'communication_channel[address]' : old_email, + 'communication_channel[type]' : 'email', + 'skip_confirmation': True + } + + response = requests.post(create_url, headers=header, data = body) + print(response.json()) #confirm that the channel was created + + body = { + 'communication_channel[address]' : new_email, + 'communication_channel[type]' : 'email', + 'skip_confirmation': True + } + + response = requests.post(create_url, headers=header, data = body) + print(response.json()) #confirm that the channel was created + + print('after creating:') + list_channels = fetch(create_url) + print(json.dumps(list_channels,indent=2)) + print('\n\n') if __name__ == "__main__": @@ -2607,6 +2661,8 @@ if __name__ == "__main__": 30: ['get portfolios for user id', get_portfolios], 31: ['get portfolio pages for portfolio id', get_port_pages], + 40: ['reset user email without confirmation', set_email_skip_confirm] + #3: ['Main index, 1 year, teachers and their classes', getAllTeachersInTerm], #5: ['Match names in schedule & ilearn', match_usernames], #6: ['Create Dept\'s ZTC list', create_ztc_list],
NameSIS IDStateModeStart Date# Stu
{course["name"]}{course["sis_course_id"]}{course["workflow_state"]}{ctype}{cstart}{ts}
{course["name"]}{course["sis_course_id"]}{course["workflow_state"]}{ctype}{cstart}{ts}{teacher}