def _get_quizzes(context, course_id): quiz_list = get_all_list_data(context, quizzes.list_quizzes_in_course, course_id) for quiz in quiz_list: details = get_all_list_data(context, quiz_questions.list_questions_in_quiz, course_id=course_id, quiz_id=quiz['id']) quiz['questions'] = details return quiz_list
def handle(self, *args, **options): term_id = options['term_id'][0] try: term = Term.objects.get(term_id=term_id) except Term.DoesNotExist: raise CommandError('Invalid term_id %d provided.' % term_id) logger.info("Creating Canvas user primary email report for term_id %d...", term_id) report_data = {} for ci in CourseInstance.objects.filter(term=term): if ci.canvas_course_id: logger.info("Retrieving users for canvas_course_id %d", ci.canvas_course_id) users = {} try: users_with_email = get_all_list_data( SDK_CONTEXT, list_users_in_course_users, ci.canvas_course_id, 'email' ) for user in users_with_email: user_id = user['sis_user_id'] if user_id not in users: users[user_id] = {'email': user['email'], 'roles': []} users_with_enrollments = get_all_list_data( SDK_CONTEXT, list_users_in_course_users, ci.canvas_course_id, 'enrollments' ) for user in users_with_enrollments: users[user['sis_user_id']]['roles'].extend( [enrollment['role'] for enrollment in user['enrollments']] ) except CanvasAPIError: logger.exception("Failed to retrieve users for canvas_course_id %d", ci.canvas_course_id) continue for p in Person.objects.filter(univ_id__in=list(users.keys())): user = users[p.univ_id] if p.email_address != user['email']: for role in user['roles']: if role not in report_data: report_data[role] = [] report_data[role].append([role, p.univ_id, user['email']]) report_path = os.path.join(settings.REPORT_DIR, 'report_user_primary_email.csv') with open(report_path, 'wb') as f: writer = csv.writer(f, dialect='excel') writer.writerow(['Role', 'SIS Email', 'Canvas Email']) for role, rows in report_data.items(): writer.writerows(rows) logger.info("User primary email report complete for term_id %d at %s", term_id, report_path)
def handle(self, *args, **options): term_id = options['term_id'][0] try: term = Term.objects.get(term_id=term_id) except Term.DoesNotExist: raise CommandError('Invalid term_id %d provided.' % term_id) logger.info("Creating Canvas user primary email report for term_id %d...", term_id) report_data = {} for ci in CourseInstance.objects.filter(term=term): if ci.canvas_course_id: logger.info("Retrieving users for canvas_course_id %d", ci.canvas_course_id) users = {} try: users_with_email = get_all_list_data( SDK_CONTEXT, list_users_in_course_users, ci.canvas_course_id, 'email' ) for user in users_with_email: user_id = user['sis_user_id'] if user_id not in users: users[user_id] = {'email': user['email'], 'roles': []} users_with_enrollments = get_all_list_data( SDK_CONTEXT, list_users_in_course_users, ci.canvas_course_id, 'enrollments' ) for user in users_with_enrollments: users[user['sis_user_id']]['roles'].extend( [enrollment['role'] for enrollment in user['enrollments']] ) except CanvasAPIError: logger.exception("Failed to retrieve users for canvas_course_id %d", ci.canvas_course_id) continue for p in Person.objects.filter(univ_id__in=users.keys()): user = users[p.univ_id] if p.email_address != user['email']: for role in user['roles']: if role not in report_data: report_data[role] = [] report_data[role].append([role, p.univ_id, user['email']]) report_path = os.path.join(settings.REPORT_DIR, 'report_user_primary_email.csv') with open(report_path, 'wb') as f: writer = csv.writer(f, dialect='excel') writer.writerow(['Role', 'SIS Email', 'Canvas Email']) for role, rows in report_data.iteritems(): writer.writerows(rows) logger.info("User primary email report complete for term_id %d at %s", term_id, report_path)
def test_get_all_list_data_calls_function_parameter_with_context_args_and_kwargs(self, mock_next): """ Assert that call to get_all_list_data calls function parameter with context, args, and kwargs """ mock_next.return_value = iter([]) mock_function = mock.Mock(name='mock-function') arg1, arg2 = 'arg1', 'arg2' kwargs = {'kwarg1': 'val1', 'kwarg2': 'val2'} utils.get_all_list_data(self.req_ctx, mock_function, arg1, arg2, **kwargs) mock_function.assert_called_once_with(self.req_ctx, arg1, arg2, **kwargs)
def test_get_all_list_data_calls_json_on_function_return_value(self, mock_next): """ Assert that call to get_all_list_data makes call to json method on result of function call """ mock_next.return_value = iter([]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock() mock_function.return_value = mock_response utils.get_all_list_data(self.req_ctx, mock_function) mock_response.json.assert_called_once_with()
def test_get_all_list_data_calls_get_next_with_request_context_and_response(self, mock_next): """ Assert that call to get_all_list_data makes a call to get_next with context and function response """ mock_next.return_value = iter([]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock() mock_function.return_value = mock_response utils.get_all_list_data(self.req_ctx, mock_function) mock_next.assert_called_once_with(self.req_ctx, mock_response)
def get_administered_school_accounts(canvas_user_id, allowed_roles=ADMINISTRATOR_ROLES): cache_key = CACHE_KEY_ACCOUNTS_BY_USER.format(canvas_user_id) school_accounts = cache.get(cache_key) if school_accounts is None: # get all accounts all_canvas_accounts = get_all_list_data( SDK_CONTEXT, get_sub_accounts_of_account, settings.ICOMMONS_COMMON['CANVAS_ROOT_ACCOUNT_ID'], recursive=False) # filter so we only have the school accounts all_school_accounts = { a['id']: a for a in all_canvas_accounts if (a.get('sis_account_id') or '').startswith('school') } # retrieve accounts this user is directly associated with assigned_accounts = get_all_list_data(SDK_CONTEXT, list_accounts, as_user_id=canvas_user_id) # for some reason, the role they're assigned is under admins. grab that # role for all the assigned accounts allowed_accounts = {} for account in assigned_accounts: admins = get_all_list_data(SDK_CONTEXT, list_account_admins, account['id'], user_id=canvas_user_id) if allowed_roles.intersection({a['role'] for a in admins}): allowed_accounts[account['id']] = account # if they're allowed on the root account, they're allowed everywhere if settings.ICOMMONS_COMMON[ 'CANVAS_ROOT_ACCOUNT_ID'] in allowed_accounts: school_accounts = all_school_accounts.values() else: # filter out the accounts where the user does not have the proper # permissions school_accounts = [ acct for id_, acct in all_school_accounts.iteritems() if id_ in allowed_accounts ] logger.debug(u'%s has access to %s', canvas_user_id, [a['sis_account_id'] for a in school_accounts]) cache.set(cache_key, school_accounts) return school_accounts
def test_get_all_list_data_raises_attribute_error_on_paged_results_if_initial_result_not_a_list(self, mock_next): """ Assert that call to get_all_list_data will raise an AttributeError in the event that the initial response is something other than a list (i.e., a json string or dict) """ # Simulate an initial response with 3 additional pages mock_next.return_value = iter([self.build_response_mock(json_data=["test-data"])]) mock_function = mock.Mock(name="mock-function") mock_response = self.build_response_mock(json_data={"dict": "data"}) mock_function.return_value = mock_response with self.assertRaises(AttributeError): utils.get_all_list_data(self.req_ctx, mock_function)
def test_get_all_list_data_raises_attribute_error_on_paged_results_if_initial_result_not_a_list(self, mock_next): """ Assert that call to get_all_list_data will raise an AttributeError in the event that the initial response is something other than a list (i.e., a json string or dict) """ # Simulate an initial response with 3 additional pages mock_next.return_value = iter([ self.build_response_mock(json_data=['test-data']), ]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock(json_data={'dict': 'data'}) mock_function.return_value = mock_response with self.assertRaises(AttributeError): utils.get_all_list_data(self.req_ctx, mock_function)
def _get_pages(context, course_id): page_list = get_all_list_data(context, pages.list_pages_courses, course_id) for page in page_list: details = pages.show_page_courses(context, course_id, page['url']) page['body'] = details.json()['body'] return page_list
def test_get_all_list_data_returns_concatenated_list_of_json_results( self, mock_next): """ Assert that result of call to get_all_list_data is a single concatenated list of json data from series of "next" responses. """ expected_json = [{ 'first': 'json' }, { 'second': 'json' }, { 'third': 'json' }, { 'fourth': 'json' }] # Simulate an initial response with 3 additional pages mock_next.return_value = iter([ self.build_response_mock(json_data=[expected_json[1]]), self.build_response_mock(json_data=[expected_json[2]]), self.build_response_mock(json_data=[expected_json[3]]), ]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock(json_data=[expected_json[0]]) mock_function.return_value = mock_response results = utils.get_all_list_data(self.req_ctx, mock_function) self.assertEqual( results, expected_json, "The json list of data returned by get_all function should be the fully concatenated list of json" )
def get_canvas_site_templates_for_school(school_id): """ Get the Canvas site templates for the given school. First check the cache, if not found construct the Canvas site template dictionairy list by querying CanvasSchoolTemplate and the courses Canvas API to get the Canvas template site name. :param school_id: :return: List of dicts containing data for Canvas site templates for the given school """ cache_key = CACHE_KEY_CANVAS_SITE_TEMPLATES_BY_SCHOOL_ID % school_id templates = cache.get(cache_key) if templates is None: templates = [] for t in CanvasSchoolTemplate.objects.filter(school_id=school_id): canvas_course_id = t.template_id course = get_all_list_data( SDK_CONTEXT, canvas_api_courses.get_single_course_courses, canvas_course_id, None ) templates.append({ 'canvas_course_name': course['name'], 'canvas_course_id': canvas_course_id, 'canvas_course_url': "%s/courses/%d" % (settings.CANVAS_URL, canvas_course_id), 'is_default': t.is_default }) logger.debug("Caching canvas site templates for school_id %s %s", school_id, json.dumps(templates)) cache.set(cache_key, templates) return templates
def api_get_students_list(request_context, course_id): ''' Returns a list of all students in the course. https://canvas.instructure.com/doc/api/courses.html#method.courses.users ''' logging.info("API fetching students for course: %s" % course_id) results = get_all_list_data(request_context, courses.list_users_in_course_users, course_id, "email", enrollment_type="student") # reduce the student data to the subset that we need students = [ { "id": x['id'], # canvas ID for user "sis_user_id": x['sis_user_id'], # huid from student information systems "sortable_name": x['sortable_name'], "name": x['name'], } for x in results ] logging.debug("Students in course: %s" % students) return list(students)
def api_get_submissions(request_context, course_id, assignment_ids): ''' Returns the submission data for each assignment. https://canvas.instructure.com/doc/api/submissions.html#method.submissions_api.index ''' logging.info( "API fetching submissions for course %s and %d assignments (assignment_ids: %s)" % (course_id, len(assignment_ids), ', '.join(map(str, assignment_ids)))) include = "assignment" results = [] for position, assignment_id in enumerate(assignment_ids, start=1): logging.info( "[%d of %d] Fetching submissions for assignment_id: %d..." % (position, len(assignment_ids), assignment_id)) list_data = get_all_list_data( request_context, submissions.list_assignment_submissions_courses, course_id, assignment_id, include) logging.debug("Submissions for assignment %s: %s" % (assignment_id, list_data)) results.append({ "assignment_id": assignment_id, "submissions": list_data }) return results
def _get_users_by_email(email_address, account_id=None, use_cache=True): if not email_address or not email_address.strip(): return [] if account_id is None: account_id = '1' # account ID for root account in Canvas cache_key = CACHE_KEY_USER_IN_ACCOUNT_BY_SEARCH_TERM.format(account_id, email_address) result = cache.get(cache_key) if use_cache else None if not result: kwargs = {'search_term': email_address, 'include': 'email'} try: result = get_all_list_data( SDK_CONTEXT, list_users_in_account, account_id, **kwargs) except CanvasAPIError: logger.error('Unable to lookup users in account {} for email ' 'address {}'.format(account_id, email_address)) raise logger.debug('Canvas user search results for account {} with search ' 'term {}: {}'.format(account_id, email_address, result)) if use_cache: cache.set(cache_key, result) return result
def _list_user_comm_channels(user_id, use_cache=False): """ Note: we don't want to cache by default because we want to immediately pick up changes in user communication channels if they change them, e.g. if they're notified they need to update their communication channels and they attempt to email a list immediately afterwards, we want to pick up that change. Caching can still be done at the level of the mailgun route handler event trigger. """ if not user_id: return [] cache_key = CACHE_KEY_COMM_CHANNELS_BY_CANVAS_USER_ID % user_id result = cache.get(cache_key) if use_cache else None if not result: kwargs = {'user_id': user_id} try: result = get_all_list_data( SDK_CONTEXT, communication_channels.list_user_communication_channels, **kwargs) except CanvasAPIError: logger.error('Unable to get communication channels for ' 'Canvas user {}'.format(user_id)) raise logger.debug('Canvas communication channel results for user {}: ' '{}'.format(user_id, result)) if use_cache: cache.set(cache_key, result) return result
def get_canvas_courses(account_id=None, term_id=None, search_term=None, state=None): """Returns a list of active courses for the given account and term""" try: canvas_courses = get_all_list_data( SDK_CONTEXT, list_active_courses_in_account, account_id=account_id, enrollment_term_id=term_id, search_term=search_term, state=state, ) total_count = len(canvas_courses) logger.info('Found %d courses' % total_count) except Exception as e: message = 'Error listing active courses in account' if isinstance(e, CanvasAPIError): message += ', SDK error details={}'.format(e) logger.exception(message) raise e return canvas_courses
def get_course_list_from_canvas(account_id): """ Get a list of all the active courses for the give account_id. """ course_list = get_all_list_data(SDK_CONTEXT, accounts.list_active_courses_in_account, account_id, with_enrollments=True) return course_list
def get_enrolled_roles_for_user_ids(canvas_course_id, search_results_user_ids): """ Look for any ids returned from the search query that match ids already enrolled in the course. If we find a match, add them to the found_ids. This list will be used in the template to disable the checkbox for ids that are already enrolled in the course and to display Canvas role names. Do not match XIDs. """ canvas_enrollments = get_all_list_data( SDK_CONTEXT, enrollments.list_enrollments_courses, canvas_course_id) # get the updated (or cached) Canvas role list so we can show the right # role labels for these enrollments canvas_roles_by_role_id = get_roles_for_account_id('self') found_ids = defaultdict(list) for enrollment in canvas_enrollments: try: sis_user_id = enrollment['user']['sis_user_id'] except KeyError: continue else: if sis_user_id in search_results_user_ids: enrollment.update( {'canvas_role_label': canvas_roles_by_role_id[ enrollment['role_id']]['label']}) found_ids[sis_user_id].append(enrollment) return found_ids
def get_assignments_list(request_context, course_id): """ Returns a list of assignments for the course. https://canvas.instructure.com/doc/api/assignments.html#method.assignments_api.index """ results = get_all_list_data(request_context, assignments.list_assignments, course_id, "") return list(filter(lambda x: "rubric" in x, results))
def get_administered_school_accounts(canvas_user_id, allowed_roles=ADMINISTRATOR_ROLES): cache_key = CACHE_KEY_ACCOUNTS_BY_USER.format(canvas_user_id) school_accounts = cache.get(cache_key) if school_accounts is None: # get all accounts all_canvas_accounts = get_all_list_data( SDK_CONTEXT, get_sub_accounts_of_account, settings.ICOMMONS_COMMON['CANVAS_ROOT_ACCOUNT_ID'], recursive=True) # filter so we only have the school accounts all_school_accounts = {a['id']: a for a in all_canvas_accounts if (a.get('sis_account_id') or '').startswith('school')} # retrieve accounts this user is directly associated with assigned_accounts = get_all_list_data(SDK_CONTEXT, list_accounts, as_user_id=canvas_user_id) # for some reason, the role they're assigned is under admins. grab that # role for all the assigned accounts allowed_accounts = {} for account in assigned_accounts: admins = get_all_list_data(SDK_CONTEXT, list_account_admins, account['id'], user_id=canvas_user_id) if allowed_roles.intersection({a['role'] for a in admins}): allowed_accounts[account['id']] = account # if they're allowed on the root account, they're allowed everywhere if settings.ICOMMONS_COMMON['CANVAS_ROOT_ACCOUNT_ID'] in allowed_accounts: school_accounts = all_school_accounts.values() else: # filter out the accounts where the user does not have the proper # permissions school_accounts = [acct for id_, acct in all_school_accounts.iteritems() if id_ in allowed_accounts] logger.debug(u'%s has access to %s', canvas_user_id, [a['sis_account_id'] for a in school_accounts]) cache.set(cache_key, school_accounts) return school_accounts
def api_get_assignments_list(request_context, course_id): ''' Returns a list of assignments for the course. https://canvas.instructure.com/doc/api/assignments.html#method.assignments_api.index ''' logging.info("API fetching assignments for course: %s" % course_id) results = get_all_list_data(request_context, assignments.list_assignments, course_id, '') logging.debug("Assignments List: %s" % [r['id'] for r in results]) return results
def get_enrollments_from_canvas_section(canvas_section_id): """ get the list of enrollments for the canvas section """ enrollment_list = get_all_list_data(SDK_CONTEXT, enrollments.list_enrollments_sections, canvas_section_id) sis_user_id_list = [] for enrollment in enrollment_list: sis_user_id = enrollment['user'].get('sis_user_id') if sis_user_id: sis_user_id_list.append(sis_user_id) return sis_user_id_list
def get_assignments_list(request_context, course_id): ''' Returns a list of assignments for the course. https://canvas.instructure.com/doc/api/assignments.html#method.assignments_api.index ''' keys = ('id', 'name', 'due_at', 'rubric', 'rubric_settings' ) # reduce the clutter results = get_all_list_data(request_context, assignments.list_assignments, course_id, '') my_assignments = list(filter(lambda x: 'rubric' in x, results)) return [{k: assignment[k] for k in keys} for assignment in my_assignments]
def get_sections_list(request_context, course_id): """ Returns a list of sections for the course. https://canvas.instructure.com/doc/api/sections.html#method.sections_api.index """ results = get_all_list_data(request_context, sections.list_course_sections, course_id, "students") try: returned_sections = filter(lambda x: x['students'] is not None, results) except KeyError: return [] return list(returned_sections)
def get_external_tool_id(api_auth, canvas_course_id): context = _get_context(api_auth) tools = get_all_list_data(context, external_tools.list_external_tools_courses, canvas_course_id) domain = settings.EXTERNAL_TOOL_DOMAIN tool_id = next((x['id'] for x in tools if x.get('domain', None) == domain), None) if not tool_id: tool = external_tools.create_external_tool_courses( context, canvas_course_id, 'Open edX at Harvard', 'anonymous', settings.EDX_LTI_KEY, settings.EDX_LTI_SECRET, domain=domain ) tool_id = tool.json().get('id', None) return tool_id
def get_account_list_from_canvas(): """ Get the list of all sub-accounts of the Harvard root account and build a list of the account id's. """ sub_account_list = get_all_list_data(SDK_CONTEXT, accounts.get_sub_accounts_of_account, settings.MANAGE_SECTIONS.get('ROOT_ACCOUNT', 1), recursive=True) sub_list = [] for a in sub_account_list: sub_account_id = a.get('id') if sub_account_id: sub_list.append(sub_account_id) return sorted(sub_list)
def fetch_allevents(id): '''Fetches assignments and events for course with given id''' if oauthtoken is None: raise_exception() # Create request context req_context = request_context.RequestContext(oauthtoken, baseurl) # Create separate parameters for assignment event and calendar event requests assignparams = { "all_events": "true", "type": "assignment", "context_codes[]": "course_" + str(id) } eventparams = { "all_events": "true", "context_codes[]": "course_" + str(id) } # Make requests, making sure to get all the data assignments = get_all_list_data(req_context, base.get, baseurl + "/v1/calendar_events", params=assignparams, auth_token=oauthtoken) events = get_all_list_data(req_context, base.get, baseurl + "/v1/calendar_events", params=eventparams, auth_token=oauthtoken) # Merge lists allevents = assignments + events # Sort and filter, separating into a tuple filtered = filter_undated(sort_events(allevents)) return filtered
def fetch_allevents(id): '''Fetches assignments and events for course with given id''' if oauthtoken is None: raise_exception() # Create request context req_context = request_context.RequestContext(oauthtoken, baseurl) # Create separate parameters for assignment event and calendar event requests assignparams = {"all_events": "true", "type" : "assignment", "context_codes[]" : "course_" + str(id)} eventparams = {"all_events": "true", "context_codes[]" : "course_" + str(id)} # Make requests, making sure to get all the data assignments = get_all_list_data(req_context, base.get, baseurl + "/v1/calendar_events", params=assignparams, auth_token=oauthtoken) events = get_all_list_data(req_context, base.get, baseurl + "/v1/calendar_events", params=eventparams, auth_token=oauthtoken) # Merge lists allevents = assignments + events # Sort and filter, separating into a tuple filtered = filter_undated(sort_events(allevents)) return filtered
def test_get_all_list_data_returns_initial_response_list_json_when_response_has_no_paged_results(self, mock_next): """ Assert that result of call to get_all_list_data for a request that's a list of data and has no paged results returns the json list. """ expected_json = [{'first': 'json'}] mock_next.return_value = iter([]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock(json_data=expected_json) mock_function.return_value = mock_response results = utils.get_all_list_data(self.req_ctx, mock_function) self.assertEqual( results, expected_json, "The json data returned by get_all function should match up with expected_json list")
def get_courses_for_account_in_term(account_id, enrollment_term_id, include_sections=False): kwargs = { 'account_id': account_id, 'enrollment_term_id': enrollment_term_id, } if include_sections: kwargs['include'] = 'sections' try: result = get_all_list_data(SDK_CONTEXT, accounts.list_active_courses_in_account, **kwargs) except CanvasAPIError: logger.error('Unable to get courses for account {}, term {}'.format(account_id, enrollment_term_id)) raise return result
def fetch_assigngroups(id): '''Fetches assignment groups for course with given id''' if oauthtoken is None: raise_exception() # Create request context req_context = request_context.RequestContext(oauthtoken, baseurl) # URL for request url = baseurl + "/v1/courses/" + str(id) + "/assignment_groups" # Make request, making sure to get all results groups = get_all_list_data(req_context, base.get, url, auth_token=oauthtoken) return groups
def get_account_list_from_canvas(): """ Get the list of all sub-accounts of the Harvard root account and build a list of the account id's. """ sub_account_list = get_all_list_data(SDK_CONTEXT, accounts.get_sub_accounts_of_account, settings.MANAGE_SECTIONS.get( 'ROOT_ACCOUNT', 1), recursive=True) sub_list = [] for a in sub_account_list: sub_account_id = a.get('id') if sub_account_id: sub_list.append(sub_account_id) return sorted(sub_list)
def get_submissions_with_rubric_assessments(request_context, course_id, assignment_ids): ''' Returns the submission and rubric assessment data for each assignment. https://canvas.instructure.com/doc/api/submissions.html#method.submissions_api.index ''' include = "rubric_assessment" results = [] for assignment_id in assignment_ids: list_data = get_all_list_data( request_context, submissions.list_assignment_submissions_courses, course_id, assignment_id, include) results.append({ "assignment_id": assignment_id, "submissions": list_data, }) return results
def get_students_list(request_context, course_id): ''' Returns a list of all students in the course. https://canvas.instructure.com/doc/api/courses.html#method.courses.users ''' results = get_all_list_data(request_context, courses.list_users_in_course_users, course_id, "email", enrollment_type="student") students = sorted([{ "sortable_name": x['sortable_name'], "id": x['id'] } for x in results], key=lambda x: x['sortable_name']) return list(students)
def get_canvas_sections_list(canvas_course_id): """ Get a list of the sections exluding the primary section for the given canvas_course_id. We don't want primary sections. Primary sections are ones where the there exists an sis_section_id that the same as the sis_course_id. We also don't want sections with no name. """ canvas_course_sections = get_all_list_data(SDK_CONTEXT, sections.list_course_sections, canvas_course_id) canvas_course_sections_list = [] for canvas_section in canvas_course_sections: canvas_section_id = canvas_section.get('id') canvas_section_name = canvas_section.get('name') sis_course_id = canvas_section.get('sis_course_id') sis_section_id = canvas_section.get('sis_section_id') if canvas_section_id and (sis_course_id != sis_section_id) and canvas_section_name: canvas_course_sections_list.append(canvas_section_id) return canvas_course_sections_list
def test_get_all_list_data_returns_concatenated_list_of_json_results(self, mock_next): """ Assert that result of call to get_all_list_data is a single concatenated list of json data from series of "next" responses. """ expected_json = [ {'first': 'json'}, {'second': 'json'}, {'third': 'json'}, {'fourth': 'json'}] # Simulate an initial response with 3 additional pages mock_next.return_value = iter([ self.build_response_mock(json_data=[expected_json[1]]), self.build_response_mock(json_data=[expected_json[2]]), self.build_response_mock(json_data=[expected_json[3]]), ]) mock_function = mock.Mock(name='mock-function') mock_response = self.build_response_mock(json_data=[expected_json[0]]) mock_function.return_value = mock_response results = utils.get_all_list_data(self.req_ctx, mock_function) self.assertEqual( results, expected_json, "The json list of data returned by get_all function should be the fully concatenated list of json")
def get_enrolled_roles_for_user_ids(canvas_course_id, search_results_user_ids): """ Look for any ids returned from the search query that match ids already enrolled in the course. If we find a match, add them to the found_ids. This list will be used in the template to disable the checkbox for ids that are already enrolled in the course. Do not match XIDs. """ canvas_enrollments = get_all_list_data( SDK_CONTEXT, enrollments.list_enrollments_courses, canvas_course_id) found_ids = defaultdict(list) for enrollment in canvas_enrollments: try: sis_user_id = enrollment['user']['sis_user_id'] except KeyError: continue else: if sis_user_id in search_results_user_ids: found_ids[sis_user_id].append(enrollment['role']) return found_ids
def get_external_tool_id(api_auth, canvas_course_id): context = _get_context(api_auth) tools = get_all_list_data(context, external_tools.list_external_tools_courses, canvas_course_id) domain = settings.EXTERNAL_TOOL_DOMAIN tool_id = next((x['id'] for x in tools if x.get('domain', None) == domain), None) if not tool_id: tool = external_tools.create_external_tool_courses( context, canvas_course_id, 'Open edX at Harvard', 'anonymous', settings.EDX_LTI_KEY, settings.EDX_LTI_SECRET, domain=domain) tool_id = tool.json().get('id', None) return tool_id
# SDK script to get the numbers of page views and participations in a Canvas course from analytics using the SDK import csv from canvas_sdk import RequestContext from pprint import pprint from canvas_sdk.methods import analytics from canvas_sdk.utils import get_all_list_data from secure import oauth_token data=[] canvas_url = 'https://canvas.harvard.edu/api/' # for production SDK_CONTEXT = RequestContext(oauth_token, canvas_url) # use the SDK to make the request to the Canvas API to the canvas instance defined in canvas_url response = get_all_list_data(SDK_CONTEXT, analytics.get_course_level_student_summary_data, course_id=495) # save the response from the SDK call for a particular course_id # use the API to get move through all pages length = len(response) # save the id, participation, and page_view data from the response for c in range(0,length): print c user_id = response[c]['id'] participations = response[c]['participations'] page_views = response[c]['page_views'] data.append([user_id, participations, page_views]) # write the data saved from the response to a CSV with open('analytics.csv', 'wb') as f:
def main(request): #get the course ID of current course canvas_course_id = request.session['LTI_LAUNCH'].get('custom_canvas_course_id') logger.debug('////////////////////////////////////>') logger.debug('canvas_course_id=%s' % canvas_course_id) logger.debug('////////////////////////////////////>') #call to get course name for roster header canvas_course_name = get_all_list_data(SDK_CONTEXT, courses.get_single_course_courses, canvas_course_id, "all_courses") logger.debug('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>') logger.debug(canvas_course_name) logger.debug('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>') #get a list of STUDENTS in the current course, we will use only the user_id from the list #the 'avatar_url' is just a required option, and we will actually use the avatar_url from the profile list below enrollmentGet = {} try: enrollmentGet = get_all_list_data(SDK_CONTEXT, courses.list_users_in_course_users, canvas_course_id, 'avatar_url', enrollment_type='student') except CanvasAPIError as api_error: logger.debug((canvas_course_id, api_error)) #get a list of TEACHERS in the current course. Should be automatically hidden with js in lti_view.thml, and can be toggled with a switch faculty = {} try: faculty = get_all_list_data(SDK_CONTEXT, courses.list_users_in_course_users, canvas_course_id, 'avatar_url', enrollment_type='teacher') except CanvasAPIError as api_error: logger.debug((canvas_course_id, api_error)) #for each student user in the course, get their user_id userID = [] for d in enrollmentGet: userIdSingle = d['id'] userID.append(userIdSingle) logger.debug('*********************************************>') logger.debug('userID=%s' %userID) logger.debug('*********************************************>') facultyID = [] for d in faculty: facultyIdSingle = d['id'] facultyID.append(facultyIdSingle) logger.debug('++++++++++++++++++++++++++++++++++++++++++++++++>') logger.debug('facultyID=%s' %facultyID) logger.debug('++++++++++++++++++++++++++++++++++++++++++++++++>') #Run the user profile call for each student user_id we just got, #and use what we need in the html userGet = [] for l in userID: try: studentFull = get_all_list_data(SDK_CONTEXT, users.get_user_profile, l) userGet.append(studentFull) except CanvasAPIError as api_error: logger.debug('CanvasAPIError in get_all_list_data enrollments.list_enrollments_courses call for canvas_course_id=%s. Exception=%s:' % (canvas_course_id, api_error)) facultyGet = [] for l in facultyID: try: facultyFull = get_all_list_data(SDK_CONTEXT, users.get_user_profile, l) facultyGet.append(facultyFull) except CanvasAPIError as api_error: logger.debug('CanvasAPIError in get_all_list_data enrollments.list_enrollments_courses call for canvas_course_id=%s. Exception=%s:' % (canvas_course_id, api_error)) #dictionary definitions for the lti_view html return render(request, 'canvasPR_app/lti_view.html', {'request': request, 'pages': enrollmentGet, 'userGet': userGet, 'facultyGet': facultyGet, 'courseName': canvas_course_name})
def get_courses(api_auth): context = _get_context(api_auth) get_all_list_data(context, courses.list_your_courses, 'term')
def get_module_list(api_token, canvas_course_id): context = _get_context(api_token) return get_all_list_data(context, modules.list_modules, canvas_course_id, 'items')
def remove_user(request): canvas_course_id = request.POST.get('canvas_course_id') sis_user_id = request.POST.get('sis_user_id') canvas_role_id = request.POST.get('canvas_role_id') user_role_id = request.POST.get('user_role_id') try: course_instance_id = request.LTI['lis_course_offering_sourcedid'] except KeyError as e: return lti_key_error_response(request, e) user_role = get_user_role_if_permitted(course_instance_id, user_role_id) if user_role is None: return JsonResponse( {'result': 'failure', 'message': 'Error: The specified user role {} is not valid.' .format(user_role_id)}, status=500) if int(user_role.canvas_role_id) != int(canvas_role_id): logger.exception( u'The specified Canvas role %s does not correspond with user role ' u'%s record\'s Canvas role (%s).', canvas_role_id, user_role_id, user_role.canvas_role_id) return JsonResponse( {'result': 'failure', 'message': 'Error: The specified canvas role {} is not valid.' .format(canvas_role_id)}, status=500) # start by getting all the enrollments for this user user_id = 'sis_user_id:%s' % sis_user_id try: user_enrollments = get_all_list_data( SDK_CONTEXT, enrollments.list_enrollments_users, user_id) except CanvasAPIError as api_error: logger.exception( u"CanvasAPIError trying to get enrollments for user %s", sis_user_id, api_error) return JsonResponse( {'result': 'failure', 'message': 'Error: There was a problem getting enrollments for ' 'the user.'}, status=500) else: # create a filtered list of just the users enrollments for the course # matching the canvas_course_id and the canvas role being removed user_enrollments_to_remove = [ enrollment['id'] for enrollment in user_enrollments if (int(enrollment['course_id']) == int(canvas_course_id) and int(enrollment['role_id']) == int(canvas_role_id))] # Remove the user from all Canvas sections in course for enrollment_id in user_enrollments_to_remove: try: enrollments.conclude_enrollment(SDK_CONTEXT, canvas_course_id, enrollment_id, task='delete') except CanvasAPIError as api_error: logger.exception( u'Canvas API Error trying to delete user %s with enrollment id ' u'%s from course instance %s: %s', sis_user_id, enrollment_id, course_instance_id, api_error ) return JsonResponse( {'result': 'failure', 'message': 'Error: There was a problem in deleting the user'}, status=500) # update canvas api caches canvas_api_helper_courses.delete_cache(canvas_course_id=canvas_course_id) canvas_api_helper_enrollments.delete_cache(canvas_course_id) canvas_api_helper_sections.delete_cache(canvas_course_id) logger.debug( u'Now removing user with user_id=%s from course_instance_id=%s in ' u'CourseManager DB.', sis_user_id, course_instance_id ) # find the enrollment in question model_class = get_course_member_class(user_role) try: enrollment = model_class.objects.get( course_instance_id=course_instance_id, user_id=sis_user_id) except model_class.DoesNotExist: logger.exception(u'Unable to remove user %s from %s membership in ' u'course %s: no such membership exists.', sis_user_id, model_class._meta.db_table, course_instance_id) return JsonResponse( {'result': 'failure', 'message': 'Error: There was a problem in deleting the user'}, status=500) # now delete it try: enrollment.delete() except Exception as e: logger.exception( u"Error in deleting user=%s from course_instance_id=%s: %s", sis_user_id, course_instance_id, e.message ) return JsonResponse( {'result': 'failure', 'message': 'Error: There was a problem in deleting the user'}, status=500) # Record the delete in the audit log audit_logger.info( u'Course Enrollee=%s was deleted by user=%s for canvas_course_id=%s', sis_user_id, request.user.username, canvas_course_id) response_data = { 'result': 'success', 'message': 'User successfully removed from course', } return JsonResponse(response_data)
def get_enrollments_added_through_tool(sis_course_id): """ This method fetches the primary section enrollments for this course and then filters out the course enrollees that are fed via sis import feed process or cross-registration. """ logger.debug(u'get_enrollments_added_through_tool(course_instance_id=%s)', sis_course_id) section_id_param = 'sis_section_id:' + str(sis_course_id) try: canvas_enrollments = get_all_list_data( SDK_CONTEXT, enrollments.list_enrollments_sections, section_id_param) except CanvasAPIError as api_error: logger.error( u'CanvasAPIError in get_all_list_data call for sis_course_id=%s. ' u'Exception=%s:', sis_course_id, api_error) return [] logger.debug(u"size of enrollees in canvas= %s" % len(canvas_enrollments)) # get the list of enrolles from Course Manager DB, who are eligible to be # deleted via this tool. This is achieved by using a filter to exclude # users with values equal to e.g. 'xmlfeed','fasfeed', or 'xreg_map' in the # 'SOURCE' column, which indicates that these users were fed from the # registrar feed or xreg. Note: The code is excluding any source containing # 'feed' to capture various feed sources like 'xmlfeed','fasfeed','icfeed' # etc. eligible_ids = set() query = (Q(course_instance_id=sis_course_id) & (Q(source__isnull=True) | ~(Q(source__icontains='feed') | Q(source='xreg_map')))) for model in COURSE_MEMBER_CLASSES: try: ids = list(model.objects.filter(query).values_list('user_id', 'role_id')) except Exception as e: logger.exception(u'unable to look up course members in %s: %s', model._meta.db_table, e) else: logger.debug(u'eligible %s members = %s', model._meta.db_table, ids) eligible_ids.update(ids) logger.debug(u'full set of eligible user/role ids: %s', eligible_ids) # get a mapping of canvas role_id to UserRole ids canvas_role_to_user_role = get_canvas_to_user_role_id_map() # get the updated (or cached) Canvas role list so we can show the right # Canvas role labels for the enrollments canvas_roles_by_role_id = get_roles_for_account_id('self') # Further filter users to remove users who may not yet be be in canvas. # For the moment we are treating COURSEMANAGER as the single source of truth # for who should be enrolled in a course, but we also need to get Canvas # enrollee attributes like enrollee_id, which is required for deleting the # user further on. Hence we disallow deletion of anyone who's not also # accurately represented in Canvas. filtered_enrollments = [] for enrollment in canvas_enrollments: if enrollment.get('user'): # If sis_user_id exists, use it; if not, use login_id; if neither # exist then log it and do not include user_id = (enrollment['user'].get('sis_user_id') or enrollment['user'].get('login_id')) user_role_id = canvas_role_to_user_role[enrollment['role_id']] if user_id and (user_id, user_role_id) in eligible_ids: enrollment.update({ 'user_role_id': user_role_id, 'canvas_role_label': canvas_roles_by_role_id.get( enrollment['role_id'])['label']}) filtered_enrollments.append(enrollment) logger.debug(u'MP filter out registrar fed: Allowing (%s, %s)', user_id, user_role_id) else: # Log the users not yet in Canvas or who do not match because # they're missing an sis_user_id or login_id. These users will # not be available for removal in the tool. Assumes all users # have sortable_name and assumes sis_user_id is the source of # truth in Canvas (see comment above re:TLT-705) logger.info( u'Manage People: Canvas %s (Canvas role_id %s, ' u'user_role_id %s) enrollment for user %s (CM user id %s) ' u'was either not found in the Coursemanager DB, or was ' u'registrar-fed. Not including it in the results list.', enrollment['role'], enrollment['role_id'], canvas_role_to_user_role[enrollment['role_id']], enrollment['user'].get('sortable_name'), enrollment['user'].get('sis_user_id')) else: # Problem with canvas enrollee data structure logger.info( u'Manage People: Canvas enrollment does not have user ' u'information associated with it. Enrollment info: %s', enrollment) # Sort the users by sortable_name filtered_enrollments.sort(key=lambda x: x['user']['sortable_name']) logger.debug(u'size of filtered and sorted enrollments= %s', len(filtered_enrollments)) # add custom display badge information for enrollees to the filtered # Canvas enrollment objects badge information is used on user_form to # identify different university ID types and distinguish multiple IDs # for the same user (i.e. enrollments where the user name is identical) user_id_list = [enrollment['user'].get('sis_user_id') for enrollment in filtered_enrollments] user_badge_info_mapping = get_badge_info_for_users(user_id_list) for enrollment in filtered_enrollments: user_id = enrollment['user'].get('sis_user_id') if user_id and user_id in user_badge_info_mapping: enrollment['badge_label_name'] = user_badge_info_mapping[user_id] # Check if the given enrollment can be deleted in the current tool # If it can not, we want to disable the delete option in the template user_role = get_user_role_if_permitted(sis_course_id, enrollment['user_role_id']) enrollment['can_be_deleted'] = True if user_role is not None else False return filtered_enrollments