diff --git a/prep_assignment.py b/prep_assignment.py index bb359ca942cecdef31d6efa8d9058c5d47b578f7..dbf0fa3e1e0208adbcc5b9432d7a0fa271e296fb 100644 --- a/prep_assignment.py +++ b/prep_assignment.py @@ -1,6 +1,6 @@ import random import subprocess -# from math import ceil, log10 +from math import ceil, log10 from typing import Tuple from api.canvas_classes import * @@ -24,6 +24,7 @@ def get_students() -> Set[CompositeUser]: # TODO: assign_partners for arbitrarily-sized teams +# TODO: break this up into bite-sized chunks def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Assignment') -> \ List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]]: students_with_blacklist: Set[CompositeUser] = sorted(list(filter(lambda s: s.has_blacklist(), students)), @@ -35,12 +36,31 @@ def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Ass if groupset_name in fields: preassigned_students = set(filter(lambda s: len(s.graylist[groupset_name]) > 0, students)) unassigned_students: Set[CompositeUser] = set(students) + # handle student excusals + excused_students = set(filter(lambda s: s.graylist[groupset_name] == set('X'), preassigned_students)) + # noinspection PyUnusedLocal + confirmation: str + # noinspection PyUnusedLocal + student: CompositeUser + for student in excused_students: + confirmation = input(f'\tExclude {student.readable_name} from student pairing ([Yes]/No/Abort)? ') + if confirmation == "" or confirmation.upper()[0] == 'Y': + print(f'Removing {student.readable_name} from pre-assigned students and from unassigned students.') + preassigned_students.remove(student) + unassigned_students.remove(student) + student.graylist[groupset_name] = set() + elif confirmation.upper()[0] == 'N': + print(f'Removing {student.readable_name} from pre-assigned students but leaving in unassigned students.') + preassigned_students.remove(student) + student.graylist[groupset_name] = set() + else: + print('Aborting.') + exit(0) + # handle truly-preassigned students pair_number: int = 0 student_pairs: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]] = [] # noinspection PyUnusedLocal potential_pair: List[CompositeUser] - # noinspection PyUnusedLocal - confirmation: str if preassigned_students: print('First we shall confirm pre-assigned partners.') else: @@ -52,7 +72,7 @@ def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Ass potential_pair = [] pair_number += 1 print(f'Preparing pair {pair_number}...') - student: CompositeUser = preassigned_students.pop() + student = preassigned_students.pop() potential_pair.append(pair_number) potential_pair.append(student) potential_partners: Set[str] = student.graylist[groupset_name] @@ -120,9 +140,10 @@ def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Ass print('Very finally, we shall now assign the odd student to an existing partnership.') match_found = False while not match_found: + pair_number -= 1 potential_partners: Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]] = \ - student_pairs[--pair_number] - if potential_partners[2] is None and \ + student_pairs[pair_number] + if potential_partners[3] is None and \ odd_student.is_graylist_compatible(potential_partners[1]) and \ odd_student.is_graylist_compatible(potential_partners[2]) and \ odd_student.is_blacklist_compatible(potential_partners[1]) and \ @@ -134,6 +155,7 @@ def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Ass f'{potential_partners[1].readable_name} and {potential_partners[2].readable_name}') else: print('There is no odd student to add to an existing partnership.') + # update graylists for pair in student_pairs: usernames = set(map(lambda s: s.canvas_username if isinstance(s, CompositeUser) else None, pair)) - {None} for student in pair[1:]: @@ -142,83 +164,81 @@ def create_pairs(students: Set[CompositeUser], groupset_name: str = 'Unknown Ass return student_pairs -def save_student_roster(students: Set[CompositeUser]): +def save_student_roster(students: Set[CompositeUser]) -> None: filename = input('Please provide the name of the new student roster file: ') + print(f'Writing {filename}.') CompositeUser.write_student_csv(students, filename) -def create_contact_list(assignment_number, student_pairs): - filename = f'{assignment_number}-pairs.md' +def create_contact_list(groupset_name: str, + student_pairs: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]]) -> None: + filename = f'{groupset_name}-partners.md' + print(f'Writing {filename}.') + zero_padding: int = ceil(log10(len(partners))) with open(filename, mode='w') as pair_file: - pair_file.write(f'# PARTNERS FOR ASSIGNMENT {assignment_number}\n\n') + pair_file.write(f'# PARTNERS FOR ASSIGNMENT {groupset_name}\n\n') for pair in student_pairs: - pair_file.write(f'- {assignment_number}pair {pair[0]}\n') + pair_file.write(f'- {groupset_name} {str(pair[0]).zfill(zero_padding)}\n') pair_file.write(f' - {pair[1]}\n') pair_file.write(f' - {pair[2]}\n') if pair[3] is not None: pair_file.write(f' - {pair[3]}\n') -def create_repositories(assignment_number, student_pairs, verbose): - if verbose: - print('Creating file for clone script.') - filename = f'{assignment_number}-clone.sh' +def create_repositories(groupset_name: str, + student_pairs: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]]) -> None: + filename = f'{groupset_name}-clone.sh' + print(f'Creating file for clone script: {filename}. Creating repositories on Gitlab.') + zero_padding: int = ceil(log10(len(partners))) with open(filename, mode='w') as clone_file: clone_file.write('#!/bin/bash\n\n') clone_file.write('# Auto-generated clone script.\n') for pair in student_pairs: - if verbose: - print(f'Creating repo for pair number {pair[0]}') + print(f'\tCreating repo for pair number {str(pair[0]).zfill(zero_padding)}') project = GitlabProject.create_project_in_group(Course.gitlab_namespace, - f'{assignment_number}pair{pair[0]}') - if verbose: - print(f'\tAdding {pair[1]}') + f'{groupset_name}{str(pair[0]).zfill(zero_padding)}') + print(f'\t\tAdding {pair[1]}') project.add_user_as_maintainer(pair[1].get_gitlab_user()) - if verbose: - print(f'\tAdding {pair[2]}') + print(f'\t\tAdding {pair[2]}') project.add_user_as_maintainer(pair[2].get_gitlab_user()) if pair[3] is not None: - if verbose: - print(f'\tAdding {pair[3]}') + print(f'\t\tAdding {pair[3]}') project.add_user_as_maintainer(pair[3].get_gitlab_user()) repo_url = project.get_cloning_url() clone_file.write(f'git clone {repo_url}\n') subprocess.call(['chmod', '+x', filename]) + print('Repositories created') -def create_groups(assignment_number, student_pairs): +def create_groups(groupset_name: str, + student_pairs: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]]) -> None: + print(f'Creating groupset {groupset_name} and student groups on Canvas.') course = CanvasCourse(Course.canvas_course_id) - group_set = course.create_user_groupset(f'{assignment_number}pairs') + group_set = course.create_user_groupset(groupset_name) + zero_padding: int = ceil(log10(len(partners))) for pair in student_pairs: - group = group_set.create_group(f'{assignment_number}pair {pair[0]}') + group = group_set.create_group(f'\t{groupset_name} {str(pair[0]).zfill(zero_padding)}') group.add_student(pair[1].get_canvas_user()) group.add_student(pair[2].get_canvas_user()) if pair[3] is not None: group.add_student(pair[3].get_canvas_user()) + print('Canvas groups created') if __name__ == '__main__': groupset: str = input('Please provide the name of the student groupset: ') student_set: Set[CompositeUser] = get_students() + # TODO: check for changes to course roster + # TODO: check for changes to gitlab usernames partners: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]] = create_pairs(student_set, groupset) print() save_student_roster(student_set) - # zero_padding = ceil(log10(len(partners))) - # for partner in partners: - # print(f'{groupset} {str(partner[0]).zfill(zero_padding)}: {partner[1]}\t{partner[2]}\t{partner[3]}') - """ - assignment = '28' - pairs = create_pairs('2019-08.csv') - save_pairs(assignment, pairs) - print('Pairs created') - - create_repositories(assignment, pairs, True) - print('Repositories created') - create_groups(assignment, pairs) - print('Canvas groups created') - + create_contact_list(groupset, partners) + print() + create_repositories(groupset, partners) + print() + create_groups(groupset, partners) + print() print('TODO:\tAdd issues') print('\tCommit starter code') - print('\tUpdate graylists (also, please update the code to update the graylists)') - """