def overwrite_group_categories(course: canvasapi.course.Course, new_group_categories: Dict[str, Dict[str, List[str]]], student_identifier: str, group_missing_members: bool): current_group_categories = course.get_group_categories() # remove any existing matching group categories for group_category in current_group_categories: if group_category.name in new_group_categories: group_category.delete() # mapping of student identifier to student students = { getattr(student, student_identifier).lower(): student for student in course.get_users(enrollment_type=['student']) if hasattr(student, student_identifier) } for category_name, group in new_group_categories.items(): group_category = course.create_group_category(name=category_name, self_signup=None) for group_name, member_identifiers in group.items(): group = group_category.create_group(name=group_name) members = [] for ident in member_identifiers: ident = ident.lower() if ident in students: members.append(students[ident].id) else: print(f'Could not locate {ident}') group.edit(members=members) if group_missing_members: group_category.assign_members()
def upload_to_canvas(self, course: canvasapi.course.Course) -> None: canvas_quiz = course.create_quiz(self.quiz_info) for question in self.quiz_questions: canvas_quiz.create_question(question=question) canvas_assignment = course.get_assignment(canvas_quiz.assignment_id) edited_canvas_assignment = canvas_assignment.edit( assignment=self.assignment_info) # edited_quiz = canvas_quiz.edit(quiz={'published': True}) # second_assignment = course.get_assignment(canvas_quiz.assignment_id) pass
def create_kudo_point_giving_quiz_for_user( user: canvasapi.user.User, group: Group, course: canvasapi.course.Course, unlock_date: datetime, due_date: datetime, late_days: int = 0, number_of_kudo_points: int = 2) -> None: the_override = { 'student_ids': [user.id], 'title': f'Override for {user}', 'due_at': due_date.isoformat(), 'unlock_at': unlock_date, 'lock_at': due_date + datetime.timedelta(days=late_days) } the_quiz = course.create_quiz({ 'title': f"{user.name}'s Kudo Point Givings for {group.name}", 'quiz_type': 'assignment', 'allowed_attempts': 10, 'scoring_policy': 'keep_latest', 'published': False, 'show_correct_answers': False, 'only_visible_to_overrides': True, }) # create the questions for this quiz. # the questions in this case are who the student wants to give the Kudo points to answers = create_kudo_point_answers(user, group) for point in range(1, number_of_kudo_points + 1): question = { 'question_name': f'Kudo Point {point}', 'question_text': 'Who do you want to give this Kudo Point to?', 'question_type': 'multiple_choice_question', 'answers': answers } the_quiz.create_question(question=question) # This part is needed to make sure the assignment only shows up to # the single person we want to assign it to # for some reason it has to be set for both the quiz and the assignment the_assignment = course.get_assignment(the_quiz.assignment_id) the_assignment = the_assignment.edit( assignment={ 'grading_type': 'not_graded', 'omit_from_final_grade': True, 'only_visible_to_overrides': True, 'assignment_overrides': [the_override], 'published': True }) print(the_quiz)
def download_users_to_csv( path_to_csv: str, course: canvasapi.course.Course, roles: Tuple[str] = ('student', ), headers: Iterable[str] = ('Last Name', 'First Name', 'Login Id', 'Email', 'SID', 'Canvas Id'), user_fields: Iterable[str] = ('last_name', 'first_name', 'login_id', 'email', 'sis_user_id', 'id')): user_fields = list(user_fields) with open(path_to_csv, mode='w', newline='') as csv_file: writer = csv.writer(csv_file) users = course.get_users(enrollment_type=roles, sort='username') writer.writerow(headers) for user in users: user_info = [] for field in user_fields: last_name, first_name = user.sortable_name.split(',') if field == 'last_name': user_info.append(last_name) elif field == 'first_name': user_info.append(first_name) elif hasattr(user, field): user_info.append(getattr(user, field)) else: user_info.append('') writer.writerow(user_info)
def get_zoom_course(self, course: canvasapi.course.Course) -> None: # Get tabs and look for defined tool(s) that aren't hidden tabs = course.get_tabs() for tab in tabs: # Hidden only included if true if (tab.label == "Zoom" and not hasattr(tab, "hidden")): logger.info("Found a course with zoom as %s", tab.id) r = CANVAS._Canvas__requester.request("GET", _url=tab.url) external_url = r.json().get("url") r = requests.get(external_url) # Parse out the form from the response soup = bs(r.text, 'html.parser') # Get the form and parse out all of the inputs form = soup.find('form') if not form: logger.info( "Could not find a form to launch this zoom page, skipping" ) break self.zoom_courses.append({ 'account_id': course.account_id, 'course_id': course.id, 'course_name': course.name }) fields = form.findAll('input') formdata = dict((field.get('name'), field.get('value')) for field in fields) # Get the URL to post back to posturl = form.get('action') self.get_zoom_details(posturl, formdata, course.id) return None
def get_lti_tabs(self, course: canvasapi.course.Course) -> None: # This is a new course we're looking through self.course_count += 1 logger.info(f"Fetching course #{self.course_count} for {course}") # Get tabs and look for defined tool(s) that aren't hidden tabs = course.get_tabs() for tab in tabs: # The format in canvas of ids is like # context_external_tool_12345. But we need the numeric part tab_id = tab.id.split('_')[-1] # We want the integer value if it exists, otherwise just skip if tab_id.isdigit(): tab_id = int(tab_id) else: continue # Hidden only included if true if (tab_id in self.supported_tools and not hasattr(tab, "hidden")): self.placement_count += 1 self.lti_placements.append({ 'id': self.placement_count, 'course_id': course.id, 'account_id': course.account_id, 'course_name': course.name, 'placement_type_id': tab_id }) # TODO: Find a better way of running this just for zoom if (tab.label.upper() == "ZOOM"): self.zoom_courses_meetings.extend( self.zoom_placements.get_zoom_details( tab, self.placement_count)) return None
def create_assignment_group( assignment_group_name: str, course: canvasapi.course.Course, ignore_case: bool = True) -> canvasapi.assignment.AssignmentGroup: assignment_groups = list(course.get_assignment_groups()) number_of_times_named_used = sum( (str_starts_with(assignment_group.name, assignment_group_name, ignore_case, ignore_whitespace=True) for assignment_group in assignment_groups)) assignment_group_name += '' if number_of_times_named_used == 0 else f'({number_of_times_named_used})' return course.create_assignment_group(name=assignment_group_name, position=len(assignment_groups), group_weight=0)
def locate_assignment_group( assignment_group_name: str, course: canvasapi.course.Course, ignore_case: bool = True) -> canvasapi.assignment.AssignmentGroup: assignment_groups = list(course.get_assignment_groups()) for assignment_group in assignment_groups: if str_equal(assignment_group_name, assignment_group.name, ignore_case, ignore_whitespace=True): return assignment_group return resolve_missing_assignment_group(assignment_group_name, assignment_groups, course, ignore_case)
def __init__(self, course: canvasapi.course.Course, assignment_name: str, assignment_group: canvasapi.assignment.AssignmentGroup, number_of_kudo_points, due_date: Optional[datetime.datetime] = None, unlock_date: Optional[datetime.datetime] = None, lock_date: Optional[datetime.datetime] = None): """ :param course: :param assignment_name: :param assignment_group: :param number_of_kudo_points: :param due_date: :param unlock_date: :param lock_date: """ self.user = course self.assignment_name = assignment_name self.assignment_group = assignment_group self.unlock_date = unlock_date self.due_date = due_date self.lock_date = lock_date self.number_of_kudo_points = number_of_kudo_points students = course.get_users(sort='username', enrollment_type=['student']) self.students = {} for student in students: if student.sortable_name not in self.students: self.students[student.sortable_name] = [] self.students[student.sortable_name].append(student) self.quiz_info = self._create_quiz_info() self.assignment_info = self._create_assignment_info() self.quiz_questions = self._create_quiz_questions()
def get_all_modules( course: canvasapi.course.Course ) -> List[Union[Module, ModuleItem]]: """ Returns a list of all modules for the given course. Includes unpublished modules if self.bot.notify_unpublished is True. """ all_modules = [] for module in course.get_modules(): # If module does not have the "published" attribute, then the host of the bot does # not have access to unpublished modules. Reference: https://canvas.instructure.com/doc/api/modules.html if self.bot.notify_unpublished or not hasattr( module, "published") or module.published: all_modules.append(module) for item in module.get_module_items(): # See comment about the "published" attribute above. if self.bot.notify_unpublished or not hasattr( item, "published") or item.published: all_modules.append(item) return all_modules
def parse(studentData: pd, CLASS_ID: int, canvasClass: canvasapi.course.Course): #Fill the dictionary and the student lists dictSt = {} #ECS 36B pronounsQ = '1252650' pronounsFree = '1252651' genderMatchQ = '1252652' syncQ = '1252653' timeQ = '1252654' commPreferenceQ = '1252655' commValuesQ = '1252656' leaderQ = '1252657' countryQ = '1091825' countryFree = '1091826' internationalQ = '1252658' languageQ = '1252659' languageFree = '1252660' groupWantsQ = '1252661' groupWantsFree = '1252662' priorityQ = '1252663' studentPerfQ = '1252664' #ECS 36A #Set default to ECS 36B, but it could also be 36A #This will need to be changed each time the quiz is changed if CLASS_ID == 574775: # ECS 36A pronounsQ = '1252518' pronounsFree = '1252519' genderMatchQ = '1252520' syncQ = '1252521' timeQ = '1252522' commPreferenceQ = '1252523' commValuesQ = '1252524' leaderQ = '1252525' countryQ = '1091825' countryFree = '1091826' internationalQ = '1252526' languageQ = '1252527' languageFree = '1252528' groupWantsQ = '1252529' groupWantsFree = '1252530' priorityQ = '1252531' studentPerfQ = '1252532' # the list of question ids of questions questionList = [ pronounsQ, pronounsFree, genderMatchQ, syncQ, timeQ, commPreferenceQ, commValuesQ, leaderQ, internationalQ, languageQ, languageFree, groupWantsQ, groupWantsFree, priorityQ, studentPerfQ ] # all columns in the student data csv. Need for full question string fullQuestionList = studentData.columns.values.tolist() # print(fullQuestionList) # numerical location of the question questionsLoc = [] # iterate through all column names in csv and add location of matching id to list for loc, question in enumerate(fullQuestionList): for id in questionList: if id in question: questionsLoc.append(loc) # for item in questionList: # questionsFull.append([word for word in fullQuestionList if item in word]) #for item in questionsFull: # questionLoc.append(studentData.columns.get_loc(item)) questionsDict = dict(zip(questionList, questionsLoc)) questionsDict['id'] = studentData.columns.get_loc('id') questionsDict['name'] = studentData.columns.get_loc('name') students_still_enrolled = canvasClass.get_users( enrollment_type=('student', ), sort='username') students_still_enrolled = { student.id for student in students_still_enrolled } for row in studentData.itertuples(index=False, name=None): if row[questionsDict['id']] not in students_still_enrolled: continue #name and id tempStudent = Student(row[questionsDict['id']], row[questionsDict['name']]) #pronouns that the student prefers tempArr = row[questionsDict[pronounsQ]] freeResponse = row[questionsDict[pronounsFree]] if len(tempArr) != 0: if tempArr == "Not Included": tempStudent.pronouns = freeResponse else: tempStudent.pronouns = tempArr #preferSame is True if the student would like to share their group with someone of the same gender tempArr = row[questionsDict[genderMatchQ]] if type(tempArr) is str: if tempArr == "I would prefer another person who as the same pronouns as I do.": tempStudent.preferSame = 2 elif tempArr == "I would prefer another person who does not have the same pronouns as I do.": tempStudent.preferSame = 0 elif tempArr == "No preference": tempStudent.preferSame = 1 #meeting times - Sun - Sat, Midnight-4, 4-8, 8-noon, etc [0][0] is sunday at midnight to 4 time slot tempArr = row[questionsDict[timeQ]] if type(tempArr) is str: meetingTimes = tempArr.split(",") daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] for i in range(7): for j in range(1, 7): if daysOfWeek[i] + str(j) in meetingTimes: tempStudent.meetingTimes[i][(j - 1)] = True #asynch (2), synch (1), no pref (0)- how the student would like to meet studentSync = row[questionsDict[syncQ]] if type(studentSync) is str: if studentSync == "Synchronously": tempStudent.preferAsy = 1 elif studentSync == "Asynchronously": tempStudent.preferAsy = 2 else: tempStudent.preferAsy = 0 #contact pref - Discord, Phone, Email, Canvas - 2 = yes, 1 = no preference, 0 = not comfortable tempArr = (row[questionsDict[commPreferenceQ]]) if type(tempArr) is str: contactPreference = tempArr.split(",") # Parse which contact method student wants from a list if "Discord" in contactPreference: (tempStudent.contactPreference)[0] = True if "Text/Phone Number" in contactPreference: (tempStudent.contactPreference)[1] = True if "Email" in contactPreference: (tempStudent.contactPreference)[2] = True if "Canvas Groups" in contactPreference: (tempStudent.contactPreference)[3] = True #contact info - [DiscordHandle, PhoneNumber, [email protected]] tempArr = (row[questionsDict[commValuesQ]]) if type(tempArr) is str: contactInfo = tempArr.split(",") for i in range(3): tempStudent.contactInformation[i] = contactInfo[i] #prefer leader- True if they prefer to be the leader, false otherwise studentLeader = row[questionsDict[leaderQ]] if type(studentLeader) is str: if studentLeader == "I like to lead.": tempStudent.preferLeader = True else: tempStudent.preferLeader = False #country - Country of Origin ''' tempArr = (row[studentData.columns.str.contains(countryQ)].tolist()) freeResponse = row[studentData.columns.str.contains(countryFree)].tolist() if type(tempArr[0]) is str: countryResult = tempArr[0].split(",") if len(countryResult) == 2: if countryResult[0] == "Not Included": tempStudent.countryOfOrigin = freeResponse[0] else: tempStudent.countryOfOrigin = tempArr[0] elif len(countryResult) == 1: if countryResult[0] == "Yes" or tempArr[0] == "No": #preferCountry - True if they would like to have a groupmate from the same country if tempArr[0] == "No": tempStudent.preferCountry = False else: tempStudent.preferCountry = True else: if tempArr[1] == "No": tempStudent.preferCountry = False else: tempStudent.preferCountry = True tempArr.clear() freeResponse.clear() ''' #international student preference tempArr = row[questionsDict[internationalQ]] if type(tempArr) is str: if tempArr == "I would like to be placed with another international student.": tempStudent.international = 2 elif tempArr == "No preference": tempStudent.international = 1 elif tempArr == "I am not an international student.": tempStudent.international = 0 #languages - Preferred language languageSelect = row[questionsDict[languageQ]] notIncluedeLanguage = row[questionsDict[languageFree]] if type(languageSelect) is str: if languageSelect == "Not Included": tempStudent.language = notIncluedeLanguage else: tempStudent.language = languageSelect #Preferred stuff to do - the drop downs and free response tempArr = (row[questionsDict[groupWantsQ]]) # Take the array of one item and check if its the right type, # then assign to the variable if type(tempArr) is str: tempStudent.option1 = tempArr tempResponse = row[questionsDict[groupWantsFree]] if type(tempResponse) is str: tempStudent.freeResponse = tempResponse #Priority of what they want freeResponse = row[questionsDict[priorityQ]] if type(freeResponse) is str: priority = freeResponse.split(",") while len(priority) < 5: priority.append("default") tempStudent.priorityList = priority # how the student feels in the class tempArr = row[questionsDict[studentPerfQ]] if type(tempArr) is str: if tempArr == "I'm confident.": tempStudent.confidence = 2 elif tempArr == "I have some questions.": tempStudent.confidence = 1 elif tempArr == "I could really use some help.": tempStudent.confidence = 0 #Add the student to the dictionary of all students dictSt[row[questionsDict['id']]] = tempStudent return dictSt
def create_extra_credit_balancer( course: canvasapi.course.Course, assignment_group: canvasapi.assignment.AssignmentGroup ) -> canvasapi.assignment.Assignment: if not hasattr(assignment_group, 'assignments'): assignment_group = course.get_assignment_group(assignment_group.id, include=['assignments']) total_points = 0 for assignment in assignment_group.assignments: total_points += assignment['points_possible'] students = list( course.get_multiple_submissions( student_ids=['all'], assignment_ids=[ assignment['id'] for assignment in assignment_group.assignments ], grouped=True)) point_map = dict() for student in students: student_total = 0 for submission in student.submissions: student_total += submission.entered_score if submission.entered_score is not None else 0 point_map[student.user_id] = min(total_points - student_total, 0) now = datetime.datetime.now() balancer = course.create_assignment( assignment={ 'name': f'{assignment_group.name} Balancer', 'description': 'This assignment is here to prevent you from receiving over the maximum allowed extra credit. ' 'If you see negative points here it means you received a lot of kudo points from your partners. ' 'So much so that it would push you over the maximum allowed extra credit. Thank you for being so ' 'helpful but I cannot let the amount of extra credit in the class be unbounded.', 'position': len(assignment_group.assignments), # maybe + 1? 'assignment_group_id': assignment_group.id, 'submission_types': ['none'], 'points_possible': 0, 'due_at': now.isoformat(), 'lock_at': now.isoformat(), 'unlock_at': now.isoformat(), 'published': True }) balancer.submissions_bulk_update( grade_data={ student_id: { 'posted_grade': grade } for student_id, grade in point_map.items() }) return balancer