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
+    """