Select Git revision
initialize_student_tracking.py

Christopher Bohn authored
initialize_student_tracking.py 8.76 KiB
import datetime
import subprocess
from typing import Dict, List, Optional, Set
from gitlab.exceptions import GitlabGetError, GitlabListError
from api.canvas_classes import CanvasCourse, CanvasAssignment, CanvasUser
from api.composite_user import CompositeUser
from api.gitlab_classes import GitlabUser, GitlabProject, GitlabCommit
from common_functions import select_from_list, strip_html, semester_stamp
from course import Course
DEFAULT_HOMEWORK_REPOSITORY = 'csce361-homework'
def get_responses(assignment: CanvasAssignment, students: List[CanvasUser]) -> Dict[CanvasUser, str]:
# TODO: Can this be made more general and be useful to grade_team_contribution.display_peer_reviews() ?
student_responses: Dict[CanvasUser, Optional[str]] = {}
# noinspection PyUnusedLocal
student: CanvasUser
for student in students:
student_responses[student] = strip_html(assignment.get_submission_text(student))
return student_responses
def extract_gitlab_usernames(student_responses: Dict[CanvasUser, str]) -> Dict[CanvasUser, str]:
student_gitlab_usernames: Dict[CanvasUser, str] = {}
# noinspection PyUnusedLocal
student: CanvasUser
for student in student_responses:
response: str = student_responses[student]
if response is None:
response = ''
candidate_username: List[str] = response.split()
if len(candidate_username) == 1:
username: str = candidate_username[0]
if username[0] == '@':
student_gitlab_usernames[student] = username[1:]
else:
student_gitlab_usernames[student] = username
elif len(candidate_username) > 1:
print(f'Unexpected response from {student.get_name()}: {response}')
potential_usernames = list(filter(lambda s: s[0] == '@', candidate_username))
if len(potential_usernames) == 1:
username: str = potential_usernames[0]
print(f'Assigning {username} as username.')
student_gitlab_usernames[student] = username[1:]
else:
username: str = input('Enter gitlab username: ')
student_gitlab_usernames[student] = username
else:
print(f'{student.get_name()} has no response; assigning blank gitlab username.')
student_gitlab_usernames[student] = ''
print(f'\tCanvas username: {student.get_username()}\tGitlab username: @{student_gitlab_usernames[student]}')
return student_gitlab_usernames
def retrieve_gitlab_user(gitlab_username: str, student_name: str) -> Optional[GitlabUser]:
try:
if gitlab_username != '':
return GitlabUser(gitlab_username)
else:
return None
except IndexError:
print(f'\t{student_name} has an invalid gitlab username: {gitlab_username}.')
new_username: str = input('\tEnter new gitlab username (enter blank to skip): ')
return retrieve_gitlab_user(new_username, student_name)
# noinspection PyShadowingNames
def create_composite_users(student_usernames: Dict[CanvasUser, str]) -> (Set[CompositeUser], Set[CanvasUser]):
composite_users: Set[CompositeUser] = set()
skipped_users: Set[CanvasUser] = set()
# noinspection PyUnusedLocal
canvas_student: CanvasUser
for canvas_student in student_usernames:
gitlab_username: str = student_usernames[canvas_student]
gitlab_student: GitlabUser = retrieve_gitlab_user(gitlab_username, canvas_student.get_name())
if gitlab_student is not None:
composite_user: CompositeUser = CompositeUser.initialize_composite_user(canvas_student, gitlab_student)
composite_users.add(composite_user)
else:
print(f'{canvas_student.get_name()} has a blank gitlab username; skipping.')
skipped_users.add(canvas_student)
return composite_users, skipped_users
def retrieve_homework_repos(students: Set[CompositeUser], repo_name: str) \
-> Dict[CompositeUser, Optional[GitlabProject]]:
user_repos: Dict[CompositeUser, GitlabProject] = {}
for student in students:
path = student.get_gitlab_user().get_username() + '/' + repo_name
try:
print(f'\tRetrieving {path}.git at {datetime.datetime.now()}.')
repo = GitlabProject(path)
except GitlabGetError:
print(f'*** WARNING *** Could not retrieve {path}.git!')
repo = None
user_repos[student] = repo
return user_repos
def add_gitlab_email(user_repos: Dict[CompositeUser, Optional[GitlabProject]]) -> None:
# noinspection PyUnusedLocal
student: CompositeUser
for student in user_repos:
project = user_repos[student]
if project is not None:
try:
commits: List[GitlabCommit] = project.get_commits()
except GitlabListError:
print(f'*** NOTE *** Could not access commits for {project}. '
f'This probably indicates Guest permissions.')
commits = []
if len(commits) > 0:
commit = commits[-1] # should be the initial commit; regardless, all commits should be by the student
email = commit.get_author()['email']
student.set_gitlab_email(email)
def create_cloning_script(user_repos: Dict[CompositeUser, Optional[GitlabProject]],
no_username: Set[CanvasUser], filename: str) -> None:
file = open(filename, 'x')
file.write('#!/bin/bash\n\n')
file.write('# Auto-generated clone script.\n')
no_repo: Set[CompositeUser] = set()
# noinspection PyUnusedLocal
composite_student: CompositeUser
for composite_student in user_repos:
repo = user_repos[composite_student]
if repo is not None:
repo_url = repo.get_cloning_url()
student_name = composite_student.get_canvas_user().get_name().replace(' ', '_')
file.write(f'git clone {repo_url} {student_name}')
if composite_student.gitlab_email is None: # breaking encapsulation?
file.write(' # may have inadequate permissions')
file.write('\n')
else:
no_repo.add(composite_student)
if len(no_repo) > 0:
file.write('\n# Students without homework repositories '
'(check for misspelled repository names or repositories created but not shared):\n')
for composite_student in no_repo:
student_name = composite_student.get_canvas_user().get_name().replace(' ', '_')
file.write(f'# git clone {composite_student.get_gitlab_user().get_username()}/... {student_name}\n')
if len(no_username) > 0:
file.write('\n# Students without valid gitlab usernames (check for repositories shared but username not '
'submitted):\n')
# noinspection PyUnusedLocal
canvas_student: CanvasUser
for canvas_student in no_username:
student_name = canvas_student.get_name().replace(' ', '_')
file.write(f'# git clone ... {student_name}\n')
file.close()
subprocess.call(['chmod', '+x', filename])
print(f'\tCreated {filename}. Distribute to TAs so they can clone the homework repositories.')
if __name__ == '__main__':
course = CanvasCourse(Course.canvas_course_id)
print('First, select the "setup" assignment to extract student information from.\n')
assignment_groups = course.get_assignment_groups()
assignment_group = select_from_list(assignment_groups, 'assignment group')
print()
assignments = assignment_group.get_assignments()
setup_assignment = select_from_list(assignments, 'assignment')
print(f'\nWe will now extract student information from {setup_assignment}.')
responses = get_responses(setup_assignment, course.get_students())
gitlab_usernames = extract_gitlab_usernames(responses)
print('\nWe will now check the validity of the gitlab usernames.')
composite_users, skipped_users = create_composite_users(gitlab_usernames)
if len(skipped_users) == 0:
print(f'\nSuccessfully retrieved {len(composite_users)} students.')
else:
print(
f'\n*** WARNING *** Skipped {len(skipped_users)} '
f'out of {len(skipped_users)+len(composite_users)} students!')
print('We will now retrieve the students\' homework repositories.')
choice = input(f'Enter homework repository name [{DEFAULT_HOMEWORK_REPOSITORY}]: ')
if choice == '':
choice = DEFAULT_HOMEWORK_REPOSITORY
homework_repos = retrieve_homework_repos(composite_users, choice)
add_gitlab_email(homework_repos)
print('\nCreating cloning script for homework repositories.')
create_cloning_script(homework_repos, skipped_users, f'{semester_stamp()}-homework.sh')
CompositeUser.write_student_csv(set(homework_repos.keys()), f'{semester_stamp()}-students.csv')
print('\tCreated student csv file.')