Skip to content
Snippets Groups Projects
Commit b7b53520 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Updated partnering code to allow triplets and pre-assigned partners

parent dc3b7411
No related branches found
No related tags found
No related merge requests found
......@@ -19,24 +19,24 @@ class CompositeUser:
gitlab_username: str
canvas_email: str
gitlab_email: str
graylist: Set[str]
graylist: Dict[str, Set[str]]
blacklist: Set[str]
candidate_teammates: Set[str]
assignments: ClassVar[List[str]] = []
assignments: ClassVar[List[str]] = [] # TODO: make use of this!!
instances: ClassVar[Dict[str, "CompositeUser"]] = {}
def __init__(self, student_dictionary: Dict[str, str], graylist: Dict[str, Set[str]], blacklist: Collection[str]):
self.canvas_user: CanvasUser = None
self.gitlab_user: GitlabUser = None
self.sortable_name: str = student_dictionary['SortableName']
self.readable_name: str = student_dictionary['ReadableName']
self.NUID: int = int(student_dictionary['NUID'])
self.canvas_username: str = student_dictionary['CanvasUsername']
self.gitlab_username: str = student_dictionary['GitlabUsername']
self.canvas_email: str = student_dictionary['CanvasEmail']
self.gitlab_email: str = student_dictionary['GitlabEmail']
self.graylist: Dict[str, Set[str]] = graylist
self.blacklist: Set[str] = set(blacklist)
self.sortable_name = student_dictionary['SortableName']
self.readable_name = student_dictionary['ReadableName']
self.NUID = int(student_dictionary['NUID'])
self.canvas_username = student_dictionary['CanvasUsername']
self.gitlab_username = student_dictionary['GitlabUsername']
self.canvas_email = student_dictionary['CanvasEmail']
self.gitlab_email = student_dictionary['GitlabEmail']
self.graylist = graylist
self.blacklist = set(blacklist)
self.candidate_teammates: Set[str] = None
CompositeUser.instances[self.canvas_email] = self
CompositeUser.instances[self.gitlab_email] = self
......@@ -76,7 +76,7 @@ class CompositeUser:
def tentatively_team_with(self, usernames: Collection[str]) -> None:
self.candidate_teammates = set(usernames)
def commit_team(self) -> None: # TODO: Will want to re-visit this based on tracking by project
def commit_team(self) -> None: # TODO: Will NEED to re-visit this based on tracking by project -- maybe use it, too
self.graylist = self.graylist.union(self.candidate_teammates)
def discard_team(self) -> None:
......@@ -90,8 +90,26 @@ class CompositeUser:
self.get_canvas_user().get_name() not in other.blacklist
def is_graylist_compatible(self, other: "CompositeUser") -> bool:
return other.get_canvas_user().get_name() not in self.graylist and \
self.get_canvas_user().get_name() not in other.graylist
my_past_partners: Set[str] = set()
your_past_partners: Set[str] = set()
for assignment in self.graylist:
if isinstance(self.graylist[assignment], set):
my_past_partners.union(self.graylist[assignment])
elif isinstance(self.graylist[assignment], str):
my_past_partners.add(str(self.graylist[assignment]))
else:
print(f'Weird. {self}\'s partners for {assignment} '
f'are recorded as a {self.graylist[assignment].__class__}')
for assignment in other.graylist:
if isinstance(other.graylist[assignment], set):
your_past_partners.union(other.graylist[assignment])
elif isinstance(other.graylist[assignment], str):
your_past_partners.add(str(other.graylist[assignment]))
else:
print(f'Weird. {other}\'s partners for {assignment} '
f'are recorded as a {other.graylist[assignment].__class__}')
return other.get_canvas_user().get_username() not in my_past_partners and \
self.get_canvas_user().get_username() not in your_past_partners
def __repr__(self) -> str:
if self.canvas_email == self.gitlab_email:
......@@ -148,16 +166,9 @@ class CompositeUser:
csv_reader = csv.DictReader(csv_file)
for csv_student in csv_reader:
blacklist: Set[str] = CompositeUser.string_to_set(csv_student['Blacklist'])
# for count in range(NO_PARTNERING_LIST_MAXIMUM):
# former_partner: str = csv_student[f'Graylist{count}']
# undesired_partner: str = csv_student[f'Blacklist{count}']
# if former_partner != "":
# graylist.add(former_partner)
# if undesired_partner != "":
# blacklist.add(undesired_partner)
graylist: Dict[str, Set[str]] = {}
handled_fields = {'SortableName', 'ReadableName', 'NUID', 'CanvasUsername', 'CanvasEmail',
'GitlabUsername', 'GitlabEmail', 'Blacklist'}
'GitlabUsername', 'GitlabEmail', 'Blacklist'} # TODO: remove duplication
for field in set(csv_student.keys()) - handled_fields:
graylist[field] = CompositeUser.string_to_set(csv_student[field])
student = CompositeUser(csv_student, graylist, blacklist)
......@@ -174,9 +185,9 @@ class CompositeUser:
'CanvasUsername', 'CanvasEmail',
'GitlabUsername', 'GitlabEmail', 'Blacklist']
random_student = students.pop()
assignments = random_student.graylist.keys() - set(fieldnames)
student_assignments = random_student.graylist.keys()
students.add(random_student)
fieldnames.extend(sorted(assignments))
fieldnames.extend(sorted(student_assignments))
with open(filename, mode='w') as csv_file:
fieldnames.extend(CompositeUser.assignments)
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
......
......
import random
import subprocess
from api.composite_user import CompositeUser
from typing import Tuple
from api.canvas_classes import *
from api.composite_user import CompositeUser
from api.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)),
# TODO: assign_partners for arbitrarily-sized teams
def create_pairs(filename: str, assignment_name: str = 'Unknown Assignment') -> \
List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]]: # TODO: look at commit_team
students: Set[CompositeUser] = CompositeUser.read_student_csv(filename)
students_with_blacklist: Set[CompositeUser] = sorted(list(filter(lambda s: s.has_blacklist(), students)),
key=lambda t: len(t.blacklist), reverse=True)
unassigned_students = set(students)
pair_number = 0
student_pairs = []
preassigned_students: Set[CompositeUser] = set()
random_student = students.pop() # TODO: remove duplication
fields = random_student.graylist.keys()
students.add(random_student)
if assignment_name in fields:
preassigned_students = set(filter(lambda s: len(s.graylist[assignment_name]) > 0, students))
unassigned_students: Set[CompositeUser] = set(students)
pair_number: int = 0
student_pairs: List[Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]] = []
# noinspection PyUnusedLocal
potential_pair: List[CompositeUser]
print('First we shall confirm pre-assigned partners.')
while preassigned_students:
potential_pair = []
pair_number += 1
print(f'Preparing pair{pair_number}...')
student: CompositeUser = preassigned_students.pop()
potential_pair.append(pair_number)
potential_pair.append(student)
potential_partners: Set[str] = student.graylist[assignment_name]
print(f'\t{student.readable_name} pre-assigned to {potential_partners}')
while potential_partners:
student = CompositeUser.get_user(potential_partners.pop())
potential_pair.append(student)
partner_potential_partners: Set[str] = student.graylist[assignment_name]
print(f'\t{student.readable_name} pre-assigned to {partner_potential_partners}')
confirmation: str = input('Confirm partners? [Y]')
if confirmation == "" or confirmation.upper()[0] == 'Y':
# noinspection PyUnusedLocal
pair: Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]]
if len(potential_pair) == 2:
pair = (pair_number, potential_pair[0], potential_pair[1], None)
else:
pair = (pair_number, potential_pair[0], potential_pair[1], potential_pair[2])
student_pairs.append(tuple(pair))
for student in potential_pair:
unassigned_students.remove(student)
if student in preassigned_students:
preassigned_students.remove(student)
else:
print('We\'re not accepting NO for an answer yet. Goodbye.')
exit(1)
print('Next we shall assign partners to students with blacklists.')
for student in students_with_blacklist:
pair_number += 1
unassigned_students.remove(student)
potential_partner = random.choice(tuple(unassigned_students))
potential_partner: CompositeUser = 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)
student_pairs.append((pair_number, student, potential_partner))
student_pairs.append((pair_number, student, potential_partner, None))
print(f'\t{student.readable_name} partnered with {potential_partner.readable_name}')
print('Finally we shall assign partners to the remaining students.')
odd_student: Optional[CompositeUser] = \
random.choice(tuple(unassigned_students)) if len(unassigned_students) % 2 == 1 else None
if odd_student is not None:
unassigned_students.remove(odd_student)
while unassigned_students:
pair_number += 1
student = random.choice(tuple(unassigned_students))
......@@ -33,8 +82,28 @@ def create_pairs(filename):
while not (student.is_graylist_compatible(potential_partner) and attempts <= len(unassigned_students)):
attempts += 1
potential_partner = random.choice(tuple(unassigned_students))
if attempts > len(unassigned_students):
print(f'NO MATCH POSSIBLE FOR {student}! YOU REALLY SHOULD WRITE CODE TO SWAP PARTNERS.') # TODO
exit(1)
unassigned_students.remove(potential_partner)
student_pairs.append((pair_number, student, potential_partner))
student_pairs.append((pair_number, student, potential_partner, None))
print(f'\t{student.readable_name} partnered with {potential_partner.readable_name}')
print('Very finally, we shall now assign the odd student to an existing partnership.')
if odd_student is not None:
match_found = False
while not match_found:
potential_partners: Tuple[int, CompositeUser, CompositeUser, Optional[CompositeUser]] = \
student_pairs[--pair_number]
if potential_partners[2] 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 \
odd_student.is_blacklist_compatible(potential_partners[2]):
match_found = True
student_pairs[pair_number] = (
potential_partners[0], potential_partners[1], potential_partners[2], odd_student)
print(f'\t{odd_student.readable_name} partnered with '
f'{potential_partners[1].readable_name} and {potential_partners[2].readable_name}')
return student_pairs
......@@ -46,6 +115,8 @@ def save_pairs(assignment_number, 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')
if pair[3] is not None:
pair_file.write(f' - {pair[3]}\n')
def create_repositories(assignment_number, student_pairs, verbose):
......@@ -66,6 +137,10 @@ def create_repositories(assignment_number, student_pairs, verbose):
if verbose:
print(f'\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]}')
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])
......@@ -78,9 +153,13 @@ def create_groups(assignment_number, 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 pair[3] is not None:
group.add_student(pair[3].get_canvas_user())
if __name__ == '__main__':
create_pairs('2020-01-students.csv', '09pair')
"""
assignment = '28'
pairs = create_pairs('2019-08.csv')
save_pairs(assignment, pairs)
......@@ -94,25 +173,4 @@ if __name__ == '__main__':
print('TODO:\tAdd issues')
print('\tCommit starter code')
print('\tUpdate graylists (also, please update the code to update the graylists)')
"""
had a graylist violation on 28pair 4
encountered this on 28pair 18:
Traceback (most recent call last):
File "/Users/cabohn/courses/csce361/scripts/prep_assignment.py", line 91, in <module>
create_groups(assignment, pairs)
File "/Users/cabohn/courses/csce361/scripts/prep_assignment.py", line 79, in create_groups
group.add_student(pair[1].get_canvas_user())
File "/Users/cabohn/courses/csce361/scripts/api/composite_user.py", line 33, in get_canvas_user
self.canvas_user = CanvasUser(self.NUID) # n.b., can retrieve own user but not arbitrary user
File "/Users/cabohn/courses/csce361/scripts/api/canvas_classes.py", line 22, in __init__
self.canvas_user = CanvasSession.get_session().get_user(user, 'sis_user_id')
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/canvasapi/canvas.py", line 1110, in get_user
response = self.__requester.request("GET", uri)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/canvasapi/requester.py", line 227, in request
raise Unauthorized(response.json())
canvasapi.exceptions.Unauthorized: [{'message': 'user not authorized to perform that action'}]
"""
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment