Пример #1
0
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
Пример #3
0
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)
Пример #4
0
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
Пример #7
0
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)
Пример #8
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()
Пример #10
0
        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
Пример #11
0
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
Пример #12
0
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