def instructor_grading_list_grader_assignments(ctx, course, assignment_id, grader_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) if grader_id is not None: grader = get_grader_or_exit(ctx, course, grader_id) else: grader = None teams_registrations = get_teams_registrations(course, assignment) teams = list(teams_registrations.keys()) teams.sort(key=operator.attrgetter("team_id")) for t in teams: registration = teams_registrations[t] if grader is None: if registration.grader is None: grader_str = "<no-grader-assigned>" else: grader_str = registration.grader.user.username print(t.team_id, grader_str) else: if grader == registration.grader.user.username: print(t.team_id) return CHISUBMIT_SUCCESS
def instructor_grading_add_rubrics(ctx, course, assignment_id, commit, all_teams): assignment = course.get_assignment(assignment_id) if assignment is None: print "Assignment %s does not exist" % assignment_id ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment, only_ready_for_grading=not all_teams) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print "%s does not have a grading repository" % team.team_id continue rubric = RubricFile.from_assignment(assignment, registration.get_grades()) rubricfile = "%s.rubric.txt" % assignment.assignment_id rubricfilepath = "%s/%s" % (repo.repo_path, rubricfile) if commit: if not os.path.exists(rubricfilepath): rubric.save(rubricfilepath, include_blank_comments=True) rv = repo.commit([rubricfile], "Added grading rubric") if rv: print rubricfilepath, "(COMMITTED)" else: print rubricfilepath, "(NO CHANGES - Not committed)" else: print rubricfilepath, "(SKIPPED - already exists)" else: rubric.save(rubricfilepath, include_blank_comments=True) print rubricfilepath
def grader_validate_rubrics(ctx, course, assignment_id, grader, only): if grader is None: user = ctx.obj["client"].get_user() grader = get_grader_or_exit(ctx, course, user.username) else: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader = grader, only = only) all_valid = True for team, registration in teams_registrations.items(): valid, error_msg = validate_repo_rubric(ctx, course, assignment, team, registration) if valid: print "%s: Rubric OK." % team.team_id else: print "%s: Rubric ERROR: %s" % (team.team_id, error_msg) all_valid = False if not all_valid: ctx.exit(CHISUBMIT_FAIL) else: return CHISUBMIT_SUCCESS
def instructor_grading_list_grader_assignments(ctx, course, assignment_id, grader_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) if grader_id is not None: grader = get_grader_or_exit(ctx, course, grader_id) else: grader = None teams_registrations = get_teams_registrations(course, assignment) teams = teams_registrations.keys() teams.sort(key=operator.attrgetter("team_id")) for t in teams: registration = teams_registrations[t] if grader is None: if registration.grader is None: grader_str = "<no-grader-assigned>" else: grader_str = registration.grader.user.username print t.team_id, grader_str else: if grader == registration.grader.user.username: print t.team_id return CHISUBMIT_SUCCESS
def instructor_grading_pull_grading(ctx, course, assignment_id, from_students, only, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if from_students: print "Pulling grading from the student repositories is an uncommon operation." print "Instead, you should only ever make changes to the grading in the staging" print "repositories, and push those changes to the student repositories (which" print "should always mirror what is in the staging repositories). So, there should" print "never be a need to pull grading from a student repository." print print "Are you sure you want to continue and pull the grading from" print "the student repositories? (y/n): ", if not yes: yesno = raw_input() else: yesno = 'y' print 'y' if yesno not in ('y', 'Y', 'yes', 'Yes', 'YES'): ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment, only = only) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] print "Pulling grading branch for team %s... " % team.team_id gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration, from_students = from_students) return CHISUBMIT_SUCCESS
def instructor_grading_push_grading(ctx, course, assignment_id, to_students, all_teams, only, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if to_students: print "You are going to push the grading branches to the student repositories." print "If you do so, students will be able to see their grading!" print print "Are you sure you want to continue? (y/n): ", if not yes: yesno = raw_input() else: yesno = 'y' print 'y' if yesno not in ('y', 'Y', 'yes', 'Yes', 'YES'): ctx.exit(CHISUBMIT_FAIL) print else: print "Pushing grading to staging repositories. Only instructors and graders will" print "be able to access this grading. If you want to push the grading to the" print "student repositories, use the --to-students option" print teams_registrations = get_teams_registrations(course, assignment, only = only, only_ready_for_grading=not all_teams) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] print ("Pushing grading branch for team %s... " % team.team_id), gradingrepo_push_grading_branch(ctx.obj['config'], course, team, registration, to_students = to_students) print "done." return CHISUBMIT_SUCCESS
def grader_push_grading(ctx, course, assignment_id, grader, only, skip_rubric_validation): if grader is None: user = ctx.obj["client"].get_user() grader = get_grader_or_exit(ctx, course, user.username) else: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader = grader, only = only, only_ready_for_grading=True) if len(teams_registrations) == 0: print "No teams found" ctx.exit(CHISUBMIT_FAIL) for team, registration in teams_registrations.items(): if not skip_rubric_validation: valid, error_msg = validate_repo_rubric(ctx, course, assignment, team, registration) if not valid: print "Not pushing branch for team %s. Rubric does not validate: %s" % (team.team_id, error_msg) continue print "Pushing grading branch for team %s... " % team.team_id gradingrepo_push_grading_branch(ctx.obj['config'], course, team, registration) return CHISUBMIT_SUCCESS
def grader_validate_rubrics(ctx, course, assignment_id, grader, only): if grader is None: user = ctx.obj["client"].get_user() grader = get_grader_or_exit(ctx, course, user.username) else: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader=grader, only=only) all_valid = True for team, registration in list(teams_registrations.items()): valid, error_msg = validate_repo_rubric(ctx, course, assignment, team, registration) if valid: print("%s: Rubric OK." % team.team_id) else: print("%s: Rubric ERROR: %s" % (team.team_id, error_msg)) all_valid = False if not all_valid: ctx.exit(CHISUBMIT_FAIL) else: return CHISUBMIT_SUCCESS
def instructor_grading_list_submissions(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) conn = create_connection(course, ctx.obj['config']) if conn is None: print "Could not connect to git server." ctx.exit(CHISUBMIT_FAIL) for team in teams: registration = teams_registrations[team] if registration.final_submission is None: print "%25s NOT SUBMITTED" % team.team_id else: submission_commit = conn.get_commit(course, team, registration.final_submission.commit_sha) if submission_commit is not None: if registration.is_ready_for_grading(): print "%25s SUBMITTED (READY for grading)" % team.team_id else: print "%25s SUBMITTED (NOT READY for grading)" % team.team_id else: print "%25s ERROR: Submitted but commit %s not found in repository" % (team.team_id, registration.final_submission.commit_sha) return CHISUBMIT_SUCCESS
def instructor_grading_list_submissions(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) for team in teams: registration = teams_registrations[team] if registration.final_submission is None: print("%25s NOT SUBMITTED" % team.team_id) else: submission_commit = conn.get_commit( course, team, registration.final_submission.commit_sha) if submission_commit is not None: if registration.is_ready_for_grading(): print("%25s SUBMITTED (READY for grading)" % team.team_id) else: print("%25s SUBMITTED (NOT READY for grading)" % team.team_id) else: print( "%25s ERROR: Submitted but commit %s not found in repository" % (team.team_id, registration.final_submission.commit_sha)) return CHISUBMIT_SUCCESS
def instructor_grading_create_grading_repos(ctx, course, assignment_id, all_teams, only, master): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations( course, assignment, only=only, only_ready_for_grading=not all_teams) if len(teams_registrations) == 0: ctx.exit(CHISUBMIT_FAIL) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print("%40s -- Creating grading repo... " % team.team_id), repo = GradingGitRepo.create_grading_repo(ctx.obj['config'], course, team, registration, staging_only=not master) repo.sync() if registration.final_submission is not None: if repo.has_grading_branch_staging(): gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print "done" else: if master: repo.create_grading_branch() print "done (and created grading branch)" else: print "done (warning: could not pull grading branch; it does not exist)" else: print "done (note: has not submitted yet)" else: print("%40s -- Updating grading repo... " % team.team_id), if repo.has_grading_branch_staging(): gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print "done (pulled latest grading branch)" elif repo.has_grading_branch(): print "nothing to update (grading branch is not in staging)" elif registration.final_submission is not None and master: repo.create_grading_branch() print "done (created missing grading branch)" else: print "nothing to update (there is no grading branch)" return CHISUBMIT_SUCCESS
def grader_pull_grading(ctx, course, grader, assignment_id): if grader is None: user = ctx.obj["client"].get_user() grader = get_grader_or_exit(ctx, course, user.username) else: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader=grader) if len(teams_registrations) == 0: print("No teams found") ctx.exit(CHISUBMIT_FAIL) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print(("%40s -- Creating grading repo... " % team.team_id), end=' ') repo = GradingGitRepo.create_grading_repo(ctx.obj['config'], course, team, registration, staging_only=True) repo.sync() gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) repo.set_grader_author() print("done") else: print(("%40s -- Pulling grading branch..." % team.team_id), end=' ') gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print("done") rubricfile = "%s.rubric.txt" % assignment.assignment_id rubricfilepath = "%s/%s" % (repo.repo_path, rubricfile) if not os.path.exists(rubricfilepath): rubric = RubricFile.from_assignment(assignment) rubric.save(rubricfilepath, include_blank_comments=True) return CHISUBMIT_SUCCESS
def instructor_team_pull_repos(ctx, course, assignment_id, directory, only_ready_for_grading, reset, only): assignment = get_assignment_or_exit(ctx, course, assignment_id) conn = create_connection(course, ctx.obj['config']) teams_registrations = get_teams_registrations(course, assignment, only = only) directory = os.path.expanduser(directory) if not os.path.exists(directory): os.makedirs(directory) teams = sorted([t for t in teams_registrations.keys() if t.active], key = operator.attrgetter("team_id")) max_len = max([len(t.team_id) for t in teams]) for team in teams: registration = teams_registrations[team] team_dir = "%s/%s" % (directory, team.team_id) team_git_url = conn.get_repository_git_url(course, team) if not registration.is_ready_for_grading() and only_ready_for_grading: print "%-*s SKIPPING (not ready for grading)" % (max_len, team.team_id) continue try: msg = "" if not os.path.exists(team_dir): r = LocalGitRepo.create_repo(team_dir, clone_from_url=team_git_url) msg = "Cloned repo" else: r = LocalGitRepo(team_dir) if reset: r.fetch("origin") r.reset_branch("origin", "master") msg = "Reset to match origin/master" else: if r.repo.is_dirty(): print "%-*s ERROR: Cannot pull. Local repository has unstaged changes." % (max_len, team.team_id) continue r.checkout_branch("master") r.pull("origin", "master") msg = "Pulled latest changes" if only_ready_for_grading: r.checkout_commit(registration.final_submission.commit_sha) msg += " and checked out commit %s" % (registration.final_submission.commit_sha) print "%-*s %s" % (max_len, team.team_id, msg) except ChisubmitException, ce: print "%-*s ERROR: Could not checkout or pull master branch (%s)" % (max_len, team.team_id, ce.message) except GitCommandError, gce: print "%-*s ERROR: Could not checkout or pull master branch" % (max_len, team.team_id) print gce
def instructor_grading_push_grading(ctx, course, assignment_id, to_students, all_teams, only, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if to_students: print( "You are going to push the grading branches to the student repositories." ) print("If you do so, students will be able to see their grading!") print() print("Are you sure you want to continue? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno not in ('y', 'Y', 'yes', 'Yes', 'YES'): ctx.exit(CHISUBMIT_FAIL) print() else: print( "Pushing grading to staging repositories. Only instructors and graders will" ) print( "be able to access this grading. If you want to push the grading to the" ) print("student repositories, use the --to-students option") print() teams_registrations = get_teams_registrations( course, assignment, only=only, only_ready_for_grading=not all_teams) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] print(("Pushing grading branch for team %s... " % team.team_id), end=' ') gradingrepo_push_grading_branch(ctx.obj['config'], course, team, registration, to_students=to_students) print("done.") return CHISUBMIT_SUCCESS
def instructor_grading_pull_grading(ctx, course, assignment_id, from_students, only, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if from_students: print( "Pulling grading from the student repositories is an uncommon operation." ) print( "Instead, you should only ever make changes to the grading in the staging" ) print( "repositories, and push those changes to the student repositories (which" ) print( "should always mirror what is in the staging repositories). So, there should" ) print("never be a need to pull grading from a student repository.") print() print("Are you sure you want to continue and pull the grading from") print("the student repositories? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno not in ('y', 'Y', 'yes', 'Yes', 'YES'): ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment, only=only) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] print("Pulling grading branch for team %s... " % team.team_id) gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration, from_students=from_students) return CHISUBMIT_SUCCESS
def instructor_validate_rubrics(ctx, course, assignment_id, grader, only): if grader is not None: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader = grader, only = only) for team, registration in teams_registrations.items(): valid, error_msg = validate_repo_rubric(ctx, course, assignment, team, registration) if valid: print "%s: Rubric OK." % team.team_id else: print "%s: Rubric ERROR: %s" % (team.team_id, error_msg) return CHISUBMIT_SUCCESS
def instructor_grading_create_grading_repos(ctx, course, assignment_id, all_teams, only, master): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, only = only, only_ready_for_grading=not all_teams) if len(teams_registrations) == 0: ctx.exit(CHISUBMIT_FAIL) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print ("%20s -- Creating grading repo... " % team.team_id), repo = GradingGitRepo.create_grading_repo(ctx.obj['config'], course, team, registration, staging_only = not master) repo.sync() if registration.final_submission is not None: if repo.has_grading_branch_staging(): gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print "done" else: if master: repo.create_grading_branch() print "done (and created grading branch)" else: print "done (warning: could not pull grading branch; it does not exist)" else: print "done (note: has not submitted yet)" else: print "%20s -- Updating grading repo... " % team.team_id if repo.has_grading_branch_staging(): gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print "done (pulled latest grading branch)" elif registration.final_submission is not None and master: repo.create_grading_branch() print "done (created missing grading branch)" else: print "nothing to update (there is no grading branch)" return CHISUBMIT_SUCCESS
def grader_pull_grading(ctx, course, grader, assignment_id): if grader is None: user = ctx.obj["client"].get_user() grader = get_grader_or_exit(ctx, course, user.username) else: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader = grader) if len(teams_registrations) == 0: print "No teams found" ctx.exit(CHISUBMIT_FAIL) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print ("%20s -- Creating grading repo... " % team.team_id), repo = GradingGitRepo.create_grading_repo(ctx.obj['config'], course, team, registration, staging_only = True) repo.sync() gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) repo.set_grader_author() print "done" else: print ("%20s -- Pulling grading branch..." % team.team_id), gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print "done" rubricfile = "%s.rubric.txt" % assignment.assignment_id rubricfilepath = "%s/%s" % (repo.repo_path, rubricfile) if not os.path.exists(rubricfilepath): rubric = RubricFile.from_assignment(assignment) rubric.save(rubricfilepath, include_blank_comments=True) return CHISUBMIT_SUCCESS
def instructor_validate_rubrics(ctx, course, assignment_id, grader, only): if grader is not None: grader = get_grader_or_exit(ctx, course, grader) assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, grader=grader, only=only) for team, registration in list(teams_registrations.items()): valid, error_msg = validate_repo_rubric(ctx, course, assignment, team, registration) if valid: print("%s: Rubric OK." % team.team_id) else: print("%s: Rubric ERROR: %s" % (team.team_id, error_msg)) return CHISUBMIT_SUCCESS
def instructor_grading_add_rubrics(ctx, course, assignment_id, commit, all_teams): assignment = course.get_assignment(assignment_id) if assignment is None: print("Assignment %s does not exist" % assignment_id) ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations( course, assignment, only_ready_for_grading=not all_teams) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print("%s does not have a grading repository" % team.team_id) continue rubric = RubricFile.from_assignment(assignment, registration.get_grades()) rubricfile = "%s.rubric.txt" % assignment.assignment_id rubricfilepath = "%s/%s" % (repo.repo_path, rubricfile) if commit: if not os.path.exists(rubricfilepath): rubric.save(rubricfilepath, include_blank_comments=True) rv = repo.commit([rubricfile], "Added grading rubric") if rv: print(rubricfilepath, "(COMMITTED)") else: print(rubricfilepath, "(NO CHANGES - Not committed)") else: print(rubricfilepath, "(SKIPPED - already exists)") else: rubric.save(rubricfilepath, include_blank_comments=True) print(rubricfilepath)
def instructor_grading_show_grading_status(ctx, course, assignment_id, by_grader, include_diff_urls, use_stored_grades): assignment = get_assignment_or_exit(ctx, course, assignment_id, include_rubric = True) rubric_components = assignment.get_rubric_components() teams_registrations = get_teams_registrations(course, assignment, include_grades = use_stored_grades) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) team_status = [] graders = set() for team in teams: registration = teams_registrations[team] if registration.grader is None: grader_str = "<no grader assigned>" else: grader_str = registration.grader.user.username graders.add(grader_str) grading_status = None diff_url = "" if use_stored_grades: grades = registration.get_grades() total_grade = registration.get_total_grade() graded_rc_ids = [g.rubric_component_id for g in grades] else: total_grade = 0.0 repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: grading_status = "NO GRADING REPO" else: rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): grading_status = "NOT GRADED - No rubric" else: try: rubric = RubricFile.from_file(open(rubricfile), assignment) graded_rc_ids = [rc.id for rc in rubric_components if rubric.points_obtained[rc.description] is not None] total_grade = rubric.get_total_points_obtained() except ChisubmitRubricException as cre: grading_status = "ERROR: Rubric does not validate (%s)" % (cre) if grading_status is None: has_some = False has_all = True for rc in rubric_components: if rc.id in graded_rc_ids: has_some = True else: has_all = False if not has_some: grading_status = "NOT GRADED" elif has_all: grading_status = "GRADED" else: grading_status = "PARTIALLY GRADED" if include_diff_urls and has_some: commit_sha = registration.final_submission.commit_sha[:8] diff_url = "https://mit.cs.uchicago.edu/%s-staging/%s/compare/%s...%s-grading" % (course.course_id, team.team_id, commit_sha, assignment.assignment_id) team_status.append((team.team_id, grader_str, total_grade, diff_url, grading_status)) if not by_grader: for team, grader, total_grade, diff_url, status in team_status: print("%-40s %-20s %-20.2f %10s %s" % (team, status, total_grade, grader, diff_url)) else: all_grades = [] for grader in sorted(list(graders)): print(grader) print("-" * len(grader)) grades = [] team_status_grader = [ts for ts in team_status if ts[1] == grader] for team, _, total_grade, diff_url, status in team_status_grader: if status == "NOT GRADED": print("%-40s %s %s" % (team, status, diff_url)) else: print("%-40s %s %8.2f %s" % (team, status, total_grade, diff_url)) if status == "GRADED": grades.append(total_grade) if len(grades) > 0: all_grades += grades print_grades_stats(grades) print() if len(all_grades) > 0: print("TOTAL") print("-----") print_grades_stats(all_grades) return CHISUBMIT_SUCCESS
def instructor_grading_collect_rubrics(ctx, course, assignment_id, dry_run, only, grader_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) if grader_id is not None: grader = get_grader_or_exit(ctx, course, grader_id) else: grader = None rcs = assignment.get_rubric_components() teams_registrations = get_teams_registrations(course, assignment, grader=grader, only=only) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print("Repository for %s does not exist" % (team.team_id)) continue rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): print( "Repository for %s does not have a rubric for assignment %s" % (team.team_id, assignment.assignment_id)) continue try: rubric = RubricFile.from_file(open(rubricfile), assignment) except ChisubmitRubricException as cre: print("ERROR: Rubric for %s does not validate (%s)" % (team.team_id, cre)) continue points = [] for rc in rcs: grade = rubric.points_obtained[rc.description] if grade is None: points.append(0.0) else: if not dry_run: registration.set_grade(rc, grade) points.append(grade) adjustments = {} total_penalties = 0.0 total_bonuses = 0.0 if rubric.penalties is not None: for desc, p in list(rubric.penalties.items()): adjustments[desc] = p total_penalties += p if rubric.bonuses is not None: for desc, p in list(rubric.bonuses.items()): adjustments[desc] = p total_bonuses += p if not dry_run: registration.grade_adjustments = adjustments if ctx.obj["verbose"]: print(team.team_id) print("Points Obtained: %s" % points) print("Penalties: %.2f" % total_penalties) print("Bonuses: %.2f" % total_bonuses) if not dry_run: total_grade = registration.get_total_grade() else: total_grade = sum(points) + total_penalties + total_bonuses if ctx.obj["verbose"]: print("TOTAL: %.2f" % total_grade) print() else: print("%-40s %.2f" % (team.team_id, total_grade))
def instructor_grading_assign_graders(ctx, course, assignment_id, from_assignment, avoid_assignment, grader_file, dry_run, reset): assignment = get_assignment_or_exit(ctx, course, assignment_id) if from_assignment is not None: from_assignment = get_assignment_or_exit(ctx, course, from_assignment) if avoid_assignment is not None: avoid_assignment = get_assignment_or_exit(ctx, course, avoid_assignment) if reset and from_assignment is not None: print("--reset and --from_assignment are mutually exclusive") ctx.exit(CHISUBMIT_FAIL) if avoid_assignment is not None and from_assignment is not None: print( "--avoid_assignment and --from_assignment are mutually exclusive") ctx.exit(CHISUBMIT_FAIL) if grader_file is not None: grader_workload = yaml.load(grader_file) graders = [] for username in grader_workload: try: grader = course.get_grader(username) graders.append(grader) except UnknownObjectException: print("No such grader: %s" % username) ctx.exit(CHISUBMIT_FAIL) else: graders = course.get_graders() grader_workload = {g.user.username: "******" for g in graders} if len(graders) == 0: print("ERROR: No graders.") ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment) teams = list(teams_registrations.keys()) n_teams = len(teams) teams_per_grader = {} teams_per_grader_assigned = dict([(g.user.username, 0) for g in graders]) assigned = 0 graders_assigned_remainder = [] for username, workload in list(grader_workload.items()): if workload != "remainder": if not isinstance(workload, int) or workload <= 0: print("Invalid workload for grader '%s': %s" % (username, workload)) teams_per_grader[username] = workload assigned += workload else: teams_per_grader[username] = None graders_assigned_remainder.append(username) min_teams_per_grader = old_div((n_teams - assigned), len(graders_assigned_remainder)) extra_teams = (n_teams - assigned) % len(graders_assigned_remainder) for username in teams_per_grader: if teams_per_grader[username] is None: teams_per_grader[username] = min_teams_per_grader random.seed(course.course_id + assignment_id) random.shuffle(graders_assigned_remainder) # so many graders in this course that some will end up expecting zero # teams to grade. Make sure they are able to get at least one. for username in graders_assigned_remainder[:extra_teams]: teams_per_grader[username] += 1 assert sum([v for v in list(teams_per_grader.values())]) == n_teams team_grader = {t.team_id: None for t in list(teams_registrations.keys())} if from_assignment is not None: from_assignment_registrations = get_teams_registrations( course, from_assignment) common_teams = [ t for t in teams if t in teams_registrations and t in from_assignment_registrations ] for t in common_teams: registration = from_assignment_registrations[t] # try to assign the same grader that would grade the same team's other assignment grader = registration.grader if grader is not None and teams_per_grader[ grader.user.username] > 0: team_grader[t.team_id] = grader.user.username teams_per_grader[grader.user.username] -= 1 teams_per_grader_assigned[grader.user.username] += 1 if avoid_assignment is not None: avoid_assignment_registrations = get_teams_registrations( course, avoid_assignment) if not reset: for t in teams: if teams_registrations[t].grader is None: team_grader[t.team_id] = None else: grader_id = teams_registrations[t].grader.user.username team_grader[t.team_id] = grader_id teams_per_grader[grader_id] = max( 0, teams_per_grader[grader_id] - 1) teams_per_grader_assigned[grader_id] += 1 not_ready_for_grading = [] ta_avoid = {} graders_cycle = itertools.cycle(graders) for team, registration in list(teams_registrations.items()): if team_grader[team.team_id] is not None: continue if not registration.is_ready_for_grading(): not_ready_for_grading.append(team.team_id) continue graders_left = len(graders) for g in graders_cycle: # Make sure we don't fall into an infinite loop if graders_left == 0: print("Cannot find a grader for %s!" % team.team_id) break else: graders_left -= 1 if teams_per_grader[g.user.username] == 0: continue else: if avoid_assignment is not None: avoid_registration = ta_avoid.setdefault( team.team_id, avoid_assignment_registrations.get(team)) if avoid_registration is not None and avoid_registration.grader.user.username == grader.user.username: continue valid = True for tm in team.get_team_members(): if tm.username in g.conflicts_usernames: valid = False break if valid: team_grader[team.team_id] = g.user.username teams_per_grader[g.user.username] -= 1 teams_per_grader_assigned[g.user.username] += 1 break for team, registration in list(teams_registrations.items()): if team_grader[team.team_id] is None: if team.team_id not in not_ready_for_grading: print("Team %s has no grader" % (team.team_id)) else: print("Team %s's submission isn't ready for grading yet" % (team.team_id)) else: if not dry_run: if registration.grader_username != team_grader[team.team_id]: registration.grader_username = team_grader[team.team_id] else: print("%s: %s" % (team.team_id, team_grader[team.team_id])) print() for grader_id, assigned in list(teams_per_grader_assigned.items()): if teams_per_grader[grader_id] != 0: print( grader_id, assigned, "(still needs to be assigned %i more assignments)" % (teams_per_grader[grader_id])) else: print(grader_id, assigned) return CHISUBMIT_SUCCESS
def instructor_grading_show_grading_status(ctx, course, assignment_id, by_grader, include_diff_urls, use_stored_grades): assignment = get_assignment_or_exit(ctx, course, assignment_id, include_rubric=True) rubric_components = assignment.get_rubric_components() teams_registrations = get_teams_registrations( course, assignment, include_grades=use_stored_grades) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) team_status = [] graders = set() for team in teams: registration = teams_registrations[team] if registration.grader is None: grader_str = "<no grader assigned>" else: grader_str = registration.grader.user.username graders.add(grader_str) grading_status = None diff_url = "" if use_stored_grades: grades = registration.get_grades() total_grade = registration.get_total_grade() graded_rc_ids = [g.rubric_component_id for g in grades] else: total_grade = 0.0 repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: grading_status = "NO GRADING REPO" else: rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): grading_status = "NOT GRADED - No rubric" else: try: rubric = RubricFile.from_file(open(rubricfile), assignment) graded_rc_ids = [ rc.id for rc in rubric_components if rubric.points_obtained[rc.description] is not None ] total_grade = rubric.get_total_points_obtained() except ChisubmitRubricException as cre: grading_status = "ERROR: Rubric does not validate (%s)" % ( cre) if grading_status is None: has_some = False has_all = True for rc in rubric_components: if rc.id in graded_rc_ids: has_some = True else: has_all = False if not has_some: grading_status = "NOT GRADED" elif has_all: grading_status = "GRADED" else: grading_status = "PARTIALLY GRADED" if include_diff_urls and has_some: commit_sha = registration.final_submission.commit_sha[:8] diff_url = "https://mit.cs.uchicago.edu/%s-staging/%s/compare/%s...%s-grading" % ( course.course_id, team.team_id, commit_sha, assignment.assignment_id) team_status.append( (team.team_id, grader_str, total_grade, diff_url, grading_status)) if not by_grader: for team, grader, total_grade, diff_url, status in team_status: print("%-40s %-20s %-20.2f %10s %s" % (team, status, total_grade, grader, diff_url)) else: all_grades = [] for grader in sorted(list(graders)): print(grader) print("-" * len(grader)) grades = [] team_status_grader = [ts for ts in team_status if ts[1] == grader] for team, _, total_grade, diff_url, status in team_status_grader: if status == "NOT GRADED": print("%-40s %s %s" % (team, status, diff_url)) else: print("%-40s %s %8.2f %s" % (team, status, total_grade, diff_url)) if status == "GRADED": grades.append(total_grade) if len(grades) > 0: all_grades += grades print_grades_stats(grades) print() if len(all_grades) > 0: print("TOTAL") print("-----") print_grades_stats(all_grades) return CHISUBMIT_SUCCESS
def instructor_grading_show_grading_status(ctx, course, assignment_id, by_grader, include_diff_urls, use_stored_grades): assignment = get_assignment_or_exit(ctx, course, assignment_id, include_rubric = True) rubric_components = assignment.get_rubric_components() teams_registrations = get_teams_registrations(course, assignment, include_grades = True) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) team_status = [] graders = set() for team in teams: registration = teams_registrations[team] if registration.grader is None: grader_str = "<no grader assigned>" else: grader_str = registration.grader.user.username graders.add(grader_str) grading_status = None if use_stored_grades: grades = registration.get_grades() total_grade = registration.get_total_grade() graded_rc_ids = [g.rubric_component_id for g in grades] else: total_grade = 0.0 repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: grading_status = "NO GRADING REPO" else: rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): grading_status = "NOT GRADED - No rubric" else: try: rubric = RubricFile.from_file(open(rubricfile), assignment) graded_rc_ids = [rc.id for rc in rubric_components if rubric.points[rc.description] is not None] for rc in rubric_components: grade = rubric.points[rc.description] if grade is not None: total_grade += grade if rubric.penalties is not None: for p in rubric.penalties.values(): total_grade += p if rubric.bonuses is not None: for p in rubric.bonuses.values(): total_grade += p except ChisubmitRubricException, cre: grading_status = "ERROR: Rubric does not validate (%s)" % (cre.message) if grading_status is None: has_some = False has_all = True for rc in rubric_components: if rc.id in graded_rc_ids: has_some = True else: has_all = False if not has_some: grading_status = "NOT GRADED" elif has_all: grading_status = "GRADED" else: grading_status = "PARTIALLY GRADED" if include_diff_urls and has_some: commit_sha = registration.final_submission.commit_sha[:8] diff_url = "https://mit.cs.uchicago.edu/%s-staging/%s/compare/%s...%s-grading" % (course.course_id, team.team_id, commit_sha, assignment.assignment_id) else: diff_url = "" team_status.append((team.team_id, grader_str, total_grade, diff_url, grading_status))
def instructor_grading_create_grading_repos(ctx, course, assignment_id, all_teams, only, master): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations( course, assignment, only=only, only_ready_for_grading=not all_teams) if len(teams_registrations) == 0: print("There are no grading repos to create.") ctx.exit(CHISUBMIT_FAIL) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print(("%40s -- Creating grading repo... " % team.team_id), end=' ') try: repo = GradingGitRepo.create_grading_repo( ctx.obj['config'], course, team, registration, staging_only=not master) repo.sync() except GitCommandError as gce: print(gce) if registration.final_submission is not None: if repo.has_grading_branch_staging(): if not registration.grading_started: print( "ERROR: This repo has a grading branch, but is not marked as ready for grading." ) else: gradingrepo_pull_grading_branch( ctx.obj['config'], course, team, registration) print("done") else: if master: repo.create_grading_branch() registration.grading_started = True print("done (and created grading branch)") else: print( "done (warning: could not pull grading branch; it does not exist)" ) else: if registration.grading_started: print( "ERROR: This team has not submitted this assignment, but the repo is marked as ready for grading." ) else: print("done (note: has not submitted yet)") else: print(("%40s -- Updating grading repo... " % team.team_id), end=' ') if repo.has_grading_branch_staging(): if not registration.grading_started: print( "ERROR: This repo has a grading branch, but is not marked as ready for grading." ) else: try: gradingrepo_pull_grading_branch( ctx.obj['config'], course, team, registration) print("done (pulled latest grading branch)") except GitCommandError as gce: print(gce) elif repo.has_grading_branch(): print("nothing to update (grading branch is not in staging)") elif registration.final_submission is not None and master: try: repo.create_grading_branch() if not registration.grading_started: registration.grading_started = True print("done (created missing grading branch)") except GitCommandError as gce: print(gce) else: print("nothing to update (there is no grading branch)") return CHISUBMIT_SUCCESS
def instructor_grading_assign_graders(ctx, course, assignment_id, from_assignment, avoid_assignment, grader_file, dry_run, reset): assignment = get_assignment_or_exit(ctx, course, assignment_id) if from_assignment is not None: from_assignment = get_assignment_or_exit(ctx, course, from_assignment) if avoid_assignment is not None: avoid_assignment = get_assignment_or_exit(ctx, course, avoid_assignment) if reset and from_assignment is not None: print "--reset and --from_assignment are mutually exclusive" ctx.exit(CHISUBMIT_FAIL) if avoid_assignment is not None and from_assignment is not None: print "--avoid_assignment and --from_assignment are mutually exclusive" ctx.exit(CHISUBMIT_FAIL) if grader_file is not None: grader_workload = yaml.load(grader_file) graders = [] for username in grader_workload: try: grader = course.get_grader(username) graders.append(grader) except UnknownObjectException: print "No such grader: %s" % username ctx.exit(CHISUBMIT_FAIL) else: graders = course.get_graders() grader_workload = {g.user.username: "******" for g in graders} if len(graders) == 0: print "ERROR: No graders." ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment) teams = teams_registrations.keys() n_teams = len(teams) teams_per_grader = {} teams_per_grader_assigned = dict([(g.user.username, 0) for g in graders]) assigned = 0 graders_assigned_remainder = [] for username, workload in grader_workload.items(): if workload != "remainder": if not isinstance(workload, int) or workload <= 0: print "Invalid workload for grader '%s': %s" % (username, workload) teams_per_grader[username] = workload assigned += workload else: teams_per_grader[username] = None graders_assigned_remainder.append(username) min_teams_per_grader = (n_teams - assigned) / len(graders_assigned_remainder) extra_teams = (n_teams - assigned) % len(graders_assigned_remainder) for username in teams_per_grader: if teams_per_grader[username] is None: teams_per_grader[username] = min_teams_per_grader random.seed(course.course_id + assignment_id) random.shuffle(graders_assigned_remainder) # so many graders in this course that some will end up expecting zero # teams to grade. Make sure they are able to get at least one. for username in graders_assigned_remainder[:extra_teams]: teams_per_grader[username] += 1 assert sum([v for v in teams_per_grader.values()]) == n_teams team_grader = {t.team_id: None for t in teams_registrations.keys()} if from_assignment is not None: from_assignment_registrations = get_teams_registrations(course, from_assignment) common_teams = [t for t in teams if teams_registrations.has_key(t) and from_assignment_registrations.has_key(t)] for t in common_teams: registration = from_assignment_registrations[t] # try to assign the same grader that would grade the same team's other assignment grader = registration.grader if grader is not None and teams_per_grader[grader.user.username] > 0: team_grader[t.team_id] = grader.user.username teams_per_grader[grader.user.username] -= 1 teams_per_grader_assigned[grader.user.username] += 1 if avoid_assignment is not None: avoid_assignment_registrations = get_teams_registrations(course, avoid_assignment) if not reset: for t in teams: if teams_registrations[t].grader is None: team_grader[t.team_id] = None else: grader_id = teams_registrations[t].grader.user.username team_grader[t.team_id] = grader_id teams_per_grader[grader_id] = max(0, teams_per_grader[grader_id] - 1) teams_per_grader_assigned[grader_id] += 1 not_ready_for_grading = [] ta_avoid = {} graders_cycle = itertools.cycle(graders) for team, registration in teams_registrations.items(): if team_grader[team.team_id] is not None: continue if not registration.is_ready_for_grading(): not_ready_for_grading.append(team.team_id) continue for g in graders_cycle: if teams_per_grader[g.user.username] == 0: continue else: if avoid_assignment is not None: avoid_registration = ta_avoid.setdefault(team.team_id, avoid_assignment_registrations.get(team)) if avoid_registration is not None and avoid_registration.grader.user.username == grader.user.username: continue valid = True for tm in team.get_team_members(): conflicts = g.get_conflicts() if tm.username in conflicts: valid = False break if valid: team_grader[team.team_id] = g.user.username teams_per_grader[g.user.username] -= 1 teams_per_grader_assigned[g.user.username] += 1 break for team, registration in teams_registrations.items(): if team_grader[team.team_id] is None: if team.team_id not in not_ready_for_grading: print "Team %s has no grader" % (team.team_id) else: print "Team %s's submission isn't ready for grading yet" % (team.team_id) else: if not dry_run: if registration.grader_username != team_grader[team.team_id]: registration.grader_username = team_grader[team.team_id] else: print "%s: %s" % (team.team_id, team_grader[team.team_id]) print for grader_id, assigned in teams_per_grader_assigned.items(): if teams_per_grader[grader_id] != 0: print grader_id, assigned, "(still needs to be assigned %i more assignments)" % (teams_per_grader[grader_id]) else: print grader_id, assigned return CHISUBMIT_SUCCESS
def instructor_team_pull_repos(ctx, course, directory, assignment, only_ready_for_grading, reset, only): if only_ready_for_grading and assignment is None: print( "--only-ready-for-grading can only be used with --assignment option" ) ctx.exit(CHISUBMIT_FAIL) if assignment is not None: assignment = get_assignment_or_exit(ctx, course, assignment) conn = create_connection(course, ctx.obj['config']) directory = os.path.expanduser(directory) if not os.path.exists(directory): os.makedirs(directory) if assignment is None: teams = sorted(course.get_teams(), key=operator.attrgetter("team_id")) else: teams_registrations = get_teams_registrations(course, assignment, only=only) teams = sorted( [t for t in list(teams_registrations.keys()) if t.active], key=operator.attrgetter("team_id")) max_len = max([len(t.team_id) for t in teams]) for team in teams: team_dir = "%s/%s" % (directory, team.team_id) team_git_url = conn.get_repository_git_url(course, team) if only_ready_for_grading: registration = teams_registrations[team] if not registration.is_ready_for_grading( ) and only_ready_for_grading: print("%-*s SKIPPING (not ready for grading)" % (max_len, team.team_id)) continue try: msg = "" if not os.path.exists(team_dir): r = LocalGitRepo.create_repo(team_dir, clone_from_url=team_git_url) msg = "Cloned repo" else: r = LocalGitRepo(team_dir) if reset: r.fetch("origin") r.reset_branch("origin", "master") msg = "Reset to match origin/master" else: if r.repo.is_dirty(): print( "%-*s ERROR: Cannot pull. Local repository has unstaged changes." % (max_len, team.team_id)) continue r.checkout_branch("master") r.pull("origin", "master") msg = "Pulled latest changes" if only_ready_for_grading: r.checkout_commit(registration.final_submission.commit_sha) msg += " and checked out commit %s" % ( registration.final_submission.commit_sha) print("%-*s %s" % (max_len, team.team_id, msg)) except ChisubmitException as ce: print( "%-*s ERROR: Could not checkout or pull master branch (%s)" % (max_len, team.team_id, ce)) except GitCommandError as gce: print("%-*s ERROR: Could not checkout or pull master branch" % (max_len, team.team_id)) print(gce) except InvalidGitRepositoryError as igre: print( "%-*s ERROR: Directory %s exists but does not contain a valid git repository" % (max_len, team.team_id, team_dir)) except Exception as e: print( "%-*s ERROR: Unexpected exception when trying to checkout/pull" % (max_len, team.team_id)) raise return CHISUBMIT_SUCCESS
def instructor_grading_show_grading_status(ctx, course, assignment_id, by_grader, include_diff_urls, use_stored_grades): assignment = get_assignment_or_exit(ctx, course, assignment_id, include_rubric=True) rubric_components = assignment.get_rubric_components() teams_registrations = get_teams_registrations(course, assignment, include_grades=True) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) team_status = [] graders = set() for team in teams: registration = teams_registrations[team] if registration.grader is None: grader_str = "<no grader assigned>" else: grader_str = registration.grader.user.username graders.add(grader_str) grading_status = None diff_url = "" if use_stored_grades: grades = registration.get_grades() total_grade = registration.get_total_grade() graded_rc_ids = [g.rubric_component_id for g in grades] else: total_grade = 0.0 repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: grading_status = "NO GRADING REPO" else: rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): grading_status = "NOT GRADED - No rubric" else: try: rubric = RubricFile.from_file(open(rubricfile), assignment) graded_rc_ids = [ rc.id for rc in rubric_components if rubric.points[rc.description] is not None ] for rc in rubric_components: grade = rubric.points[rc.description] if grade is not None: total_grade += grade if rubric.penalties is not None: for p in rubric.penalties.values(): total_grade += p if rubric.bonuses is not None: for p in rubric.bonuses.values(): total_grade += p except ChisubmitRubricException, cre: grading_status = "ERROR: Rubric does not validate (%s)" % ( cre.message) if grading_status is None: has_some = False has_all = True for rc in rubric_components: if rc.id in graded_rc_ids: has_some = True else: has_all = False if not has_some: grading_status = "NOT GRADED" elif has_all: grading_status = "GRADED" else: grading_status = "PARTIALLY GRADED" if include_diff_urls and has_some: commit_sha = registration.final_submission.commit_sha[:8] diff_url = "https://mit.cs.uchicago.edu/%s-staging/%s/compare/%s...%s-grading" % ( course.course_id, team.team_id, commit_sha, assignment.assignment_id) team_status.append( (team.team_id, grader_str, total_grade, diff_url, grading_status))
def instructor_grading_create_grading_repos(ctx, course, assignment_id, all_teams, only, master): assignment = get_assignment_or_exit(ctx, course, assignment_id) teams_registrations = get_teams_registrations(course, assignment, only = only, only_ready_for_grading=not all_teams) if len(teams_registrations) == 0: print("There are no grading repos to create.") ctx.exit(CHISUBMIT_FAIL) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print(("%40s -- Creating grading repo... " % team.team_id), end=' ') try: repo = GradingGitRepo.create_grading_repo(ctx.obj['config'], course, team, registration, staging_only = not master) repo.sync() except GitCommandError as gce: print(gce) if registration.final_submission is not None: if repo.has_grading_branch_staging(): if not registration.grading_started: print("ERROR: This repo has a grading branch, but is not marked as ready for grading.") else: gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print("done") else: if master: repo.create_grading_branch() registration.grading_started = True print("done (and created grading branch)") else: print("done (warning: could not pull grading branch; it does not exist)") else: if registration.grading_started: print("ERROR: This team has not submitted this assignment, but the repo is marked as ready for grading.") else: print("done (note: has not submitted yet)") else: print(("%40s -- Updating grading repo... " % team.team_id), end=' ') if repo.has_grading_branch_staging(): if not registration.grading_started: print("ERROR: This repo has a grading branch, but is not marked as ready for grading.") else: try: gradingrepo_pull_grading_branch(ctx.obj['config'], course, team, registration) print("done (pulled latest grading branch)") except GitCommandError as gce: print(gce) elif repo.has_grading_branch(): print("nothing to update (grading branch is not in staging)") elif registration.final_submission is not None and master: try: repo.create_grading_branch() if not registration.grading_started: registration.grading_started = True print("done (created missing grading branch)") except GitCommandError as gce: print(gce) else: print("nothing to update (there is no grading branch)") return CHISUBMIT_SUCCESS
def instructor_team_pull_repos(ctx, course, directory, assignment, only_ready_for_grading, reset, only): if only_ready_for_grading and assignment is None: print("--only-ready-for-grading can only be used with --assignment option") ctx.exit(CHISUBMIT_FAIL) if assignment is not None: assignment = get_assignment_or_exit(ctx, course, assignment) conn = create_connection(course, ctx.obj['config']) directory = os.path.expanduser(directory) if not os.path.exists(directory): os.makedirs(directory) if assignment is None: teams = sorted(course.get_teams(), key = operator.attrgetter("team_id")) else: teams_registrations = get_teams_registrations(course, assignment, only = only) teams = sorted([t for t in list(teams_registrations.keys()) if t.active], key = operator.attrgetter("team_id")) max_len = max([len(t.team_id) for t in teams]) for team in teams: team_dir = "%s/%s" % (directory, team.team_id) team_git_url = conn.get_repository_git_url(course, team) if only_ready_for_grading: registration = teams_registrations[team] if not registration.is_ready_for_grading() and only_ready_for_grading: print("%-*s SKIPPING (not ready for grading)" % (max_len, team.team_id)) continue try: msg = "" if not os.path.exists(team_dir): r = LocalGitRepo.create_repo(team_dir, clone_from_url=team_git_url) msg = "Cloned repo" else: r = LocalGitRepo(team_dir) if reset: r.fetch("origin") r.reset_branch("origin", "master") msg = "Reset to match origin/master" else: if r.repo.is_dirty(): print("%-*s ERROR: Cannot pull. Local repository has unstaged changes." % (max_len, team.team_id)) continue r.checkout_branch("master") r.pull("origin", "master") msg = "Pulled latest changes" if only_ready_for_grading: r.checkout_commit(registration.final_submission.commit_sha) msg += " and checked out commit %s" % (registration.final_submission.commit_sha) print("%-*s %s" % (max_len, team.team_id, msg)) except ChisubmitException as ce: print("%-*s ERROR: Could not checkout or pull master branch (%s)" % (max_len, team.team_id, ce)) except GitCommandError as gce: print("%-*s ERROR: Could not checkout or pull master branch" % (max_len, team.team_id)) print(gce) except InvalidGitRepositoryError as igre: print("%-*s ERROR: Directory %s exists but does not contain a valid git repository" % (max_len, team.team_id, team_dir)) except Exception as e: print("%-*s ERROR: Unexpected exception when trying to checkout/pull" % (max_len, team.team_id)) raise return CHISUBMIT_SUCCESS
def instructor_grading_collect_rubrics(ctx, course, assignment_id, dry_run, only, grader_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) if grader_id is not None: grader = get_grader_or_exit(ctx, course, grader_id) else: grader = None rcs = assignment.get_rubric_components() teams_registrations = get_teams_registrations(course, assignment, grader=grader, only=only) teams = sorted(teams_registrations.keys(), key=operator.attrgetter("team_id")) for team in teams: registration = teams_registrations[team] repo = GradingGitRepo.get_grading_repo(ctx.obj['config'], course, team, registration) if repo is None: print "Repository for %s does not exist" % (team.team_id) continue rubricfile = repo.repo_path + "/%s.rubric.txt" % assignment.assignment_id if not os.path.exists(rubricfile): print "Repository for %s does not have a rubric for assignment %s" % (team.team_id, assignment.assignment_id) continue try: rubric = RubricFile.from_file(open(rubricfile), assignment) except ChisubmitRubricException, cre: print "ERROR: Rubric for %s does not validate (%s)" % (team.team_id, cre.message) continue points = [] for rc in rcs: grade = rubric.points[rc.description] if grade is None: points.append(0.0) else: if not dry_run: registration.set_grade(rc, grade) points.append(grade) adjustments = {} total_penalties = 0.0 total_bonuses = 0.0 if rubric.penalties is not None: for desc, p in rubric.penalties.items(): adjustments[desc] = p total_penalties += p if rubric.bonuses is not None: for desc, p in rubric.bonuses.items(): adjustments[desc] = p total_bonuses += p if not dry_run: registration.grade_adjustments = adjustments if ctx.obj["verbose"]: print team.team_id print "Points Obtained: %s" % points print "Penalties: %.2f" % total_penalties print "Bonuses: %.2f" % total_bonuses if not dry_run: total_grade = registration.get_total_grade() else: total_grade = sum(points) + total_penalties + total_bonuses if ctx.obj["verbose"]: print "TOTAL: %.2f" % total_grade print else: print "%-40s %.2f" % (team.team_id, total_grade)
def instructor_gradescope_upload(ctx, course, assignment_id, dry_run, only, limit): config = ctx.obj['config'] gradescope_api_key = config.get_gradescope_api_key() if gradescope_api_key is None: print("No API key found for Gradescope") ctx.exit(CHISUBMIT_FAIL) if course.gradescope_id is None: print("{} does not have its 'gradescope_id' attribute set".format( course.course_id)) ctx.exit(CHISUBMIT_FAIL) assignment = get_assignment_or_exit(ctx, course, assignment_id) if assignment.gradescope_id is None: print("Assignment {} does not have its 'gradescope_id' attribute set". format(assignment_id)) ctx.exit(CHISUBMIT_FAIL) if assignment.expected_files is None: print("Assignment {} does not have a list of expected files".format( assignment_id)) ctx.exit(CHISUBMIT_FAIL) teams_registrations = get_teams_registrations(course, assignment, only=only) teams = sorted(list(teams_registrations.keys()), key=operator.attrgetter("team_id")) n_submissions = 0 for team_obj in teams: registration_obj = teams_registrations[team_obj] if registration_obj.gradescope_uploaded: print("[SKIPPED] {} has already been uploaded".format( team_obj.team_id)) continue repo = GradingGitRepo.get_grading_repo(config, course, team_obj, registration_obj) if repo is None: print("[SKIPPED] {} does not have a grading repo".format( team_obj.team_id)) continue files = [] missing_files = [] for pattern in assignment.expected_files.split(","): abs_pattern = "{}/{}".format(repo.repo_path, pattern) matching_files = glob.glob(abs_pattern) if len(matching_files) == 0: missing_files.append(pattern) continue for f in matching_files: files.append((os.path.basename(f), f)) if len(missing_files) > 0: print("[WARNING] {} does not have required file(s): {}".format( team_obj.team_id, " ".join(missing_files))) continue team_members = team_obj.get_team_members() if all(tm.student.dropped for tm in team_members): print("[SKIPPED] All students in {} have dropped the class".format( team_obj.team_id)) continue emails = [ "{}@uchicago.edu".format(tm.student.username) for tm in team_members ] if dry_run: print("[DRY RUN] upload_submission(..., {}, {}, '{}', {}".format( course.gradescope_id, assignment.gradescope_id, emails, files)) n_submissions += 1 else: try: submission_id = gradescope_upload_submission( gradescope_api_key, course.gradescope_id, assignment.gradescope_id, emails, files) print("[DONE]: Upload for {} (submission id: {})".format( team_obj.team_id, submission_id)) registration_obj.gradescope_uploaded = True n_submissions += 1 except GradescopeException: print("[ERROR] Could not upload submission for {}".format( team_obj.team_id)) if limit is not None and n_submissions >= limit: print("Reached submission limit ({})".format(limit)) break