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_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 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_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 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_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_assignment_register(ctx, course, assignment_id, student, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if len(student) < assignment.min_students or len( student) > assignment.max_students: print( "You specified %i students, but this assignment requires teams with at least %i students and at most %i students" % (len(student), assignment.min_students, assignment.max_students)) print() print("Are you sure you want to proceed? (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) assignment.register(students=student) print("Registration successful.") return CHISUBMIT_SUCCESS
def instructor_grading_set_grade(ctx, course, team_id, assignment_id, rubric_component_description, points): assignment = get_assignment_or_exit(ctx, course, assignment_id) team = get_team_or_exit(ctx, course, team_id) registration = get_assignment_registration_or_exit(ctx, team, assignment_id) rubric_components = assignment.get_rubric_components() rubric_component = [ rc for rc in rubric_components if rc.description == rubric_component_description ] if len(rubric_component) == 0: print("No such rubric component in %s: %s" % (assignment_id, rubric_component_description)) ctx.exit(CHISUBMIT_FAIL) elif len(rubric_component) > 1: print( "ERROR: Server returned more than one rubric component for '%s' in %s" % (rubric_component_description, assignment_id)) print( " This should not happen. Please contact the chisubmit administrator." ) ctx.exit(CHISUBMIT_FAIL) else: rc = rubric_component[0] try: registration.set_grade(rc, points) ctx.exit(CHISUBMIT_SUCCESS) except ValueError as ve: print(ve) ctx.exit(CHISUBMIT_FAIL)
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_assignment_add_rubric(ctx, course, assignment_id, rubric_file, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) try: rubric = RubricFile.from_file(rubric_file) except ChisubmitRubricException as cre: print("ERROR: Rubric does not validate (%s)" % (cre)) ctx.exit(CHISUBMIT_FAIL) rubric_components = assignment.get_rubric_components() if len(rubric_components) > 0: print("This assignment already has a rubric. If you load this") print("new rubric, the old one will be REMOVED. If grading of") print("this assignment has already begun, doing this may break") print("existing rubric files completed by the graders.") print() if not ask_yesno(yes=yes): ctx.exit(CHISUBMIT_FAIL) print() for rc in rubric_components: rc.delete() order = 10 for rc in rubric.rubric_components: points = rubric.points_possible[rc] assignment.create_rubric_component(rc, points, order) order += 10 return CHISUBMIT_SUCCESS
def instructor_grading_set_grade(ctx, course, team_id, assignment_id, rubric_component_description, points): assignment = get_assignment_or_exit(ctx, course, assignment_id) team = get_team_or_exit(ctx, course, team_id) registration = get_assignment_registration_or_exit(ctx, team, assignment_id) rubric_components = assignment.get_rubric_components() rubric_component = [rc for rc in rubric_components if rc.description == rubric_component_description] if len(rubric_component) == 0: print "No such rubric component in %s: %s" % (assignment_id, rubric_component_description) ctx.exit(CHISUBMIT_FAIL) elif len(rubric_component) > 1: print "ERROR: Server returned more than one rubric component for '%s' in %s" % (rubric_component_description, assignment_id) print " This should not happen. Please contact the chisubmit administrator." ctx.exit(CHISUBMIT_FAIL) else: rc = rubric_component[0] try: registration.set_grade(rc, points) ctx.exit(CHISUBMIT_SUCCESS) except ValueError, ve: print ve.message ctx.exit(CHISUBMIT_FAIL)
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 student_assignment_register(ctx, course, assignment_id, partner): assignment = get_assignment_or_exit(ctx, course, assignment_id) user = ctx.obj["client"].get_user() try: course.get_instructor(user.username) # If get_instructor doesn't raise an exception, then the user # is an instructor, and we don't include the user in the list # of partners. r = assignment.register(students=partner) except UnknownObjectException: # Otherwise, we include the current user if user.username in partner: print( "You specified your own username in --partner. You should use this" ) print("option to specify your partners, not including yourself.") ctx.exit(CHISUBMIT_FAIL) r = assignment.register(students=partner + (user.username, )) print("Your registration for %s (%s) is complete." % (r.registration.assignment.assignment_id, r.registration.assignment.name)) if len(r.team_members) > 1: some_unconfirmed = False print() print("Your team name is '%s'." % r.team.team_id) print() print("The team is composed of the following students:") print() for tm in r.team_members: if tm.confirmed: conf = "" else: conf = "UNCONFIRMED" some_unconfirmed = True print(" - %s, %s (%s) %s" % (tm.student.user.last_name, tm.student.user.first_name, tm.student.user.username, conf)) if some_unconfirmed: print() print( "Note: Some students have not yet confirmed that they are part of" ) print( " this team. To confirm they are part of this team, they just" ) print( " need to register as a team themselves (using this same") print(" command, and listing the same team members).") return CHISUBMIT_SUCCESS
def instructor_assignment_stats(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) all_students = course.get_students() student_dict = {s.user.username: s.user for s in all_students if not s.dropped} students = set(student_dict.keys()) dropped = set([s.user.username for s in all_students if s.dropped]) teams_unconfirmed = [] nteams = 0 nstudents = len(student_dict) nstudents_assignment = 0 nsubmissions = 0 nstudents_submitted = 0 unsubmitted = [] teams = course.get_teams(include_students=True, include_assignments=True) for team in teams: try: registrations = team.get_assignment_registrations() registrations = [r for r in registrations if r.assignment_id == assignment_id] if len(registrations) == 1: registration = registrations[0] else: continue except UnknownObjectException: continue unconfirmed = False includes_dropped = False team_members = team.get_team_members() for tm in team_members: if tm.username not in dropped: try: students.remove(tm.username) nstudents_assignment += 1 if not tm.confirmed: unconfirmed = True except KeyError, ke: print "WARNING: Student %s seems to be in more than one team (offending team: %s)" % (tm.username, team.team_id) else: includes_dropped = True if not includes_dropped: nteams += 1 if registration.final_submission is not None: nsubmissions += 1 nstudents_submitted += len(team_members) else: unsubmitted.append(team) if unconfirmed: teams_unconfirmed.append(team)
def student_assignment_cancel_registration(ctx, course, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) # Get registration team, registration = get_team_registration_from_user(ctx, course, assignment) team_members = team.get_team_members() if len(team_members) == 1: student = team_members[0].student individual = True print("You (%s %s) currently have an INDIVIDUAL registration for %s (%s)" % (student.user.first_name, student.user.last_name, assignment.assignment_id, assignment.name)) else: students = [tm.student for tm in team_members] individual = False print("You currently have a TEAM registration for %s (%s)." % (assignment.assignment_id, assignment.name)) print() print("Team %s has the following students:" % team.team_id) for s in students: print(" - %s %s" % (s.user.first_name, s.user.last_name)) print() if registration.final_submission is not None: if individual: print("You have already made a submission for %s." % assignment_id) else: print("Team %s has already made a submission for assignment %s." % (team.team_id, assignment_id)) print() print("Please cancel your submission first, and then try canceling your registration.") ctx.exit(CHISUBMIT_FAIL) if individual: print("Are you sure you want to cancel your registration for this assignment? (y/n): ", end=' ') else: print("Are you sure you want to cancel this team's registration for the assignment? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): try: registration.cancel() print() print("Your registration has been cancelled.") ctx.exit(CHISUBMIT_SUCCESS) except BadRequestException as bre: response_data = bre.json print("ERROR: Your registration cannot be cancelled. The server reported the following:") print() bre.print_errors() ctx.exit(CHISUBMIT_FAIL)
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 student_assignment_register(ctx, course, assignment_id, partner): assignment = get_assignment_or_exit(ctx, course, assignment_id) user = ctx.obj["client"].get_user() try: course.get_instructor(user.username) # If get_instructor doesn't raise an exception, then the user # is an instructor, and we don't include the user in the list # of partners. r = assignment.register(students = partner) except UnknownObjectException: # Otherwise, we include the current user if user.username in partner: print("You specified your own username in --partner. You should use this") print("option to specify your partners, not including yourself.") ctx.exit(CHISUBMIT_FAIL) r = assignment.register(students = partner + (user.username,)) print("Your registration for %s (%s) is complete." % (r.registration.assignment.assignment_id, r.registration.assignment.name)) if len(r.team_members) > 1: some_unconfirmed = False print() print("Your team name is '%s'." % r.team.team_id) print() print("The team is composed of the following students:") print() for tm in r.team_members: if tm.confirmed: conf = "" else: conf = "UNCONFIRMED" some_unconfirmed = True print(" - %s, %s (%s) %s" % (tm.student.user.last_name, tm.student.user.first_name, tm.student.user.username, conf)) if some_unconfirmed: print() print("Note: Some students have not yet confirmed that they are part of") print(" this team. To confirm they are part of this team, they just") print(" need to register as a team themselves (using this same") print(" command, and listing the same team members).") 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): ", 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_assignment_register(ctx, course, assignment_id, student, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) if len(student) < assignment.min_students or len(student) > assignment.max_students: print("You specified %i students, but this assignment requires teams with at least %i students and at most %i students" % (len(student), assignment.min_students, assignment.max_students)) print() print("Are you sure you want to proceed? (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) assignment.register(students = student) print("Registration successful.") return CHISUBMIT_SUCCESS
def shared_team_list(ctx, course, ids, assignment, include_inactive): teams = course.get_teams() teams.sort(key=operator.attrgetter("team_id")) assignment_id = assignment if assignment_id is not None: assignment = get_assignment_or_exit(ctx, course, assignment_id) else: assignment = None for team in teams: registrations = team.get_assignment_registrations() if assignment is not None: if not assignment.assignment_id in [r.assignment.assignment_id for r in registrations]: continue if not (team.active or include_inactive): continue if ids: print team.team_id else: team_members = team.get_team_members() if len(team_members) == 0: students = "No students" else: students = "Students: " + ",".join([tm.student.user.username for tm in team_members]) if len(registrations) == 0: assignments = "No assignments" else: assignments = "Assignments: " + ",".join([r.assignment.assignment_id for r in registrations]) fields = [team.team_id, students, assignments] print " ".join(fields) 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_assignment_update_rubric(ctx, course, assignment_id, rubric_component, description, points, add, edit, remove, up, down, yes): num_cmds = len([x for x in [add, edit, remove, up, down] if x is True]) if num_cmds > 1: print( "You can only specify one of the following: --add / --edit / --remove / --up / --down" ) ctx.exit(CHISUBMIT_FAIL) if num_cmds == 0: print( "You must specify one of the following: --add / --edit / --remove / --up / --down" ) ctx.exit(CHISUBMIT_FAIL) if add and points is None: print("The --add option requires the --points option") ctx.exit(CHISUBMIT_FAIL) if edit and not (description or points): print( "The --edit option requires the --description option or the --points option (or both)" ) ctx.exit(CHISUBMIT_FAIL) if (remove or up or down) and points is not None: print("The --points option cannot be used with --remove/--up/--down") ctx.exit(CHISUBMIT_FAIL) if (remove or up or down) and points is not None: print("The --points option cannot be used with --remove/--up/--down") ctx.exit(CHISUBMIT_FAIL) assignment = get_assignment_or_exit(ctx, course, assignment_id) rubric_components = assignment.get_rubric_components() rcs = [(i, rc) for i, rc in enumerate(rubric_components) if rc.description == rubric_component] if len(rcs) == 0: if not add: print("No such rubric component: %s" % rubric_component) ctx.exit(CHISUBMIT_FAIL) else: i, rc = None, None elif len(rcs) > 1: print("Multiple rubric components with this name: %s" % rubric_component) print("(this should not happen)") ctx.exit(CHISUBMIT_FAIL) else: i, rc = rcs[0] if add: if rc is not None: print("There is already a rubric component with this name: %s" % rubric_component) ctx.exit(CHISUBMIT_FAIL) if len(rubric_components) == 0: last_order = 0 else: last_order = rubric_components[-1].order assignment.create_rubric_component(rubric_component, points, last_order + 10) elif edit: if description is not None: print("If grading of this assignment has begun, changing the") print("description of a rubric component may break existing") print("rubric files completed by the graders.") print() if not ask_yesno(yes=yes): ctx.exit(CHISUBMIT_FAIL) print() rc.description = description if points is not None: rc.points = points elif remove: print("If grading of this assignment has begun, removing") print("a rubric component may break existing rubric files") print("completed by the graders.") print() if not ask_yesno(yes=yes): ctx.exit(CHISUBMIT_FAIL) print() rc.delete() elif up: if i - 1 >= 0: other_rc = rubric_components[i - 1] other_rc_order = rubric_components[i - 1].order other_rc.order = rc.order rc.order = other_rc_order elif down: if i + 1 < len(rubric_components): other_rc = rubric_components[i + 1] other_rc_order = rubric_components[i + 1].order other_rc.order = rc.order rc.order = other_rc_order
def student_assignment_cancel_submit(ctx, course, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) # Determine team for this assignment team, registration = get_team_registration_from_user(ctx, course, assignment) team_members = team.get_team_members() if len(team_members) == 1: individual = True else: individual = False if registration.final_submission is None: if individual: print("You have not made a submission for assignment %s," % assignment_id) else: print("Team %s has not made a submission for assignment %s," % (team.team_id, assignment_id)) print("so there is nothing to cancel.") ctx.exit(CHISUBMIT_FAIL) if registration.grading_started: print("You cannot cancel this submission.") print("You made a submission and it has already been sent to the graders for grading.") print("Please contact an instructor if you wish to amend your submission.") ctx.exit(CHISUBMIT_FAIL) conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) submission_commit = conn.get_commit(course, team, registration.final_submission.commit_sha) print() print("This is your existing submission for assignment %s:" % assignment_id) print() if submission_commit is None: print("WARNING: Previously submitted commit '%s' is not in the repository!" % registration.final_submission.commit_sha) else: print_commit(submission_commit) print() print("Are you sure you want to cancel this submission? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): registration.final_submission_id = None # TODO: Can't do this until GitLab supports updating tags # # message = "Extensions: %i\n" % extensions_requested # if submission_tag is None: # conn.create_submission_tag(course, team, tag_name, message, commit.sha) # else: # conn.update_submission_tag(course, team, tag_name, message, commit.sha) print() print("Your submission has been cancelled.")
def instructor_assignment_update_rubric(ctx, course, assignment_id, rubric_component, description, points, add, edit, remove, up, down, yes): num_cmds = len([x for x in [add,edit,remove,up,down] if x is True]) if num_cmds > 1: print("You can only specify one of the following: --add / --edit / --remove / --up / --down") ctx.exit(CHISUBMIT_FAIL) if num_cmds == 0: print("You must specify one of the following: --add / --edit / --remove / --up / --down") ctx.exit(CHISUBMIT_FAIL) if add and points is None: print("The --add option requires the --points option") ctx.exit(CHISUBMIT_FAIL) if edit and not (description or points): print("The --edit option requires the --description option or the --points option (or both)") ctx.exit(CHISUBMIT_FAIL) if (remove or up or down) and points is not None: print("The --points option cannot be used with --remove/--up/--down") ctx.exit(CHISUBMIT_FAIL) if (remove or up or down) and points is not None: print("The --points option cannot be used with --remove/--up/--down") ctx.exit(CHISUBMIT_FAIL) assignment = get_assignment_or_exit(ctx, course, assignment_id) rubric_components = assignment.get_rubric_components() rcs = [(i, rc) for i, rc in enumerate(rubric_components) if rc.description == rubric_component] if len(rcs) == 0: if not add: print("No such rubric component: %s" % rubric_component) ctx.exit(CHISUBMIT_FAIL) else: i, rc = None, None elif len(rcs) > 1: print("Multiple rubric components with this name: %s" % rubric_component) print("(this should not happen)") ctx.exit(CHISUBMIT_FAIL) else: i, rc = rcs[0] if add: if rc is not None: print("There is already a rubric component with this name: %s" % rubric_component) ctx.exit(CHISUBMIT_FAIL) if len(rubric_components) == 0: last_order = 0 else: last_order = rubric_components[-1].order assignment.create_rubric_component(rubric_component, points, last_order + 10) elif edit: if description is not None: print("If grading of this assignment has begun, changing the") print("description of a rubric component may break existing") print("rubric files completed by the graders.") print() if not ask_yesno(yes=yes): ctx.exit(CHISUBMIT_FAIL) print() rc.description = description if points is not None: rc.points = points elif remove: print("If grading of this assignment has begun, removing") print("a rubric component may break existing rubric files") print("completed by the graders.") print() if not ask_yesno(yes=yes): ctx.exit(CHISUBMIT_FAIL) print() rc.delete() elif up: if i-1 >= 0: other_rc = rubric_components[i-1] other_rc_order = rubric_components[i-1].order other_rc.order = rc.order rc.order = other_rc_order elif down: if i+1 < len(rubric_components): other_rc = rubric_components[i+1] other_rc_order = rubric_components[i+1].order other_rc.order = rc.order rc.order = other_rc_order
def instructor_assignment_register(ctx, course, assignment_id, student): assignment = get_assignment_or_exit(ctx, course, assignment_id) assignment.register(students=student) return CHISUBMIT_SUCCESS
def student_assignment_submit(ctx, course, assignment_id, commit_sha, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) # Determine team for this assignment team, registration = get_team_registration_from_user(ctx, course, assignment) team_members = team.get_team_members() title = "SUBMISSION FOR ASSIGNMENT %s (%s)" % (assignment.assignment_id, assignment.name) print(title) print("-" * len(title)) print() if len(team_members) == 1: student = team_members[0].student individual = True print("This is an INDIVIDUAL submission for %s %s" % (student.user.first_name, student.user.last_name)) else: students = [tm.student for tm in team_members] individual = False print("This is a TEAM submission for team %s with the following students:" % team.team_id) for s in students: print(" - %s %s" % (s.user.first_name, s.user.last_name)) print() conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) if commit_sha is None: commit = conn.get_latest_commit(course, team) if commit is None: print("It seems there are no commits in your repository, so I cannot submit anything") ctx.exit(CHISUBMIT_FAIL) user_specified_commit = False else: commit = conn.get_commit(course, team, commit_sha) if commit is None: print("Commit %s does not exist in repository" % commit_sha) ctx.exit(CHISUBMIT_FAIL) user_specified_commit = True try: submit_response = registration.submit(commit.sha, dry_run = True) except BadRequestException as bre: response_data = bre.json if "extensions_needed" in response_data and "extensions_available" in response_data: extensions_needed = response_data["extensions_needed"] extensions_available = response_data["extensions_available"] deadline_utc = parse(response_data["deadline"]) submitted_at_utc = parse(response_data["submitted_at"]) deadline_local = convert_datetime_to_local(deadline_utc) submitted_at_local = convert_datetime_to_local(submitted_at_utc) if extensions_needed > extensions_available: msg1 = "You do not have enough extensions to submit this assignment." msg2 = "You would need %i extensions to submit this assignment at this " \ "time, but you only have %i left" % (extensions_needed, extensions_available) print() print(msg1) print() print(" Deadline (UTC): %s" % deadline_utc.isoformat(sep=" ")) print(" Now (UTC): %s" % submitted_at_utc.isoformat(sep=" ")) print() print(" Deadline (Local): %s" % deadline_local.isoformat(sep=" ")) print(" Now (Local): %s" % submitted_at_local.isoformat(sep=" ")) print() print(msg2) print() else: print("ERROR: Your submission cannot be completed. The server reported the following:") print() bre.print_errors() else: print("ERROR: Your submission cannot be completed. The server reported the following:") print() bre.print_errors() ctx.exit(CHISUBMIT_FAIL) if registration.final_submission is not None: prior_commit_sha = registration.final_submission.commit_sha prior_extensions_used = registration.final_submission.extensions_used prior_submitted_at_utc = registration.final_submission.submitted_at prior_submitted_at_local = convert_datetime_to_local(prior_submitted_at_utc) submission_commit = conn.get_commit(course, team, prior_commit_sha) if prior_commit_sha == commit.sha: print("You have already submitted assignment %s" % registration.assignment.assignment_id) print() print("You submitted the following commit on %s:" % prior_submitted_at_local) print() if submission_commit is None: print("WARNING: Previously submitted commit '%s' is not in the repository!" % prior_commit_sha) else: print_commit(submission_commit) print() if user_specified_commit: print("You are trying to submit the same commit again (%s)" % prior_commit_sha) print("If you want to re-submit, please specify a different commit.") else: print("The above commit is the latest commit in your repository.") print() print("If you were expecting to see a different commit, make sure you've pushed") print("your latest code to your repository.") ctx.exit(CHISUBMIT_FAIL) print("You have already submitted assignment %s" % registration.assignment.assignment_id) print() print("You submitted the following commit on %s:" % prior_submitted_at_local) print() if submission_commit is None: print("WARNING: Previously submitted commit '%s' is not in the repository!" % prior_commit_sha) else: print_commit(submission_commit) print() msg = "IF YOU CONTINUE, THE ABOVE SUBMISSION FOR %s (%s) WILL BE CANCELLED." % (registration.assignment.assignment_id, registration.assignment.name) print("!"*len(msg)) print(msg) print("!"*len(msg)) print() if not user_specified_commit: print("If you continue, your submission will instead point to the latest commit in your repository:") else: print("If you continue, your submission will instead point to the following commit:") else: if not user_specified_commit: print("The latest commit in your repository is the following:") else: print("The commit you are submitting is the following:") print() print_commit(commit) print() print("PLEASE VERIFY THIS IS THE EXACT COMMIT YOU WANT TO SUBMIT") print() if individual: print("You currently have %i extensions" % (submit_response.extensions_before)) else: print("Your team currently has %i extensions" % (submit_response.extensions_before)) print() if registration.final_submission is not None: print("You used %i extensions in your previous submission of this assignment." % prior_extensions_used) print("and you are going to use %i additional extensions now." % (submit_response.extensions_needed - prior_extensions_used)) else: print("You are going to use %i extensions on this submission." % submit_response.extensions_needed) print() print("You will have %i extensions left after this submission." % submit_response.extensions_after) print() if submit_response.in_grace_period: print("NOTE: You are submitting after the deadline, but the instructor has") print("allowed some extra time after the deadline for students to submit") print("without having to consume an extension.") print() print("Are you sure you want to continue? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): try: submit_response = registration.submit(commit.sha, dry_run=False) # TODO: Can't do this until GitLab supports updating tags # # message = "Extensions: %i\n" % extensions_requested # if submission_tag is None: # conn.create_submission_tag(course, team, tag_name, message, commit.sha) # else: # conn.update_submission_tag(course, team, tag_name, message, commit.sha) print() print("Your submission has been completed.") if submit_response.in_grace_period: print() print("Your submission was made during the deadline's grace period. This means") print("that, although your submission was technically made *after* the") print("deadline, we are counting it as if it had been made before the deadline.") print() print("In the future, you should not rely on the presence of this grace period!") print("Your instructor may choose not to use one in future assignments, or may") print("use a shorter grace period. Your instructor is also aware of what") print("submissions are made during the grace period; if you repeatedly submit") print("during the grace period, your instructor may charge you an extension") print("or refuse to accept your assignment if you are out of extensions.") return CHISUBMIT_SUCCESS except BadRequestException as bre: print() print("ERROR: Your submission was not completed. The server reported the following errors:") bre.print_errors() ctx.exit(CHISUBMIT_FAIL) else: print("Your submission has not been completed.") print() print("If you chose not to proceed because the above commit is not the one you wanted") print("to submit, make sure you've pushed your latest code to your repository before") print("attempting to submit again.") print() print("If you want to submit a different commit from your latest commit (e.g., an earlier") print("commit), you can use the --commit-sha option to specify a different commit.") ctx.exit(CHISUBMIT_FAIL)
def instructor_assignment_show_rubric(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) rubric = RubricFile.from_assignment(assignment) print(rubric.to_yaml())
def instructor_assignment_stats(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) all_students = course.get_students() student_dict = { s.user.username: s.user for s in all_students if not s.dropped } students = set(student_dict.keys()) dropped = set([s.user.username for s in all_students if s.dropped]) teams_unconfirmed = [] nteams = 0 nstudents = len(student_dict) nstudents_assignment = 0 nsubmissions = 0 nstudents_submitted = 0 unsubmitted = [] teams = course.get_teams(include_students=True, include_assignments=True) for team in teams: try: registrations = team.get_assignment_registrations() registrations = [ r for r in registrations if r.assignment_id == assignment_id ] if len(registrations) == 1: registration = registrations[0] else: continue except UnknownObjectException: continue unconfirmed = False includes_dropped = False team_members = team.get_team_members() for tm in team_members: if tm.username not in dropped: try: students.remove(tm.username) nstudents_assignment += 1 if not tm.confirmed: unconfirmed = True except KeyError, ke: print "WARNING: Student %s seems to be in more than one team (offending team: %s)" % ( tm.username, team.team_id) else: includes_dropped = True if not includes_dropped: nteams += 1 if registration.final_submission is not None: nsubmissions += 1 nstudents_submitted += len(team_members) else: unsubmitted.append(team) if unconfirmed: teams_unconfirmed.append(team)
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 student_assignment_cancel_registration(ctx, course, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) # Get registration team, registration = get_team_registration_from_user( ctx, course, assignment) team_members = team.get_team_members() if len(team_members) == 1: student = team_members[0].student individual = True print( "You (%s %s) currently have an INDIVIDUAL registration for %s (%s)" % (student.user.first_name, student.user.last_name, assignment.assignment_id, assignment.name)) else: students = [tm.student for tm in team_members] individual = False print("You currently have a TEAM registration for %s (%s)." % (assignment.assignment_id, assignment.name)) print() print("Team %s has the following students:" % team.team_id) for s in students: print(" - %s %s" % (s.user.first_name, s.user.last_name)) print() if registration.final_submission is not None: if individual: print("You have already made a submission for %s." % assignment_id) else: print("Team %s has already made a submission for assignment %s." % (team.team_id, assignment_id)) print() print( "Please cancel your submission first, and then try canceling your registration." ) ctx.exit(CHISUBMIT_FAIL) if individual: print( "Are you sure you want to cancel your registration for this assignment? (y/n): ", end=' ') else: print( "Are you sure you want to cancel this team's registration for the assignment? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): try: registration.cancel() print() print("Your registration has been cancelled.") ctx.exit(CHISUBMIT_SUCCESS) except BadRequestException as bre: response_data = bre.json print( "ERROR: Your registration cannot be cancelled. The server reported the following:" ) print() bre.print_errors() ctx.exit(CHISUBMIT_FAIL)
def instructor_assignment_stats(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) all_students = course.get_students() student_dict = { s.user.username: s.user for s in all_students if not s.dropped } students = set(student_dict.keys()) dropped = set([s.user.username for s in all_students if s.dropped]) teams_unconfirmed = [] nteams = 0 nstudents = len(student_dict) nstudents_assignment = 0 nsubmissions = 0 nstudents_submitted = 0 unsubmitted = [] teams = course.get_teams(include_students=True, include_assignments=True) for team in teams: try: registrations = team.get_assignment_registrations() registrations = [ r for r in registrations if r.assignment_id == assignment_id ] if len(registrations) == 1: registration = registrations[0] else: continue except UnknownObjectException: continue unconfirmed = False includes_dropped = False team_members = team.get_team_members() for tm in team_members: if tm.username not in dropped: try: students.remove(tm.username) nstudents_assignment += 1 if not tm.confirmed: unconfirmed = True except KeyError as ke: print( "WARNING: Student %s seems to be in more than one team (offending team: %s)" % (tm.username, team.team_id)) else: includes_dropped = True if not includes_dropped: nteams += 1 if registration.final_submission is not None: nsubmissions += 1 nstudents_submitted += len(team_members) else: unsubmitted.append(team) if unconfirmed: teams_unconfirmed.append(team) assert (nstudents == len(students) + nstudents_assignment) title = "Assignment '%s'" % (assignment.name) print(title) print("=" * len(title)) print() print("%i / %i students in %i teams have signed up for assignment %s" % (nstudents_assignment, nstudents, nteams, assignment.assignment_id)) print() print("%i / %i teams have submitted the assignment (%i students)" % (nsubmissions, nteams, nstudents_submitted)) if ctx.obj["verbose"]: if len(students) > 0: print() print("Students who have not yet signed up") print("-----------------------------------") not_signed_up = [student_dict[sid] for sid in students] not_signed_up.sort(key=operator.attrgetter("last_name")) for s in not_signed_up: print("%s, %s <%s>" % (s.last_name, s.first_name, s.email)) if len(unsubmitted) > 0: print() print("Teams that have not submitted") print("-----------------------------") for t in unsubmitted: print(t.team_id) return CHISUBMIT_SUCCESS
def instructor_assignment_cancel_submit(ctx, course, team_id, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) team = get_team_or_exit(ctx, course, team_id) registration = get_assignment_registration_or_exit( ctx, team, assignment.assignment_id) if registration.final_submission is None: print("Team %s has not made a submission for assignment %s," % (team.team_id, assignment_id)) print("so there is nothing to cancel.") ctx.exit(CHISUBMIT_FAIL) if registration.grader_username is not None: print("This submission has already been assigned a grader (%s)" % registration.grader_username) print( "Make sure the grader has been notified to discard this submission." ) print( "You must also remove the existing grading branch from the staging server." ) print("Are you sure you want to proceed? (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) else: print() if is_submission_ready_for_grading( assignment_deadline=registration.assignment.deadline, submission_date=registration.final_submission.submitted_at, extensions_used=registration.final_submission.extensions_used): print( "WARNING: You are canceling a submission that is ready for grading!" ) conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) submission_commit = conn.get_commit( course, team, registration.final_submission.commit_sha) print() print("This is the existing submission for assignment %s:" % assignment_id) print() if submission_commit is None: print( "WARNING: Previously submitted commit '%s' is not in the repository!" % registration.final_submission.commit_sha) else: print_commit(submission_commit) print() print("Are you sure you want to cancel this submission? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): registration.final_submission_id = None registration.grader_username = None registration.grading_started = False print() print("The submission has been cancelled.")
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_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 student_assignment_cancel_submit(ctx, course, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) # Determine team for this assignment team, registration = get_team_registration_from_user( ctx, course, assignment) team_members = team.get_team_members() if len(team_members) == 1: individual = True else: individual = False if registration.final_submission is None: if individual: print("You have not made a submission for assignment %s," % assignment_id) else: print("Team %s has not made a submission for assignment %s," % (team.team_id, assignment_id)) print("so there is nothing to cancel.") ctx.exit(CHISUBMIT_FAIL) if registration.grading_started: print("You cannot cancel this submission.") print( "You made a submission and it has already been sent to the graders for grading." ) print( "Please contact an instructor if you wish to amend your submission." ) ctx.exit(CHISUBMIT_FAIL) conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) submission_commit = conn.get_commit( course, team, registration.final_submission.commit_sha) print() print("This is your existing submission for assignment %s:" % assignment_id) print() if submission_commit is None: print( "WARNING: Previously submitted commit '%s' is not in the repository!" % registration.final_submission.commit_sha) else: print_commit(submission_commit) print() print("Are you sure you want to cancel this submission? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): registration.final_submission_id = None # TODO: Can't do this until GitLab supports updating tags # # message = "Extensions: %i\n" % extensions_requested # if submission_tag is None: # conn.create_submission_tag(course, team, tag_name, message, commit.sha) # else: # conn.update_submission_tag(course, team, tag_name, message, commit.sha) print() print("Your submission has been cancelled.")
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(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 shared_assignment_set_attribute(ctx, course, assignment_id, attr_name, attr_value): assignment = get_assignment_or_exit(ctx, course, assignment_id) api_obj_set_attribute(ctx, assignment, attr_name, attr_value)
def instructor_assignment_add_rubric_component(ctx, course, assignment_id, description, points): assignment = get_assignment_or_exit(ctx, course, assignment_id) assignment.create_rubric_component(description, points) 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_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_assignment_stats(ctx, course, assignment_id): assignment = get_assignment_or_exit(ctx, course, assignment_id) all_students = course.get_students() student_dict = {s.user.username: s.user for s in all_students if not s.dropped} students = set(student_dict.keys()) dropped = set([s.user.username for s in all_students if s.dropped]) teams_unconfirmed = [] nteams = 0 nstudents = len(student_dict) nstudents_assignment = 0 nsubmissions = 0 nstudents_submitted = 0 unsubmitted = [] teams = course.get_teams(include_students=True, include_assignments=True) for team in teams: try: registrations = team.get_assignment_registrations() registrations = [r for r in registrations if r.assignment_id == assignment_id] if len(registrations) == 1: registration = registrations[0] else: continue except UnknownObjectException: continue unconfirmed = False includes_dropped = False team_members = team.get_team_members() for tm in team_members: if tm.username not in dropped: try: students.remove(tm.username) nstudents_assignment += 1 if not tm.confirmed: unconfirmed = True except KeyError as ke: print("WARNING: Student %s seems to be in more than one team (offending team: %s)" % (tm.username, team.team_id)) else: includes_dropped = True if not includes_dropped: nteams += 1 if registration.final_submission is not None: nsubmissions += 1 nstudents_submitted += len(team_members) else: unsubmitted.append(team) if unconfirmed: teams_unconfirmed.append(team) assert(nstudents == len(students) + nstudents_assignment) title = "Assignment '%s'" % (assignment.name) print(title) print("=" * len(title)) print() print("%i / %i students in %i teams have signed up for assignment %s" % (nstudents_assignment, nstudents, nteams, assignment.assignment_id)) print() print("%i / %i teams have submitted the assignment (%i students)" % (nsubmissions, nteams, nstudents_submitted)) if ctx.obj["verbose"]: if len(students) > 0: print() print("Students who have not yet signed up") print("-----------------------------------") not_signed_up = [student_dict[sid] for sid in students] not_signed_up.sort(key=operator.attrgetter("last_name")) for s in not_signed_up: print("%s, %s <%s>" % (s.last_name, s.first_name, s.email)) if len(unsubmitted) > 0: print() print("Teams that have not submitted") print("-----------------------------") for t in unsubmitted: print(t.team_id) 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: 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_assignment_cancel_submit(ctx, course, team_id, assignment_id, yes): assignment = get_assignment_or_exit(ctx, course, assignment_id) team = get_team_or_exit(ctx, course, team_id) registration = get_assignment_registration_or_exit(ctx, team, assignment.assignment_id) if registration.final_submission is None: print("Team %s has not made a submission for assignment %s," % (team.team_id, assignment_id)) print("so there is nothing to cancel.") ctx.exit(CHISUBMIT_FAIL) if registration.grader_username is not None: print("This submission has already been assigned a grader (%s)" % registration.grader_username) print("Make sure the grader has been notified to discard this submission.") print("You must also remove the existing grading branch from the staging server.") print("Are you sure you want to proceed? (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) else: print() if is_submission_ready_for_grading(assignment_deadline=registration.assignment.deadline, submission_date=registration.final_submission.submitted_at, extensions_used=registration.final_submission.extensions_used): print("WARNING: You are canceling a submission that is ready for grading!") conn = create_connection(course, ctx.obj['config']) if conn is None: print("Could not connect to git server.") ctx.exit(CHISUBMIT_FAIL) submission_commit = conn.get_commit(course, team, registration.final_submission.commit_sha) print() print("This is the existing submission for assignment %s:" % assignment_id) print() if submission_commit is None: print("WARNING: Previously submitted commit '%s' is not in the repository!" % registration.final_submission.commit_sha) else: print_commit(submission_commit) print() print("Are you sure you want to cancel this submission? (y/n): ", end=' ') if not yes: yesno = input() else: yesno = 'y' print('y') if yesno in ('y', 'Y', 'yes', 'Yes', 'YES'): registration.final_submission_id = None registration.grader_username = None registration.grading_started = False print() print("The submission has been cancelled.")
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_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