Skip to content
Snippets Groups Projects
Select Git revision
  • a118a07c4c43f2322ec0e7e7f2d7153683da893e
  • master default protected
2 results

initialize_student_tracking.py

Blame
  • 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.')