Skip to content
Snippets Groups Projects
Commit 91fa7ddd authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

added code to review peer evaluations

parent aaa69d09
Branches
No related tags found
No related merge requests found
......@@ -12,6 +12,9 @@ class CanvasSession:
return CanvasSession.__instance
# PEOPLE CLASSES
class CanvasUser:
def __init__(self, user):
super().__init__()
......@@ -102,7 +105,7 @@ class CanvasUser:
"""
class CanvasGroupSet: # aka, group_category
class CanvasUserGroupSet: # aka, group_category
def __init__(self, group_category):
super().__init__()
self.canvas_group_category = group_category
......@@ -114,19 +117,19 @@ class CanvasGroupSet: # aka, group_category
canvas_groups = self.canvas_group_category.get_groups()
groups = []
for group in canvas_groups:
groups.append(CanvasGroup(group))
groups.append(CanvasUserGroup(group))
return groups
def create_group(self, group_name):
canvas_group = self.canvas_group_category.create_group(name=group_name)
return CanvasGroup(canvas_group)
return CanvasUserGroup(canvas_group)
def create_groups(self, number_of_groups):
base_name = self.get_name()
groups = []
for group_number in range(1, number_of_groups + 1):
canvas_group = self.create_group(f'{base_name} {group_number}')
groups.append(CanvasGroup(canvas_group))
groups.append(CanvasUserGroup(canvas_group))
return groups
def __repr__(self):
......@@ -178,26 +181,26 @@ class CanvasGroupSet: # aka, group_category
"""
class CanvasGroup:
class CanvasUserGroup:
def __init__(self, group):
super().__init__()
self.canvas_group = group
self.canvas_user_group = group
def get_name(self):
return self.canvas_group.name
return self.canvas_user_group.name
def get_number_of_students(self):
return self.canvas_group.members_count
return self.canvas_user_group.members_count
def get_students(self):
canvas_users = self.canvas_group.get_users()
canvas_users = self.canvas_user_group.get_users()
students = []
for student in canvas_users:
students.append(CanvasUser(student))
return students
def add_student(self, user):
self.canvas_group.create_membership(user.get_canvas_id())
self.canvas_user_group.create_membership(user.get_canvas_id())
def __repr__(self):
return self.get_name()
......@@ -261,6 +264,341 @@ class CanvasGroup:
"""
# ASSIGNMENT-RELATED CLASSES
class CanvasAssignment:
def __init__(self, assignment):
super().__init__()
self.canvas_assignment = assignment
def get_name(self):
return self.canvas_assignment.name
def is_quiz(self):
return 'online_quiz' in self.canvas_assignment.submission_types
def get_submission_text(self, canvas_user):
submission = self.canvas_assignment.get_submission(canvas_user.get_canvas_id())
if submission.submission_type == 'online_text_entry':
return submission.body
elif submission.submission_type == 'online_url':
return submission.url
elif self.is_quiz():
return 'online quiz'
else:
return None
def get_quiz_response(self, canvas_user):
questions_and_answers = []
questions = []
answers = []
if self.is_quiz():
quiz_id = self.canvas_assignment.quiz_id
course_id = self.canvas_assignment.course_id
quiz_submissions = CanvasCourse(course_id).canvas_course.get_quiz(
quiz_id).get_submissions() # breaking encapsulation
candidate_submission = list(filter(lambda q: q.user_id == canvas_user.get_canvas_id(), quiz_submissions))
if len(candidate_submission) > 0:
full_questions = candidate_submission[0].get_submission_questions()
for question in full_questions:
questions.append(question.question_text)
submission = self.canvas_assignment.get_submission(canvas_user.get_canvas_id(),
include=['submission_history'])
history = sorted(list(submission.submission_history), key=lambda s: s['submitted_at'], reverse=True)
if len(history) > 0:
submission_data = history[0]['submission_data']
for datum in submission_data:
answers.append(datum['text'])
if len(questions) == len(answers):
for i in range(len(questions)):
questions_and_answers.append({'question': questions[i], 'answer': answers[i]})
else:
questions_and_answers = None
else:
questions_and_answers = None
return questions_and_answers
def __repr__(self):
return self.get_name()
"""
{
// the ID of the assignment
"id": 4,
// the name of the assignment
"name": "some assignment",
// the assignment description, in an HTML fragment
"description": "<p>Do the following:</p>...",
// The time at which this assignment was originally created
"created_at": "2012-07-01T23:59:00-06:00",
// The time at which this assignment was last modified in any way
"updated_at": "2012-07-01T23:59:00-06:00",
// the due date for the assignment. returns null if not present. NOTE: If this
// assignment has assignment overrides, this field will be the due date as it
// applies to the user requesting information from the API.
"due_at": "2012-07-01T23:59:00-06:00",
// the lock date (assignment is locked after this date). returns null if not
// present. NOTE: If this assignment has assignment overrides, this field will
// be the lock date as it applies to the user requesting information from the
// API.
"lock_at": "2012-07-01T23:59:00-06:00",
// the unlock date (assignment is unlocked after this date) returns null if not
// present NOTE: If this assignment has assignment overrides, this field will be
// the unlock date as it applies to the user requesting information from the
// API.
"unlock_at": "2012-07-01T23:59:00-06:00",
// whether this assignment has overrides
"has_overrides": true,
// (Optional) all dates associated with the assignment, if applicable
"all_dates": null,
// the ID of the course the assignment belongs to
"course_id": 123,
// the URL to the assignment's web page
"html_url": "https://...",
// the URL to download all submissions as a zip
"submissions_download_url": "https://example.com/courses/:course_id/assignments/:id/submissions?zip=1",
// the ID of the assignment's group
"assignment_group_id": 2,
// Boolean flag indicating whether the assignment requires a due date based on
// the account level setting
"due_date_required": true,
// Allowed file extensions, which take effect if submission_types includes
// 'online_upload'.
"allowed_extensions": ["docx", "ppt"],
// An integer indicating the maximum length an assignment's name may be
"max_name_length": 15,
// Boolean flag indicating whether or not Turnitin has been enabled for the
// assignment. NOTE: This flag will not appear unless your account has the
// Turnitin plugin available
"turnitin_enabled": true,
// Boolean flag indicating whether or not VeriCite has been enabled for the
// assignment. NOTE: This flag will not appear unless your account has the
// VeriCite plugin available
"vericite_enabled": true,
// Settings to pass along to turnitin to control what kinds of matches should be
// considered. originality_report_visibility can be 'immediate',
// 'after_grading', 'after_due_date', or 'never' exclude_small_matches_type can
// be null, 'percent', 'words' exclude_small_matches_value: - if type is null,
// this will be null also - if type is 'percent', this will be a number between
// 0 and 100 representing match size to exclude as a percentage of the document
// size. - if type is 'words', this will be number > 0 representing how many
// words a match must contain for it to be considered NOTE: This flag will not
// appear unless your account has the Turnitin plugin available
"turnitin_settings": null,
// If this is a group assignment, boolean flag indicating whether or not
// students will be graded individually.
"grade_group_students_individually": false,
// (Optional) assignment's settings for external tools if submission_types
// include 'external_tool'. Only url and new_tab are included (new_tab defaults
// to false). Use the 'External Tools' API if you need more information about
// an external tool.
"external_tool_tag_attributes": null,
// Boolean indicating if peer reviews are required for this assignment
"peer_reviews": false,
// Boolean indicating peer reviews are assigned automatically. If false, the
// teacher is expected to manually assign peer reviews.
"automatic_peer_reviews": false,
// Integer representing the amount of reviews each user is assigned. NOTE: This
// key is NOT present unless you have automatic_peer_reviews set to true.
"peer_review_count": 0,
// String representing a date the reviews are due by. Must be a date that occurs
// after the default due date. If blank, or date is not after the assignment's
// due date, the assignment's due date will be used. NOTE: This key is NOT
// present unless you have automatic_peer_reviews set to true.
"peer_reviews_assign_at": "2012-07-01T23:59:00-06:00",
// Boolean representing whether or not members from within the same group on a
// group assignment can be assigned to peer review their own group's work
"intra_group_peer_reviews": false,
// The ID of the assignment’s group set, if this is a group assignment. For
// group discussions, set group_category_id on the discussion topic, not the
// linked assignment.
"group_category_id": 1,
// if the requesting user has grading rights, the number of submissions that
// need grading.
"needs_grading_count": 17,
// if the requesting user has grading rights and the
// 'needs_grading_count_by_section' flag is specified, the number of submissions
// that need grading split out by section. NOTE: This key is NOT present unless
// you pass the 'needs_grading_count_by_section' argument as true. ANOTHER
// NOTE: it's possible to be enrolled in multiple sections, and if a student is
// setup that way they will show an assignment that needs grading in multiple
// sections (effectively the count will be duplicated between sections)
"needs_grading_count_by_section": [{"section_id":"123456","needs_grading_count":5},
// {"section_id":"654321","needs_grading_count":0}],
// the sorting order of the assignment in the group
"position": 1,
// (optional, present if Sync Grades to SIS feature is enabled)
"post_to_sis": true,
// (optional, Third Party unique identifier for Assignment)
"integration_id": "12341234",
// (optional, Third Party integration data for assignment)
"integration_data": "12341234",
// For courses using Old Gradebook, indicates whether the assignment is muted.
// For courses using New Gradebook, true if the assignment has any unposted
// submissions, otherwise false. To see the posted status of submissions, check
// the 'posted_attribute' on Submission.
"muted": null,
// the maximum points possible for the assignment
"points_possible": 12,
// the types of submissions allowed for this assignment list containing one or
// more of the following: 'discussion_topic', 'online_quiz', 'on_paper', 'none',
// 'external_tool', 'online_text_entry', 'online_url', 'online_upload'
// 'media_recording'
"submission_types": ["online_text_entry"],
// If true, the assignment has been submitted to by at least one student
"has_submitted_submissions": true,
// The type of grading the assignment receives; one of 'pass_fail', 'percent',
// 'letter_grade', 'gpa_scale', 'points'
"grading_type": "points",
// The id of the grading standard being applied to this assignment. Valid if
// grading_type is 'letter_grade' or 'gpa_scale'.
"grading_standard_id": null,
// Whether the assignment is published
"published": true,
// Whether the assignment's 'published' state can be changed to false. Will be
// false if there are student submissions for the assignment.
"unpublishable": false,
// Whether the assignment is only visible to overrides.
"only_visible_to_overrides": false,
// Whether or not this is locked for the user.
"locked_for_user": false,
// (Optional) Information for the user about the lock. Present when
// locked_for_user is true.
"lock_info": null,
// (Optional) An explanation of why this is locked for the user. Present when
// locked_for_user is true.
"lock_explanation": "This assignment is locked until September 1 at 12:00am",
// (Optional) id of the associated quiz (applies only when submission_types is
// ['online_quiz'])
"quiz_id": 620,
// (Optional) whether anonymous submissions are accepted (applies only to quiz
// assignments)
"anonymous_submissions": false,
// (Optional) the DiscussionTopic associated with the assignment, if applicable
"discussion_topic": null,
// (Optional) Boolean indicating if assignment will be frozen when it is copied.
// NOTE: This field will only be present if the AssignmentFreezer plugin is
// available for your account.
"freeze_on_copy": false,
// (Optional) Boolean indicating if assignment is frozen for the calling user.
// NOTE: This field will only be present if the AssignmentFreezer plugin is
// available for your account.
"frozen": false,
// (Optional) Array of frozen attributes for the assignment. Only account
// administrators currently have permission to change an attribute in this list.
// Will be empty if no attributes are frozen for this assignment. Possible
// frozen attributes are: title, description, lock_at, points_possible,
// grading_type, submission_types, assignment_group_id, allowed_extensions,
// group_category_id, notify_of_update, peer_reviews NOTE: This field will only
// be present if the AssignmentFreezer plugin is available for your account.
"frozen_attributes": ["title"],
// (Optional) If 'submission' is included in the 'include' parameter, includes a
// Submission object that represents the current user's (user who is requesting
// information from the api) current submission for the assignment. See the
// Submissions API for an example response. If the user does not have a
// submission, this key will be absent.
"submission": null,
// (Optional) If true, the rubric is directly tied to grading the assignment.
// Otherwise, it is only advisory. Included if there is an associated rubric.
"use_rubric_for_grading": true,
// (Optional) An object describing the basic attributes of the rubric, including
// the point total. Included if there is an associated rubric.
"rubric_settings": "{"points_possible"=>12}",
// (Optional) A list of scoring criteria and ratings for each rubric criterion.
// Included if there is an associated rubric.
"rubric": null,
// (Optional) If 'assignment_visibility' is included in the 'include' parameter,
// includes an array of student IDs who can see this assignment.
"assignment_visibility": [137, 381, 572],
// (Optional) If 'overrides' is included in the 'include' parameter, includes an
// array of assignment override objects.
"overrides": null,
// (Optional) If true, the assignment will be omitted from the student's final
// grade
"omit_from_final_grade": true,
// Boolean indicating if the assignment is moderated.
"moderated_grading": true,
// The maximum number of provisional graders who may issue grades for this
// assignment. Only relevant for moderated assignments. Must be a positive
// value, and must be set to 1 if the course has fewer than two active
// instructors. Otherwise, the maximum value is the number of active instructors
// in the course minus one, or 10 if the course has more than 11 active
// instructors.
"grader_count": 3,
// The user ID of the grader responsible for choosing final grades for this
// assignment. Only relevant for moderated assignments.
"final_grader_id": 3,
// Boolean indicating if provisional graders' comments are visible to other
// provisional graders. Only relevant for moderated assignments.
"grader_comments_visible_to_graders": true,
// Boolean indicating if provisional graders' identities are hidden from other
// provisional graders. Only relevant for moderated assignments with
// grader_comments_visible_to_graders set to true.
"graders_anonymous_to_graders": true,
// Boolean indicating if provisional grader identities are visible to the final
// grader. Only relevant for moderated assignments.
"grader_names_visible_to_final_grader": true,
// Boolean indicating if the assignment is graded anonymously. If true, graders
// cannot see student identities.
"anonymous_grading": true,
// The number of submission attempts a student can make for this assignment. -1
// is considered unlimited.
"allowed_attempts": 2,
// Whether the assignment has manual posting enabled. Only relevant for courses
// using New Gradebook.
"post_manually": true
}
"""
class CanvasAssignmentGroup:
# from canvasapi.assignment import Assignment
def __init__(self, group):
super().__init__()
self.canvas_assignment_group = group
def get_name(self):
return self.canvas_assignment_group.name
def get_assignments(self):
course_id = self.canvas_assignment_group.course_id
all_assignments = CanvasCourse(course_id).get_assignments()
assignments = list(filter(lambda a: a.canvas_assignment.assignment_group_id == self.canvas_assignment_group.id,
all_assignments)) # breaking encapsulation
return assignments
def __repr__(self):
return self.get_name()
"""
{
// the id of the Assignment Group
"id": 1,
// the name of the Assignment Group
"name": "group2",
// the position of the Assignment Group
"position": 7,
// the weight of the Assignment Group
"group_weight": 20,
// the sis source id of the Assignment Group
"sis_source_id": "1234",
// the integration data of the Assignment Group
"integration_data": {"5678":"0954"},
// the assignments in this Assignment Group (see the Assignment API for a
// detailed list of fields)
"assignments": [],
// the grading rules that this Assignment Group has
"rules": null
}
"""
# THE COURSE ITSELF
class CanvasCourse:
def __init__(self, course_id):
self.canvas_course = CanvasSession.get_session().get_course(course_id)
......@@ -286,23 +624,37 @@ class CanvasCourse:
instructors.append(CanvasUser(user))
return instructors
def get_all_groups(self):
def get_all_user_groups(self):
canvas_groups = self.canvas_course.get_groups()
groups = []
for group in canvas_groups:
groups.append(CanvasGroup(group))
groups.append(CanvasUserGroup(group))
return groups
def get_group_sets(self):
def get_user_groupsets(self):
canvas_group_categories = self.canvas_course.get_group_categories()
group_sets = []
for group_category in canvas_group_categories:
group_sets.append(CanvasGroupSet(group_category))
group_sets.append(CanvasUserGroupSet(group_category))
return group_sets
def create_groupset(self, groupset_name):
def create_user_groupset(self, groupset_name):
group_category = self.canvas_course.create_group_category(groupset_name)
return CanvasGroupSet(group_category)
return CanvasUserGroupSet(group_category)
def get_assignment_groups(self):
canvas_assignment_groups = self.canvas_course.get_assignment_groups(include=['assignments'])
assignment_groups = []
for assignment_group in canvas_assignment_groups:
assignment_groups.append(CanvasAssignmentGroup(assignment_group))
return assignment_groups
def get_assignments(self):
canvas_assignments = self.canvas_course.get_assignments()
assignments = []
for assignment in canvas_assignments:
assignments.append(CanvasAssignment(assignment))
return assignments
def __repr__(self):
return f'{self.canvas_course.course_code}: {self.canvas_course.name}'
......@@ -409,6 +761,5 @@ class CanvasCourse:
}
"""
if __name__ == '__main__':
pass
import textwrap
from api.canvas_classes import *
from course import Course
def structure_text(text):
import re
return textwrap.wrap(re.sub(re.compile('<.*?>'), '', text))
def select_from_list(choices, choice_name):
print(f'Choose the {choice_name} from this list:')
for i in range(len(choices)):
print(f'{i+1})\t{choices[i]}'.expandtabs(4))
selection = input('Enter selection: ')
return choices[int(selection)-1]
def display_peer_reviews(assignment, students):
for student in students:
text = []
print(f'\n\n\t{student.get_name()}'.expandtabs(4))
if assignment.is_quiz():
response = assignment.get_quiz_response(student)
if response is not None:
for entry in response:
text = text + structure_text(entry['question']) + structure_text(entry['answer'])
else:
response = assignment.get_submission_text(student)
if response is not None:
text = structure_text(response)
for line in text:
print(f'\t\t{line}'.expandtabs(4))
def display_git_contributions():
print('Review git contributions offline')
def grade(assignment1, assignment2, students):
pass
if __name__ == '__main__':
course = CanvasCourse(Course.canvas_course_id)
print('First, select the Canvas assignment to review and grade.\n')
assignment_groups = course.get_assignment_groups()
assignment_group = select_from_list(assignment_groups, 'assignment group')
print()
assignments = assignment_group.get_assignments()
peer_review_assignment = select_from_list(assignments, 'assignment')
print(f'\nSelected {peer_review_assignment}.')
print('Now select the student groupset with the teams.\n')
student_groupsets = course.get_user_groupsets()
student_groupset = select_from_list(student_groupsets, 'groupset')
print(f'\nSelected {student_groupset}.\n')
student_groups = student_groupset.get_groups()
print('Are you grading all groups, or are you revisiting a specific group?')
options = ['All groups', 'Specific group']
option = select_from_list(options, 'option')
if option is options[1]:
print('Which group?')
student_groups = [select_from_list(student_groups, 'student group')]
for student_group in student_groups:
input(f'\n\nPress any key to grade {student_group}')
display_peer_reviews(peer_review_assignment, student_group.get_students())
display_git_contributions()
# TODO: Ask if you want to grade (keep track of groups that you don't grade)
if True:
grade(peer_review_assignment, None, student_group.get_students())
......@@ -73,7 +73,7 @@ def create_repositories(assignment_number, student_pairs, verbose):
def create_groups(assignment_number, student_pairs):
course = CanvasCourse(Course.canvas_course_id)
group_set = course.create_groupset(f'{assignment_number}pairs')
group_set = course.create_user_groupset(f'{assignment_number}pairs')
for pair in student_pairs:
group = group_set.create_group(f'{assignment_number}pair {pair[0]}')
group.add_student(pair[1].get_canvas_user())
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment