diff --git a/api/canvas_experiments.py b/api/canvas_experiments.py index 4719dbc23434ececfc49d8b44b0b154106b9c355..d4c0fa51f3b5aed635b167656b7e76c8732e8c6d 100644 --- a/api/canvas_experiments.py +++ b/api/canvas_experiments.py @@ -42,6 +42,7 @@ if __name__ == '__main__': recipients = canvas.search_recipients() print(recipients) """ + """ course = canvas.get_course(Course.canvas_course_id) users = course.get_users() for user in users: @@ -63,3 +64,12 @@ if __name__ == '__main__': users = foo2.get_users() for user in users: print(user.name) + """ + user = canvas.get_user(30266045, 'sis_user_id') # can retrieve my own user + print(user) + # user = canvas.get_user(76390201, 'sis_user_id') # cannot retrieve arbitrary user + # print(user) + users = canvas.get_course(Course.canvas_course_id).get_users() + # user = list(filter(lambda s: s.sis_user_id == 76390201, users))[0] + user = list(filter(lambda s: s.login_id == 'mkluck2', users))[0] + print(user) \ No newline at end of file diff --git a/api/course.py b/api/course.py index 5a907ff767a779cd184bb06c2c411b0b2ffc9460..b2ae0d9239150bbed495600aa82a32767e891e15 100644 --- a/api/course.py +++ b/api/course.py @@ -1,6 +1,7 @@ class Course: # GitLab course information - gitlab_namespace = 'csce_361/sandbox' + # gitlab_namespace = 'csce_361/sandbox' + gitlab_namespace = 'csce_361/fall2019' # Canvas course information # canvas_course_id = '73696' # Software Engineering Sandbox diff --git a/canvas_classes.py b/canvas_classes.py index 03f52a937fd52e325a11a6fec0c545de83ffdd8c..3b639de1162297e1c8635309b20c821339b0f395 100644 --- a/canvas_classes.py +++ b/canvas_classes.py @@ -40,7 +40,7 @@ class CanvasUser: def __repr__(self): username = self.get_username() - return f'@{username}' + return f'{username}' def __eq__(self, other): if isinstance(other, CanvasUser): @@ -102,7 +102,7 @@ class CanvasUser: """ -class GroupSet: # aka, group_category +class CanvasGroupSet: # aka, group_category def __init__(self, group_category): super().__init__() self.canvas_group_category = group_category @@ -114,19 +114,19 @@ class GroupSet: # aka, group_category canvas_groups = self.canvas_group_category.get_groups() groups = [] for group in canvas_groups: - groups.append(Group(group)) + groups.append(CanvasGroup(group)) return groups def create_group(self, group_name): canvas_group = self.canvas_group_category.create_group(name=group_name) - return Group(canvas_group) + return CanvasGroup(canvas_group) def create_groups(self, number_of_groups): base_name = self.get_name() groups = [] for group_number in range(1, number_of_groups+1): canvas_group = self.create_group(f'{base_name} {group_number}') - groups.append(Group(canvas_group)) + groups.append(CanvasGroup(canvas_group)) return groups def __repr__(self): @@ -178,7 +178,7 @@ class GroupSet: # aka, group_category """ -class Group: +class CanvasGroup: def __init__(self, group): super().__init__() self.canvas_group = group @@ -261,7 +261,7 @@ class Group: """ -class Course: +class CanvasCourse: def __init__(self, course_id): self.canvas_course = CanvasSession.get_session().get_course(course_id) @@ -290,19 +290,19 @@ class Course: canvas_groups = self.canvas_course.get_groups() groups = [] for group in canvas_groups: - groups.append(Group(group)) + groups.append(CanvasGroup(group)) return groups def get_group_sets(self): canvas_group_categories = self.canvas_course.get_group_categories() group_sets = [] for group_category in canvas_group_categories: - group_sets.append(GroupSet(group_category)) + group_sets.append(CanvasGroupSet(group_category)) return group_sets def create_groupset(self, groupset_name): group_category = self.canvas_course.create_group_category(groupset_name) - return GroupSet(group_category) + return CanvasGroupSet(group_category) def __repr__(self): return f'{self.canvas_course.course_code}: {self.canvas_course.name}' diff --git a/composite_user.py b/composite_user.py index bf2d485f10f9ce5b185006dfefc78a47b5d057e1..802127e7519bc5bacf57e85444c4b18bd4e20119 100644 --- a/composite_user.py +++ b/composite_user.py @@ -1,5 +1,7 @@ from canvas_classes import CanvasUser +from canvas_classes import CanvasCourse from gitlab_classes import GitlabUser +from course import Course import csv NO_PARTNERING_LIST_MAXIMUM = 10 @@ -13,7 +15,7 @@ class CompositeUser: self.gitlab_user = None self.sortable_name = student_dictionary['SortableName'] self.readable_name = student_dictionary['ReadableName'] - self.NUID = student_dictionary['NUID'] + self.NUID = int(student_dictionary['NUID']) self.canvas_username = student_dictionary['CanvasUsername'] self.gitlab_username = student_dictionary['GitlabUsername'] self.canvas_email = student_dictionary['CanvasEmail'] @@ -28,12 +30,12 @@ class CompositeUser: def get_canvas_user(self): if self.canvas_user is None: - self.canvas_user = CanvasUser(self.NUID) + self.canvas_user = CanvasUser(self.NUID) # n.b., can retrieve own user but not arbitrary user return self.canvas_user def get_gitlab_user(self): if self.gitlab_user is None: - self.gitlab_user = GitlabUser(self.NUID) + self.gitlab_user = GitlabUser(self.gitlab_username) return self.gitlab_user def tentatively_pair_with(self, username): @@ -52,10 +54,10 @@ class CompositeUser: return len(self.blacklist) > 0 def is_blacklist_compatible(self, other): - return other not in self.blacklist + return other not in self.blacklist and self not in other.blacklist def is_graylist_compatible(self, other): - return other not in self.graylist + return other not in self.graylist and self not in other.graylist def __repr__(self): if self.canvas_email == self.gitlab_email: @@ -72,6 +74,9 @@ class CompositeUser: def __ne__(self, other): return not self.__eq__(other) + def __hash__(self) -> int: + return hash(self.canvas_username) + @staticmethod def get_user(username_or_email): return CompositeUser.instances[username_or_email] @@ -81,18 +86,22 @@ class CompositeUser: students = set() with open(filename, mode='r') as csv_file: csv_reader = csv.DictReader(csv_file) - for student in csv_reader: + for csv_student in csv_reader: graylist = set() blacklist = set() for count in range(NO_PARTNERING_LIST_MAXIMUM): - former_partner = student[f'Graylist{count}'] - undesired_partner = student[f'Blacklist{count}'] + former_partner = csv_student[f'Graylist{count}'] + undesired_partner = csv_student[f'Blacklist{count}'] if former_partner != "": graylist.add(former_partner) if undesired_partner != "": blacklist.add(undesired_partner) - student = CompositeUser(student, graylist, blacklist) + student = CompositeUser(csv_student, graylist, blacklist) students.add(student) + canvas_students = CanvasCourse(Course.canvas_course_id).get_students() + for canvas_student in canvas_students: + composite_student = list(filter(lambda s: s.canvas_username == canvas_student.get_username(), students))[0] + composite_student.canvas_user = canvas_student return students @staticmethod diff --git a/gitlab_classes.py b/gitlab_classes.py index cc3d2cf965dee3e032febadd731ece09aad78cda..18ee72cace506ba336bb7a41e8cd14aca8dba11f 100644 --- a/gitlab_classes.py +++ b/gitlab_classes.py @@ -55,7 +55,7 @@ class GitlabUser: return not self.__eq__(other) -class Issue: +class GitlabIssue: def __init__(self, issue): """ Creates an Issue object, populating the backing git_issue instance with the appropriate gitlab.Issue object @@ -168,7 +168,7 @@ class Issue: # subscribed -class Project: +class GitlabProject: def __init__(self, project): """ Creates a Project object, populating the backing git_project instance with the appropriate gitlab.Project object @@ -196,7 +196,7 @@ class Project: gitlab_projects = GitlabSession.get_session().groups.get(group).projects.list(all=True) projects = [] for project in gitlab_projects: - projects.append(Project(project)) + projects.append(GitlabProject(project)) return projects @staticmethod @@ -204,17 +204,17 @@ class Project: gitlab_projects = GitlabSession.get_session().projects.list(search=search_term, all=True) projects = [] for project in gitlab_projects: - projects.append(Project(project)) + projects.append(GitlabProject(project)) return projects @staticmethod def create_project(project_name): - return GitlabSession.get_session().projects.create({'name': project_name}) + return GitlabProject(GitlabSession.get_session().projects.create({'name': project_name})) @staticmethod def create_project_in_group(group_name, project_name): group_id = GitlabSession.get_session().groups.get(group_name).id - return GitlabSession.get_session().projects.create({'name': project_name, 'namespace_id': group_id}) + return GitlabProject(GitlabSession.get_session().projects.create({'name': project_name, 'namespace_id': group_id})) def get_project_id(self): return self.git_project.id @@ -312,12 +312,12 @@ class Project: gitlab_issues = self.git_project.issues.list(order_by='created_at', sort='asc', all=True) issues = [] for issue in gitlab_issues: - issues.append(Issue(issue)) + issues.append(GitlabIssue(issue)) return issues def create_issue(self, title, description): gitlab_issue = self.git_project.issues.create({'title': title, 'description': description}) - return Issue(gitlab_issue) + return GitlabIssue(gitlab_issue) def __repr__(self): return self.get_name_with_namespace() @@ -373,7 +373,7 @@ class Project: if __name__ == '__main__': namespace = 'csce_361/sandbox' - test_projects = Project.get_projects_by_group(namespace) + test_projects = GitlabProject.get_projects_by_group(namespace) print('All projects in sandbox:') for test_project in test_projects: print(test_project) @@ -391,7 +391,7 @@ if __name__ == '__main__': for test_issue in test_issues: creation = test_issue.get_created_at() print(f'{test_issue}\tcreated at {creation}.') - test_projects = Project.get_projects_by_keyword('csce361-homework') + test_projects = GitlabProject.get_projects_by_keyword('csce361-homework') number_of_projects = len(test_projects) print(f'retrieved {number_of_projects} projects matching \'csce361-homework\'') start_date = datetime(2019, 8, 1, tzinfo=timezone.utc) diff --git a/prep_assignment.py b/prep_assignment.py new file mode 100644 index 0000000000000000000000000000000000000000..edf24f62d37ab905f8a63e66f12a2bae77b4ce69 --- /dev/null +++ b/prep_assignment.py @@ -0,0 +1,97 @@ +import random +import subprocess +from composite_user import CompositeUser +from canvas_classes import * +from gitlab_classes import * +from course import Course + + +def create_pairs(filename): + # only works when there are an even number of students + students = CompositeUser.read_student_csv(filename) + students_with_blacklist = sorted(list(filter(lambda s: s.has_blacklist(), students)), + key=lambda t: len(t.blacklist), reverse=True) + unassigned_students = set(students) + pair_number = 0 + pairs = [] + for student in students_with_blacklist: + pair_number += 1 + unassigned_students.remove(student) + potential_partner = random.choice(tuple(unassigned_students)) + while not (student.is_blacklist_compatible(potential_partner) and + student.is_graylist_compatible(potential_partner)): + # has the potential to run infinitely + potential_partner = random.choice(tuple(unassigned_students)) + unassigned_students.remove(potential_partner) + pairs.append((pair_number, student, potential_partner)) + while unassigned_students: + pair_number += 1 + student = random.choice(tuple(unassigned_students)) + unassigned_students.remove(student) + attempts = 1 + potential_partner = random.choice(tuple(unassigned_students)) + while not (student.is_graylist_compatible(potential_partner) and attempts <= len(unassigned_students)): + attempts += 1 + potential_partner = random.choice(tuple(unassigned_students)) + unassigned_students.remove(potential_partner) + pairs.append((pair_number, student, potential_partner)) + return pairs + + +def save_pairs(assignment_number, student_pairs): + filename = f'{assignment_number}-pairs.md' + with open(filename, mode='w') as pair_file: + pair_file.write(f'# PARTNERS FOR ASSIGNMENT {assignment_number}\n\n') + for pair in student_pairs: + pair_file.write(f'- {assignment_number}pair {pair[0]}\n') + pair_file.write(f' - {pair[1]}\n') + pair_file.write(f' - {pair[2]}\n') + + +def create_repositories(assignment_number, student_pairs): + filename = f'{assignment_number}-clone.sh' + 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: + project = GitlabProject.create_project_in_group(Course.gitlab_namespace, f'{assignment_number}pair{pair[0]}') + project.add_user_as_maintainer(pair[1].get_gitlab_user()) + project.add_user_as_maintainer(pair[2].get_gitlab_user()) + repo_url = project.get_cloning_url() + clone_file.write(f'git clone {repo_url}\n') + subprocess.call(['chmod', '+x', filename]) + + +def create_groups(assignment_number, student_pairs): + course = CanvasCourse(Course.canvas_course_id) + group_set = course.create_groupset(f'{assignment_number}pairs') + for pair in student_pairs: + group = group_set.create_group(f'{assignment_number}pair {pair[0]}') + group.add_student(pair[1].get_canvas_user()) + group.add_student(pair[2].get_canvas_user()) + + +if __name__ == '__main__': + assignment = 10 + pairs = create_pairs('2019-08.csv') + save_pairs(assignment, pairs) + print('Pairs created') + + # pair = list(pairs)[0] + # print(f'{pair[1].get_gitlab_user()}, {pair[2].get_gitlab_user()}') + # project = GitlabProject.create_project_in_group(Course.gitlab_namespace, f'{assignment_number}pairx{pair[0]}') + # project.add_user_as_maintainer(pair[1].get_gitlab_user()) + # project.add_user_as_maintainer(pair[2].get_gitlab_user()) + + create_repositories(assignment, pairs) + print('Repositories created') + create_groups(assignment, pairs) + print('Canvas groups created') + + print('TODO:\tAdd issues') + print('\tCommit starter code') + + """ + BALLS! I forgot to update the graylists + I also forgot newlines in the cloning script - fixed code, need to fix script + """