diff --git a/README-images/assginment-statistics.png b/README-images/assginment-statistics.png deleted file mode 100644 index cb790922429e1742c477ed3e971ef25ef29d9496..0000000000000000000000000000000000000000 Binary files a/README-images/assginment-statistics.png and /dev/null differ diff --git a/README-images/assignment-statistics.png b/README-images/assignment-statistics.png new file mode 100644 index 0000000000000000000000000000000000000000..76bf0af95b1262326d6e240732a5f91beea33a9e Binary files /dev/null and b/README-images/assignment-statistics.png differ diff --git a/README-images/selecting-a-course.png b/README-images/selecting-a-course.png index cfd949003a7cdb97da792f3e0d5db8e8fd860a9b..dbc1fdf8df17069f49d29888d86a762ed33305d1 100644 Binary files a/README-images/selecting-a-course.png and b/README-images/selecting-a-course.png differ diff --git a/README-images/selecting-an-assignment.png b/README-images/selecting-an-assignment.png index 04581ea077aa3d605db9253c8a4ea0fce5aa96f6..bc98f3dd1728a871a5b2197544792ede5a033e32 100644 Binary files a/README-images/selecting-an-assignment.png and b/README-images/selecting-an-assignment.png differ diff --git a/README.md b/README.md index 5dfb9869c01d648c70d234c954154c41c70d3091..a1bdd035cf0665cdc0c195d1fe8e1bc3a208f99a 100644 --- a/README.md +++ b/README.md @@ -41,44 +41,13 @@ it between the opening- and closing-quotes in `config.py`. ### Getting Students' Majors -I don't currently know of a public API to pull data from MyRed. Maybe there's -an unadvertised API that ITS can provide us, but I threw this code together in -a couple of hours and it's easier for now to use what we have available. - -Log into MyRed and navigate to the [Faculty -> Class Roster page](https://myred.nebraska.edu/psc/myred/NBL/HRMS/s/WEBLIB_DSHBOARD.ISCRIPT1.FieldFormula.IScript_GETPAGE?faculty1&path=faculty1.main.cr). -On the right-side under *Filters*, select the course whose roster you're -going to download. Click on the `Apply Filters` button. Then click on the -*Download* link. - - - -Do this for each of your courses that you're going to analyze. You should be -fine pulling just the lecture sections. If you combined multiple sections into -a single Canvas course, then you can do the same for your MyRed rosters. Run -``` -dos2unix download.csv -``` -to remove unwanted unprintable characters. Then move the csv file into the -`rosters/semester` subdirectory (making the appropriate substitution for -*semester*) and rename it with the course number, such as `csce231.csv`. - -Repeat for each of your courses that you want to analyze. +You no longer need to manually retrieve students' majors from MyRed. The program +now retrieves students' majors from directory.unl.edu. ### Edit `rosters.csv` -Open `rosters/rosters.csv` for editing. After the header row, each row -corresponds to a semester. In the first column, give a plaintext name for the -semester, and in the second column specify the subdirectory that contains the -MyRed rosters for that semester. Every three columns thereafter correspond to -a specific course: a plaintext name for the course, the filename of the csv -file with the course's MyRed roster, and the course's Canvas ID. - - - - -If you use Excel to edit `rosters.csv`, pass rosters.csv` through `dos2unix` -when you're finished (even on Mac, Excel sometimes adds an unprintable Unicode -character at the start of csv files that can confuse my code). +You no longer need to maintain a rosters file. The program now retrieves your +courses from Canvas for a specified semester. ## Run the program @@ -86,23 +55,35 @@ Start the program with ``` python analyze_grades.py ``` -Select the appropriate semester. You will then be presented with a list of the -courses for that semester that you included in `rosters.csv`. Select one. +You will be prompted to confirm the program's best-guess of the semester you +want to analyze. Confirm the semester or select a different semester. You will +then be presented with a list of the courses that you are associated with for +that semester. (Note that this is now based on Canvas courses, so if you +combined courses then it will only present the course(s) as it/they appear in +Canvas.) Select one.  -You'll then be presented with an assignment group, and after you selet one, -you'll be presented with a list of assignments in that group. Select one. +Please wait patiently at ths step. To determine each student's major, the +program must access Canvas to determine their login name and then must access +the UNL directory to determine their major(s). Canvas queries in particular can +take longer than you might like. There is a progress bar to reassure you that +the program is, well, progressing. + +After the students' majors have been retrieved, you'll then be presented with an +assignment group, and after you select one, you'll be presented with a list of +assignments in that group. Select one.  -After you select the assignment, you'll be provided the statistics for it. (Be -patient, as the Canvas queries can take a few seconds.) You will then be looped back -to selecting a course. (Yes, I know there's an obvious usability optimization -here to stick with one course until you're done with it -- again, I threw this -together in a couple of hours.) +After you select the assignment, you'll be provided the statistics for it. (Again, +please be patient, as the Canvas queries can take a few seconds.) - + + +You will then be given then option to select another assignment from the same +course. If you choose not to do so, then you will be given the option to select +a different course. Right now this program will only analyze a full assignment. If you're using a particular question off of an exam or quiz, you're going to have to do that @@ -111,16 +92,36 @@ questions -- so if we want to use this program going forward then I can make that happen, but for my immediate purposes it wasn't necessary so I didn't add that code. +## Notes + +- The program will provide statistics for *all* computing majors, explicitly + stating when there are no students in a particular computing major in the + course. The program will also provide statistics for non-computing majors + represented in the course: even though this is not required for OAT, you may + find it interesting. (Due to students taking multiple majors, the numbers of + students may exceed the number of students in the course.) The program does + *not* report statistics for computing minors. +- For majors with particularly long names (*e.g.*, "Criminology and Criminal + Justice"), the formatting may be a little off. +- The program only works at the assignment level. If you are making an OAT + assessment with a specific quiz/exam question (and the quiz/exam is a Canvas + quiz), let me know and I'll add question-level functionality. + +- **I *will* be adding the option to break a course out by sections + (effectively undoing course merges). This is principally for the capstone + program so that I can separate-out students in their first capstone year from + those in their second capstone year. Perhaps it will benefit you, too.** + ## For the curious, the code - `analyze_grades.py` contains the `main` function and is pretty simple code, - under 100 lines. I hope it's readable enough that I don't need to describe - it. + a hair over 100 lines. I hope it's readable enough that I don't need to + describe it. - `config.py` you've already met. - `majors.py` defines a data structure to hold information about majors and - then defines our three majors. -- `semester.py` defines the `Course` and `Semester` classes for - `analyze_grades.py` + then defines our three computing majors as well business majors (so that the + program lumps Raikes students in business majors with non-Raikes students in + business majors). - `common_functions.py` has a few utility functions that I made for another project. I only use one of them here. - `api/canvas_classes.py` is also from another project; it has a slew of diff --git a/analyze_grades.py b/analyze_grades.py index e864a25c6b75cb65d6a1fb53b42656fc5b0306d0..cd8d02ba9ad97c7e87a14e345b3f0aae9928df0f 100644 --- a/analyze_grades.py +++ b/analyze_grades.py @@ -1,99 +1,118 @@ -import csv -import io +import datetime import statistics import sys -from typing import List, Dict, KeysView, Optional +from typing import List, Optional, Dict, Set from api.canvas_classes import CanvasAssignment, CanvasAssignmentGroup, CanvasCourse, CanvasUser from common_functions import select_from_list from majors import Major -from semester import * -rosters: str = 'rosters/rosters.csv' - -def load_semester() -> Semester: - selected_semester: Dict[str, str] - with open(rosters, mode='r') as csv_file: - csv_reader: csv.DictReader = csv.DictReader(csv_file) - candidate_semester_names: List[str] = [] - candidate_semesters: List[Dict[str, str]] = [] - for candidate_semester in csv_reader: - candidate_semester_names.append(candidate_semester['SEMESTER']) - candidate_semesters.append(candidate_semester) - selection: str = select_from_list(candidate_semester_names, 'semester') - selected_semester = [s for s in candidate_semesters if s['SEMESTER'] == selection][0] - desired_semester: Semester = Semester(selected_semester['SEMESTER'], selected_semester['SUBDIRECTORY']) - course_number: int = 1 - more_courses_remain: bool = True - semester_keys: KeysView[str] = selected_semester.keys() - while more_courses_remain: - course_key: str = f'COURSE {course_number}' - file_key: str = f'FILENAME {course_number}' - canvas_id_key: str = f'CANVAS ID {course_number}' - if course_key not in semester_keys: - more_courses_remain = False - elif selected_semester[course_key] == '': - more_courses_remain = False +def create_semester_filter() -> str: + today = datetime.date.today() + year: int = today.year + month: int = today.month + semester: str + # Normalize month to term start, recognizing the assessment may not happen until the month following the term + if month == 1: + year -= 1 + month = 8 + semester = 'Fall' + elif month < 6: + month = 1 + semester = 'Spring' + elif month < 9: + month = 5 + semester = 'Summer' + else: + month = 8 + semester = 'Fall' + answer = input(f'Analyze grades for {semester} {year}? [Y/n] ') + if answer != '' and answer[0].upper() == 'N': + year = 0 + while year == 0: + try: + answer = input('Enter year: ') + year = int(answer) + except ValueError: + print(f'\t"{answer}" is not a valid response; ' + f'please enter the year as an integer (e.g., {datetime.date.today().year}).') + semester = select_from_list(['Spring', 'Summer', 'Fall'], 'semester') + if semester == 'Spring': + month = 1 + elif semester == 'Summer': + month = 5 else: - desired_semester.add_course(Course(selected_semester[course_key], - selected_semester[canvas_id_key], selected_semester[file_key])) - course_number += 1 - return desired_semester - - -def load_roster(filename: str) -> List[Dict[str, str]]: - file = open(filename, mode='r') - file_string: str = '' - for line in file: # MyRed csv files have blank leading line - file_string = file_string if line == '\n' else f'{file_string}{line}' - file.close() - return [student for student in csv.DictReader(io.StringIO(file_string))] - - -def partition_into_majors(class_roster: List[Dict[str, str]], canvas_students: List[CanvasUser]) -> \ - Dict[str, List[CanvasUser]]: - all_majors_students: Dict[str, List[CanvasUser]] = {} - for major in Major.majors: - print(f'\tProcessing {major.name} students...', end='') - sys.stdout.flush() - major_students: List[CanvasUser] = [] - for student in class_roster: - student_majors: Set[str] = set(student['CPP'].split(', ')) - if len(student_majors.intersection(major.abbreviations)) > 0: - possible_canvas_student: List[CanvasUser] = \ - [s for s in canvas_students if s.get_nuid() == int(student['EMPLID'])] - if len(possible_canvas_student) > 0: - major_students.append(possible_canvas_student[0]) - all_majors_students[major.name] = major_students - print(f'{len(all_majors_students[major.name])} students') - return all_majors_students + month = 8 + return f'1{str(year)[-2:]}{month}' # introducing a y2.1k problem -def print_statistics(assignment: CanvasAssignment, students: Dict[str, List[CanvasUser]]) -> None: +def print_statistics_for_some_majors(assignment: CanvasAssignment, majors: Set[Major], + major_partitions: Dict[Major, Set[CanvasUser]]) -> None: points_possible: float = assignment.get_points_possible() - for major in students.keys(): - scores: List[float] = [assignment.get_score(student) for student in students[major] + for major in majors: + scores: List[float] = [assignment.get_score(student) for student in major_partitions[major] if assignment.get_score(student) is not None] try: average_score: float = statistics.mean(scores) scaled_average_score: float = 100 * average_score / points_possible - print(f'{major:27}students:{len(students[major]):>3} scaled mean score: {scaled_average_score:.2f}%') + print(f'{major.name:27}students:{len(major_partitions[major]):>3} ' + f'scaled mean score: {scaled_average_score:.2f}%') except statistics.StatisticsError as exception: - print(f'{major:27}students:{len(students[major]):>3} no mean score computed: {exception}') + print(f'{major.name:27}students:{len(major_partitions[major]):>3} no mean score computed: {exception}') -if __name__ == '__main__': - semester: Semester = load_semester() - course: Optional[Course] = select_from_list(list(semester.courses), 'course', none_is_option=True) - while course is not None: - print(f'Processing {course} for {semester}...') - roster: List[Dict[str, str]] = load_roster(f'rosters/{semester.subdirectory}/{course.roster_file}') - canvas_course: CanvasCourse = CanvasCourse(int(course.canvas_course_id)) - major_partition: Dict[str, List[CanvasUser]] = partition_into_majors(roster, canvas_course.get_students()) +def print_statistics(assignment: CanvasAssignment, major_partitions: Dict[Major, Set[CanvasUser]]) -> None: + print(f'Statistics for {assignment}:') + computing_majors: Set[Major] = {major for major in Major.majors if major.is_computing_major} + non_computing_majors: Set[Major] = {major for major in major_partitions.keys() if not major.is_computing_major} + print_statistics_for_some_majors(assignment, computing_majors, major_partitions) + print_statistics_for_some_majors(assignment, non_computing_majors, major_partitions) + + +def assess_assignments(course: CanvasCourse, major_partitions: Dict[Major, Set[CanvasUser]]) -> None: + print('You may now select the assignments to assess.') + answer: str = 'Yes' + while answer == '' or answer[0].upper() != 'N': assignment_group: CanvasAssignmentGroup = \ - select_from_list(canvas_course.get_assignment_groups(), 'assignment group') + select_from_list(course.get_assignment_groups(), 'assignment group') oat_assignment: CanvasAssignment = select_from_list(assignment_group.get_assignments(), 'assignment') - print_statistics(oat_assignment, major_partition) + print_statistics(oat_assignment, major_partitions) + print() + answer = input('Assess additional assignments? [Y/n] ') + + +def assess_course(course: CanvasCourse) -> None: + print(f'Assessing {course}.') + students: List[CanvasUser] = course.get_students() + print(f'Retrieved {len(students)} students.') + major_partitions: Dict[Major, Set[CanvasUser]] = {} + print('Retrieving majors ', end='') + sys.stdout.flush() + # Force all computing majors to be in the dictionary + for major in filter(lambda m: m.is_computing_major, Major.majors): + major_partitions[major] = set() + # Add students to the dictionary, as well as the non-computing majors represented in the course + for student in students: + print('.', end='') + sys.stdout.flush() + student_majors: Set[Major] = Major.get_student_majors(student.get_username()) + for major in student_majors: + if major not in major_partitions.keys(): + major_partitions[major] = set() + major_partitions[major].add(student) + print() + print(f'There are {len(major_partitions)} majors among the students ' + f'(some students may have more than one major).') + assess_assignments(course, major_partitions) + print() + + +if __name__ == '__main__': + semester_filter = create_semester_filter() + courses: List[CanvasCourse] = CanvasCourse.get_all_courses(semester_filter) + course_selection: Optional[CanvasCourse] = select_from_list(courses, 'course', none_is_option=True) + while course_selection is not None: + assess_course(course_selection) print() - course: Optional[Course] = select_from_list(list(semester.courses), 'course', none_is_option=True) + course_selection = select_from_list(courses, 'course', none_is_option=True) diff --git a/api/canvas_classes.py b/api/canvas_classes.py index f34747e160e760839ffe4489f1f0ad7dd7426557..5582bd049b9cab21ddfa0809f7e0cf99d253623c 100644 --- a/api/canvas_classes.py +++ b/api/canvas_classes.py @@ -937,6 +937,17 @@ class CanvasCourseSection: class CanvasCourse: canvas_course: Course + @staticmethod + def get_all_courses(semester_filter: str = '') -> List["CanvasCourse"]: + canvas_courses: Iterable[Course] = CanvasSession.get_session().get_courses() + courses: List[CanvasCourse] + if semester_filter == '': + courses = [CanvasCourse(course.id) for course in canvas_courses] + else: + courses = [CanvasCourse(course.id) for course in canvas_courses if semester_filter in course.course_code] + return courses + + def __init__(self, course_id: int): self.canvas_course = CanvasSession.get_session().get_course(course_id) diff --git a/common_functions.py b/common_functions.py index dc4cbac0fa2959b2608a6f5e30416f8f832543d6..f244516917ac9349feaec9ce586f3e54797e68db 100644 --- a/common_functions.py +++ b/common_functions.py @@ -2,6 +2,7 @@ import datetime import json import re import ssl +import sys from typing import Dict, List, Any, Optional, TypeVar from urllib import request from urllib.error import HTTPError @@ -20,6 +21,7 @@ def select_from_list(choices: List[ChoiceType], choice_name: str, none_is_option if none_is_option: print('0)\tNone of the above'.expandtabs(4)) selection = input('Enter selection: ') + sys.stdout.flush() try: selection_value = int(selection) if none_is_option and selection_value == 0: diff --git a/majors.py b/majors.py index f00754c2d8688f9b8dd8d3161d8ac425bccae7e1..61ff534dfc10d75ef3c2a04d517202fd3fac7d3f 100644 --- a/majors.py +++ b/majors.py @@ -7,11 +7,11 @@ class Major: majors: Set["Major"] = set() def __init__(self, name: str, alternate_names: Set[str] = None, - abbreviations: Optional[Set[str]] = None, is_cse_major: bool = True): - self.name: str = name + abbreviations: Optional[Set[str]] = None, is_computing_major: bool = True): + self._name: str = name self.alternate_names: Set[str] = alternate_names if alternate_names is not None else set() self.abbreviations: Set[str] = abbreviations if abbreviations is not None else set() - self.is_cse_major: bool = is_cse_major + self._is_computing_major: bool = is_computing_major Major.majors.add(self) @classmethod @@ -20,7 +20,7 @@ class Major: if candidate: return candidate[0] else: - return Major(name, is_cse_major=False) + return Major(name, is_computing_major=False) @staticmethod def get_student_majors(login: str) -> Set["Major"]: @@ -31,14 +31,33 @@ class Major: majors: Optional[Set[str]] = student_data['unlSISMajor'] return {Major.get_major(major) for major in majors} if majors is not None else Major.get_major('None') + @property + def name(self) -> str: + return self._name + + @property + def is_computing_major(self) -> bool: + return self._is_computing_major + def __str__(self) -> str: return self.name def __repr__(self) -> str: return f'Name: "{self.name}";\tAlternate Names: {self.alternate_names if self.alternate_names else "{}"};\t' \ - f'CSE Major: {self.is_cse_major};\tAbbreviations: {self.abbreviations if self.abbreviations else "{}"}' + f'CSE Major: {self._is_computing_major};\t' \ + f'Abbreviations: {self.abbreviations if self.abbreviations else "{}"}' + + def __eq__(self, other: "Major") -> bool: + return self.name == other.name + + def __ne__(self, other: "Major") -> bool: + return not self.__eq__(other) + def __hash__(self) -> int: + return hash(self.name) + +# Computing Majors Major('Computer Science', alternate_names={'Computer Science (Raikes)'}, abbreviations={'COMP-BS', 'COMP-BA', 'COMP-MAJ', 'CMPS-BSCS', 'JECS-BS', 'JECS-BA', 'JECS-MAJ', 'JECS-BSCS'}) # double-check Raikes BSCS @@ -46,3 +65,35 @@ Major('Computer Engineering', alternate_names={'Computer Engineering (Raikes)'}, abbreviations={'CENG-BSCP', 'JECE-BSCP'}) Major('Software Engineering', alternate_names={'Software Engineering (Raikes)'}, abbreviations={'SOFT-BSSE', 'JESE-BSSE'}) + +# Raikes Non-Computing Majors (to capture their alternate names) +Major('Accounting', alternate_names={'Accounting (Raikes)'}, + is_computing_major=False, + abbreviations={'ACCG-BSBA', 'ACCG-MAJ', 'JEAC-BSBA', 'JEAC-MAJ'}) +Major('Actuarial Science', alternate_names={'Actuarial Science (Raikes)'}, is_computing_major=False, + abbreviations={'AACTS-BA', 'AACTS-BS', 'AACTS-MAJ', 'ACTS-BSBA', 'ACTS-MAJ', 'JEAS-BSBA', 'JEAS-MAJ'}) +Major('Business Administration', is_computing_major=False, + alternate_names={'Business Administration with Accounting Emphasis', 'Business Administration (Raikes)'}, + abbreviations={'BAAC-BSBA', 'BAAC-MAJ', 'BLNK-BSBA', 'BSAD-BSBA', 'BSAD-MAJ', 'JEBA-BSBA', 'JEBA-MAJ'}) +Major('Economics', alternate_names={'Economics (Raikes)'}, is_computing_major=False, + abbreviations={'ECON-BSBA', 'ECON-MAJ', 'JEEC-BSBA', 'JEEC-MAJ'}) +Major('Finance', is_computing_major=False, + alternate_names={'Finance (Raikes)', + 'Finance (Banking & Financial Institutions)', + 'Finance (Banking & Financial Institutions) (Raikes)', + 'Finance (CFA-Investments)', 'Finance (CFA-Investments) (Raikes)', + 'Finance (Risk Management & Insurance)', 'Finance (Risk Management & Insurance) (Raikes)'}, + abbreviations={'FINA-BSBA', 'FINA-MAJ', 'JEFN-BSBA', 'JEFN-MAJ', + 'FINB-BSBA', 'FINB-MAJ', 'FINI-BSBA', 'FINI-MAJ', 'FINR-BSBA', 'FINR-MAJ', + 'JEFB-BSBA', 'JEFB-MAJ', 'JEFI-BSBA', 'JEFI-MAJ', 'JEFR-BSBA', 'JEFR-MAJ'}) +Major('International Business', alternate_names={'International Business (Raikes)'}, is_computing_major=False, + abbreviations={'IBUS-BSBA', 'IBUS-MAJ', 'JEIB-BSBA', 'JEIB-MAJ'}) +Major('Marketing', alternate_names={'Marketing (Raikes)'}, is_computing_major=False, + abbreviations={'MRKT-BSBA', 'MRKT-MAJ', 'JEMK-BSBA', 'JEMK-MAJ'}) +Major('Management', alternate_names={'Management (Raikes)'}, is_computing_major=False, + abbreviations={'MNGT-BSBA', 'MNGT-MAJ', 'JEMN-BSBA', 'JEMN-MAJ'}) + +# Thanks to being able to access directory.unl.edu, I think the abbreviations are now unnecessary +# But if that ever changes, see these "Data Dictionary" pages for the (almost) comprehensive list of abbreviations: +# https://registrar.unl.edu/epm/dd/ep_wf_cpp_v1.shtml/ +# https://registrar.unl.edu/data-dictionary-academic-plan/ diff --git a/rosters/README.md b/rosters/README.md deleted file mode 100644 index 82e72c44c0690db6731e05889f5806f4548c152a..0000000000000000000000000000000000000000 --- a/rosters/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Rosters directory - -* Edit `rosters.csv` so that each row corresponds to a semester. In the first - column, give a plaintext name for the semester, and in the second column - specify the subdirectory that will contain the MyRed rosters for that - semester. Every three columns thereafter correspond to a specific course: a - plaintext name for the course, the filename of the csv file with the - course's MyRed roster, and the course's Canvas ID. -  -  - If you use Excel to edit `rosters.csv`, run `dos2unix rosters.csv` when - you're finished (Excel sometimes adds an unprintable Unicode character at - the start of csv files that can confuse my code.) - -* [Download course rosters from MyRed](https://myred.nebraska.edu/psc/myred/NBL/HRMS/s/WEBLIB_DSHBOARD.ISCRIPT1.FieldFormula.IScript_GETPAGE?faculty1&path=faculty1.main.cr) - and place them into the appropriate subdirectory. If you combined multiple - sections into a single Canvas course, combine the rosters into a single csv - file. For good measure, pass the csv files through `dos2unix`. MyRed might - not add that Unicode character, but better safe than sorry. \ No newline at end of file diff --git a/rosters/fall2020/.gitignore b/rosters/fall2020/.gitignore deleted file mode 100644 index 16f2dc5fa95ef368c3f09eaca24f75cb45102d3e..0000000000000000000000000000000000000000 --- a/rosters/fall2020/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.csv \ No newline at end of file diff --git a/rosters/fall2020/.gitkeep b/rosters/fall2020/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/rosters/rosters.csv b/rosters/rosters.csv deleted file mode 100644 index 96aac7f232c816c9b987112a607ac744bad650a6..0000000000000000000000000000000000000000 --- a/rosters/rosters.csv +++ /dev/null @@ -1,3 +0,0 @@ -SEMESTER,SUBDIRECTORY,COURSE 1,FILENAME 1,CANVAS ID 1,COURSE 2,FILENAME 2,CANVAS ID 2,COURSE 3,FILENAME 3,CANVAS ID 3,COURSE 4,FILENAME 4,CANVAS ID 4,COURSE 5,FILENAME 5,CANVAS ID 5,COURSE 6,FILENAME 6,CANVAS ID 6,COURSE 7,FILENAME 7,CANVAS ID 7,COURSE 8,FILENAME 8,CANVAS ID 8,COURSE 9,FILENAME 9,CANVAS ID 9 -Spring 2021,spring2021,SOFT 161,soft161.csv,105546,CSCE 231,csce231.csv,100808,CSCE 487,csce487.csv,100787,CSCE 489,csce489.csv,100787,CSCE 493,csce493.csv,100787,CSCE 493A,csce493a.csv,100787,SOFT 404,soft 404.csv,100787,,,,,, -Fall 2020,fall2020,SOFT 160,soft160.csv,94598,CSCE 231,csce231.csv,89472,SOFT 360,soft360.csv,94606,CSCE 486,csce486.csv,89523,CSCE 488,csce488.csv,89523,SOFT 403,soft403.csv,89523,CSCE 493,csce493.csv,89523,CSCE 493A,csce493a.csv,89523,,, \ No newline at end of file diff --git a/rosters/spring2021/.gitignore b/rosters/spring2021/.gitignore deleted file mode 100644 index 16f2dc5fa95ef368c3f09eaca24f75cb45102d3e..0000000000000000000000000000000000000000 --- a/rosters/spring2021/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.csv \ No newline at end of file diff --git a/rosters/spring2021/.gitkeep b/rosters/spring2021/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/semester.py b/semester.py deleted file mode 100644 index a6dc75114d1f4638b71e5eeef6bd39fb23b24e9e..0000000000000000000000000000000000000000 --- a/semester.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Set, Iterator, FrozenSet - - -class Semester: - def __init__(self, name: str, subdirectory: str): - self._name: str = name - self._subdirectory: str = subdirectory - self._courses: Set["Course"] = set() - - def add_course(self, course: "Course") -> None: - self._courses.add(course) - - @property - def subdirectory(self) -> str: - return self._subdirectory - - @property - def courses(self) -> FrozenSet["Course"]: - return frozenset(self._courses) - - def __iter__(self) -> Iterator["Course"]: - return iter(self._courses) - - def __str__(self) -> str: - return self._name - - -class Course: - def __init__(self, name: str, canvas_course_id: str, roster_file: str): - self._name: str = name - self._canvas_course_id = canvas_course_id - self._roster_file: str = f'{roster_file}.csv' if roster_file[-4:] != '.csv' else roster_file - - @property - def canvas_course_id(self) -> str: - return self._canvas_course_id - - @property - def roster_file(self) -> str: - return self._roster_file - - def __str__(self) -> str: - return self._name