def _assert_enterprise_courses_api_response(self, content_ids, content_metadata, expected_count):

assert len(content_ids) == len(content_metadata)<EOL>assert expected_count == len(content_metadata)<EOL>for item in content_metadata:<EOL><INDENT>assert get_content_metadata_item_id(item) in content_ids<EOL><DEDENT>

DRY method to verify the enterprise courses api response.


assert len(responses.calls) == expected_count<EOL>

DRY helper for verifying request counts.


return requests.compat.urljoin(URL_BASE_NAMES[base_name].API_BASE_URL, path)<EOL>

Build a URL for the relevant API named by base_name.

Args:
    base_name (str): A name to identify the root URL by
    path (str): The URL suffix to append to the base path


assert self.client.login(username=self.user.username, password="<STR_LIT>")<EOL>


client = client_mock.return_value<EOL>client.get_course_modes.return_value = self.dummy_demo_course_modes<EOL>client.get_course_enrollment.return_value = None<EOL>


def _setup_ecommerce_client(self, client_mock, total=<NUM_LIT:50>):

dummy_price_details_mock = mock.MagicMock()<EOL>dummy_price_details_mock.return_value = {<EOL>'<STR_LIT>': total,<EOL>}<EOL>price_details_mock = mock.MagicMock()<EOL>method_name = '<STR_LIT>'<EOL>attrs = {method_name: dummy_price_details_mock}<EOL>price_details_mock.configure_mock(**attrs)<EOL>client_mock.return_value = price_details_mock<EOL>


def _setup_registry_mock(self, registry_mock, provider_id):

registry_mock.get.return_value.configure_mock(provider_id=provider_id)<EOL>


def _check_expected_enrollment_page(self, response, expected_context):

fake_organization = FAKE_COURSE['<STR_LIT>'][<NUM_LIT:0>]<EOL>default_context = {<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': "<STR_LIT>",<EOL>'<STR_LIT>': "<STR_LIT>",<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': FAKE_COURSE_RUN['<STR_LIT:title>'],<EOL>'<STR_LIT>': FAKE_COURSE_RUN['<STR_LIT>'],<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': parse(FAKE_COURSE_RUN['<STR_LIT:start>']).strftime('<STR_LIT>'),<EOL>'<STR_LIT>': False,<EOL>'<STR_LIT>': FAKE_COURSE_RUN['<STR_LIT:image>']['<STR_LIT:src>'],<EOL>'<STR_LIT>': (<EOL>"<STR_LIT>"<EOL>"<STR_LIT>"<EOL>"<STR_LIT>"<EOL>"<STR_LIT>"<EOL>"<STR_LIT>"<EOL>),<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': fake_organization['<STR_LIT>'],<EOL>'<STR_LIT>': fake_organization['<STR_LIT:name>'],<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': settings.LMS_SEGMENT_KEY,<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': True,<EOL>}<EOL>default_context.update(expected_context)<EOL>assert response.status_code == <NUM_LIT:200><EOL>for key, value in default_context.items():<EOL><INDENT>assert response.context[key] == value<EOL><DEDENT>

Check the response was successful, and contains the expected content.


self.user = UserFactory.create(is_staff=True, is_active=True)<EOL>self.user.set_password("<STR_LIT>")<EOL>self.user.save()<EOL>self.client = Client()<EOL>self.demo_course_1 = FAKE_PROGRAM_RESPONSE3['<STR_LIT>'][<NUM_LIT:0>]<EOL>self.demo_course_2 = FAKE_PROGRAM_RESPONSE3['<STR_LIT>'][<NUM_LIT:1>]<EOL>self.demo_course_id1 = FAKE_PROGRAM_RESPONSE3['<STR_LIT>'][<NUM_LIT:0>]['<STR_LIT:key>']<EOL>self.demo_course_id2 = FAKE_PROGRAM_RESPONSE3['<STR_LIT>'][<NUM_LIT:1>]['<STR_LIT:key>']<EOL>self.demo_course_ids = [self.demo_course_id1, self.demo_course_id2]<EOL>self.dummy_program_uuid = FAKE_PROGRAM_RESPONSE3['<STR_LIT>']<EOL>self.dummy_program = FAKE_PROGRAM_RESPONSE3<EOL>super(TestProgramEnrollmentView, self).setUp()<EOL>


with mock.patch('<STR_LIT>') as mock_render:<EOL><INDENT>mock_render.return_value = HttpResponse()<EOL>self.client.get(url)<EOL>assert mock_render.call_args_list[<NUM_LIT:0>][<NUM_LIT:1>]['<STR_LIT:status>'] == <NUM_LIT><EOL><DEDENT>

Mock the render method, run a GET, and assert it returns 404.


client = client_mock.return_value<EOL>client.get_program_course_keys.return_value = self.demo_course_ids<EOL>client.get_program_by_uuid.return_value = self.dummy_program<EOL>


def _setup_program_data_extender(self, extender_mock, course_overrides=None):

<EOL>dummy_program_extended = copy.deepcopy(self.dummy_program)<EOL>dummy_course_extended_1 = copy.deepcopy(self.demo_course_1)<EOL>dummy_course_extended_2 = copy.deepcopy(self.demo_course_2)<EOL>if course_overrides:<EOL><INDENT>dummy_course_extended_1.update(course_overrides)<EOL>dummy_course_extended_2.update(course_overrides)<EOL><DEDENT>dummy_course_extended_1['<STR_LIT>'][<NUM_LIT:0>].update({"<STR_LIT>": False, "<STR_LIT>": None})<EOL>dummy_course_extended_2['<STR_LIT>'][<NUM_LIT:0>].update({"<STR_LIT>": False, "<STR_LIT>": None})<EOL>dummy_program_extended.update({<EOL>"<STR_LIT>": [<EOL>dummy_course_extended_1,<EOL>dummy_course_extended_2,<EOL>],<EOL>"<STR_LIT>": {<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>"<STR_LIT>": <NUM_LIT:50>,<EOL>"<STR_LIT>": True,<EOL>"<STR_LIT>": <NUM_LIT>,<EOL>"<STR_LIT>": <NUM_LIT>,<EOL>},<EOL>"<STR_LIT>": <NUM_LIT>,<EOL>"<STR_LIT>": True,<EOL>"<STR_LIT>": [<EOL>"<STR_LIT>",<EOL>"<STR_LIT>",<EOL>],<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>})<EOL>extender_mock.return_value.extend.return_value = dummy_program_extended<EOL>return extender_mock<EOL>

Sets up the `ProgramDataExtender` mock, a utility from the edx-platform.


def _setup_get_data_sharing_consent(self, client_mock, required):

client_mock.return_value.consent_required.return_value = required<EOL>

Sets up the ``get_data_sharing_consent`` function mock.


client_mock.return_value = (program_details, program_enrollment_details,)<EOL>

Sets up the ``enterprise.views.ProgramEnrollmentView.get_base_details`` function mock.


default_context = {}<EOL>default_context.update(expected_context)<EOL>assert response.status_code == <NUM_LIT:200><EOL>for key, value in default_context.items():<EOL><INDENT>assert response.context[key] == value<EOL><DEDENT>

Check that the response was successful, and contains the expected content.


def _assert_enterprise_linking_messages(self, response, user_is_active=True):

Verify response messages for a learner when he/she is linked with an
enterprise depending on whether the learner has activated the linked
account.


def _assert_get_returns_404_with_mock(self, url, get_params):

with mock.patch('<STR_LIT>') as mock_render:<EOL><INDENT>mock_render.return_value = HttpResponse()<EOL>self.client.get(url, get_params)<EOL>assert mock_render.call_args_list[<NUM_LIT:0>][<NUM_LIT:1>]['<STR_LIT:status>'] == <NUM_LIT><EOL><DEDENT>


def _assert_post_returns_404_with_mock(self, url, get_params):

with mock.patch('<STR_LIT>') as mock_render:<EOL><INDENT>mock_render.return_value = HttpResponse()<EOL>self.client.post(url, get_params)<EOL>assert mock_render.call_args_list[<NUM_LIT:0>][<NUM_LIT:1>]['<STR_LIT:status>'] == <NUM_LIT><EOL><DEDENT>

Mock the render method, run a POST, and assert it returns 404.


Returns text to be used paragraphs of data sharing consent page


@contextmanager<EOL>def transmit_learner_data_context(command_kwargs=None, certificate=None, self_paced=False, end_date=None, passed=False):

if command_kwargs is None:<EOL><INDENT>command_kwargs = {}<EOL><DEDENT>testcase = TestTransmitLearnerData(methodName='<STR_LIT>')<EOL>testcase.setUp()<EOL>stub_transmit_learner_data_apis(testcase, certificate, self_paced, end_date, passed)<EOL>command_args = ('<STR_LIT>', testcase.api_user.username)<EOL>if '<STR_LIT>' in command_kwargs:<EOL><INDENT>command_kwargs['<STR_LIT>'] = testcase.enterprise_customer.uuid<EOL><DEDENT>with mock.patch('<STR_LIT>', mock.Mock()):<EOL><INDENT>with freeze_time(NOW):<EOL><INDENT>yield (command_args, command_kwargs)<EOL><DEDENT><DEDENT>testcase.tearDown()<EOL>

Sets up all the data and context wrappers required to run the transmit_learner_data management command.


action = '<STR_LIT>'<EOL>action2 = '<STR_LIT>'<EOL>if expected_completion['<STR_LIT>'] == NOW_TIMESTAMP:<EOL><INDENT>degreed_timestamp = '<STR_LIT>'.format(NOW_TIMESTAMP_FORMATTED)<EOL><DEDENT>elif expected_completion['<STR_LIT>'] == PAST_TIMESTAMP:<EOL><INDENT>degreed_timestamp = '<STR_LIT>'.format(PAST_TIMESTAMP_FORMATTED)<EOL><DEDENT>else:<EOL><INDENT>degreed_timestamp = '<STR_LIT:null>'<EOL>action = '<STR_LIT>'<EOL>action2 = action<EOL><DEDENT>degreed_output_template = (<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>)<EOL>sapsf_output_template = (<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>)<EOL>expected_output = [<EOL>"<STR_LIT>"<EOL>"<STR_LIT>",<EOL>"<STR_LIT>" + sapsf_output_template.format(<EOL>user_id='<STR_LIT>',<EOL>course_id=COURSE_KEY,<EOL>provider_id="<STR_LIT>",<EOL>**expected_completion<EOL>),<EOL>"<STR_LIT>".format(action),<EOL>"<STR_LIT>" + sapsf_output_template.format(<EOL>user_id='<STR_LIT>',<EOL>course_id=COURSE_ID,<EOL>provider_id="<STR_LIT>",<EOL>**expected_completion<EOL>),<EOL>"<STR_LIT>".format(action2),<EOL>"<STR_LIT>" + sapsf_output_template.format(<EOL>user_id='<STR_LIT>',<EOL>course_id=COURSE_KEY,<EOL>provider_id="<STR_LIT>",<EOL>**expected_completion<EOL>),<EOL>"<STR_LIT>".format(action),<EOL>"<STR_LIT>" + sapsf_output_template.format(<EOL>user_id='<STR_LIT>',<EOL>course_id=COURSE_ID,<EOL>provider_id="<STR_LIT>",<EOL>**expected_completion<EOL>),<EOL>"<STR_LIT>".format(action2),<EOL>"<STR_LIT>"<EOL>"<STR_LIT>",<EOL>"<STR_LIT>"<EOL>"<STR_LIT>",<EOL>"<STR_LIT>" + degreed_output_template.format(<EOL>user_email='<STR_LIT>',<EOL>course_id=COURSE_KEY,<EOL>timestamp=degreed_timestamp<EOL>),<EOL>"<STR_LIT>".format(action),<EOL>"<STR_LIT>" + degreed_output_template.format(<EOL>user_email='<STR_LIT>',<EOL>course_id=COURSE_ID,<EOL>timestamp=degreed_timestamp<EOL>),<EOL>"<STR_LIT>".format(action2),<EOL>"<STR_LIT>" + degreed_output_template.format(<EOL>user_email='<STR_LIT>',<EOL>course_id=COURSE_KEY,<EOL>timestamp=degreed_timestamp<EOL>),<EOL>"<STR_LIT>".format(action),<EOL>"<STR_LIT>" + degreed_output_template.format(<EOL>user_email='<STR_LIT>',<EOL>course_id=COURSE_ID,<EOL>timestamp=degreed_timestamp<EOL>),<EOL>"<STR_LIT>".format(action2),<EOL>"<STR_LIT>"<EOL>"<STR_LIT>"<EOL>]<EOL>return expected_output<EOL>

Returns the expected JSON record logged by the ``transmit_learner_data`` command.


with LogCapture(level=logging.INFO) as log_capture:<EOL><INDENT>call_command('<STR_LIT>')<EOL>for index, message in enumerate(expected_messages):<EOL><INDENT>assert message in log_capture.records[index].getMessage()<EOL><DEDENT><DEDENT>

DRY method to verify log messages for management command "unlink_inactive_sap_learners".


def _assert_role_assignments(self, user, role_name, user_role_assignment_count, is_feature_role=False):

role_class = SystemWideEnterpriseRole<EOL>role_assignment_class = SystemWideEnterpriseUserRoleAssignment<EOL>if is_feature_role:<EOL><INDENT>role_class = EnterpriseFeatureRole<EOL>role_assignment_class = EnterpriseFeatureUserRoleAssignment<EOL><DEDENT>enterprise_role = role_class.objects.get(name=role_name)<EOL>user_role_assignments = role_assignment_class.objects.filter(<EOL>user=user,<EOL>role=enterprise_role<EOL>)<EOL>self.assertEqual(user_role_assignments.count(), user_role_assignment_count)<EOL>

Verify expected role assignment records are created for specific role.


self.user = UserFactory.create(is_staff=True, is_active=True)<EOL>self.user.set_password("<STR_LIT>")<EOL>self.user.save()<EOL>self.client = Client()<EOL>self.template = EnrollmentNotificationEmailTemplate.objects.create(<EOL>plaintext_template='<STR_LIT>',<EOL>html_template=(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'<EOL>),<EOL>subject_line='<STR_LIT>',<EOL>enterprise_customer=EnterpriseCustomerFactory(),<EOL>)<EOL>super(TestPreviewTemplateView, self).setUp()<EOL>


super(BaseTestEnterpriseCustomerManageLearnersView, self).setUp()<EOL>self.user = UserFactory.create(is_staff=True, is_active=True, id=<NUM_LIT:1>)<EOL>self.user.set_password("<STR_LIT>")<EOL>self.user.save()<EOL>self.enterprise_customer = EnterpriseCustomerFactory()<EOL>self.default_context = {<EOL>"<STR_LIT>": True,<EOL>"<STR_LIT>": self.enterprise_customer._meta,<EOL>"<STR_LIT:user>": self.user<EOL>}<EOL>self.view_url = reverse(<EOL>"<STR_LIT>" + enterprise_admin.utils.UrlNames.MANAGE_LEARNERS,<EOL>args=(self.enterprise_customer.uuid,)<EOL>)<EOL>self.client = Client()<EOL>self.context_parameters = EnterpriseCustomerManageLearnersView.ContextParameters<EOL>

Test set up - installs common dependencies.


assert PendingEnterpriseCustomerUser.objects.filter(user_email=email).count() == <NUM_LIT:0><EOL>try:<EOL><INDENT>user = User.objects.get(email=email)<EOL>assert EnterpriseCustomerUser.objects.filter(user_id=user.id).count() == <NUM_LIT:0><EOL><DEDENT>except User.DoesNotExist:<EOL><INDENT>pass<EOL><DEDENT>

Assert that linked user record with specified email does not exist.


assert self.client.login(username=self.user.username, password="<STR_LIT>")<EOL>


def _assert_django_messages(self, post_response, expected_messages):

self.assertRedirects(post_response, self.view_url, fetch_redirect_response=False)<EOL>get_response = self.client.get(self.view_url)<EOL>response_messages = set(<EOL>(m.level, m.message) for m in get_response.context['<STR_LIT>']  <EOL>)<EOL>assert response_messages == expected_messages<EOL>

Verify that the expected_messages are included in the context of the next response.


<EOL>assert page_object.number == page_number<EOL>assert list(page_object.object_list) == total_result[page_start:page_end]<EOL>assert page_object.paginator.count == len(total_result)<EOL>assert page_object.paginator.per_page == page_size<EOL>result_pages = int(ceil(len(total_result) / float(page_size))) if total_result else <NUM_LIT:1><EOL>assert page_object.paginator.num_pages == result_pages<EOL>assert list(page_object.paginator.object_list) == total_result<EOL>


def _enroll_user_request(self, user, mode, course_id="<STR_LIT>", program_id="<STR_LIT>", notify=True):

notify = (<EOL>ManageLearnersForm.NotificationTypes.BY_EMAIL if notify<EOL>else ManageLearnersForm.NotificationTypes.NO_NOTIFICATION<EOL>)<EOL>self._login()<EOL>if isinstance(user, six.string_types):<EOL><INDENT>email_or_username = user<EOL><DEDENT>else:<EOL><INDENT>email_or_username = getattr(user, '<STR_LIT:username>', getattr(user, '<STR_LIT>', None))<EOL><DEDENT>response = self.client.post(self.view_url, data={<EOL>ManageLearnersForm.Fields.EMAIL_OR_USERNAME: email_or_username,<EOL>ManageLearnersForm.Fields.COURSE_MODE: mode,<EOL>ManageLearnersForm.Fields.COURSE: course_id,<EOL>ManageLearnersForm.Fields.PROGRAM: program_id,<EOL>ManageLearnersForm.Fields.NOTIFY: notify<EOL>})<EOL>return response<EOL>

Perform post request to log in and submit the form to enroll a user.


def _post_multi_enroll(self, forms_client, views_client, course_catalog_client, track_enrollment, create_user):

courses = {<EOL>"<STR_LIT>": {<EOL>"<STR_LIT:title>": "<STR_LIT>",<EOL>"<STR_LIT:start>": "<STR_LIT>",<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>"<STR_LIT>": "<STR_LIT>"<EOL>},<EOL>"<STR_LIT>": {<EOL>"<STR_LIT:title>": "<STR_LIT>",<EOL>"<STR_LIT:start>": "<STR_LIT>",<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>"<STR_LIT>": "<STR_LIT>"<EOL>}<EOL>}<EOL>catalog_instance = course_catalog_client.return_value<EOL>views_instance = views_client.return_value<EOL>views_instance.enroll_user_in_course.side_effect = fake_enrollment_api.enroll_user_in_course<EOL>forms_instance = forms_client.return_value<EOL>forms_instance.get_course_details.side_effect = fake_enrollment_api.get_course_details<EOL>enrollment_count = <NUM_LIT:0><EOL>user = None<EOL>user_email = FAKER.email()  <EOL>if create_user:<EOL><INDENT>user = UserFactory(email=user_email)<EOL><DEDENT>for course_id in courses:<EOL><INDENT>catalog_instance.get_course_run.return_value = courses[course_id]<EOL>mode = courses[course_id]['<STR_LIT>']<EOL>enrollment_count += <NUM_LIT:1><EOL>if user:<EOL><INDENT>response = self._enroll_user_request(user, mode, course_id=course_id)<EOL>if enrollment_count == <NUM_LIT:1>:<EOL><INDENT>views_instance.enroll_user_in_course.assert_called_once()<EOL>track_enrollment.assert_called_once()<EOL><DEDENT>views_instance.enroll_user_in_course.assert_called_with(<EOL>user.username,<EOL>course_id,<EOL>mode,<EOL>)<EOL>track_enrollment.assert_called_with('<STR_LIT>', user.id, course_id)<EOL>self._assert_django_messages(response, set([<EOL>(messages.SUCCESS, "<STR_LIT>".format(course_id)),<EOL>]))<EOL><DEDENT>else:<EOL><INDENT>response = self._enroll_user_request(user_email, mode, course_id=course_id, notify=True)<EOL><DEDENT>if user:<EOL><INDENT>all_enrollments = EnterpriseCourseEnrollment.objects.all()<EOL><DEDENT>else:<EOL><INDENT>all_enrollments = PendingEnrollment.objects.all()<EOL><DEDENT>num_enrollments = len(all_enrollments)<EOL>assert num_enrollments == enrollment_count<EOL>enrollment = all_enrollments[enrollment_count - <NUM_LIT:1>]<EOL>if user:<EOL><INDENT>assert enrollment.enterprise_customer_user.user == user<EOL><DEDENT>assert enrollment.course_id == course_id<EOL>num_messages = len(mail.outbox)<EOL>assert num_messages == enrollment_count<EOL><DEDENT>

Enroll an enterprise learner or pending learner in multiple courses.


assert response.status_code == <NUM_LIT:200><EOL>self._test_common_context(response.context)  <EOL>manage_learners_form = response.context[self.context_parameters.MANAGE_LEARNERS_FORM]<EOL>assert manage_learners_form.is_bound<EOL>return manage_learners_form<EOL>

Utility function to capture common parts of assertions on form errors.

Arguments:
    response (HttpResponse): View response.

Returns:
    ManageLearnersForm: bound instance of ManageLearnersForm used to render the response.

Raises:
    AssertionError: if response status code is not 200 or form is unbound.


@staticmethod<EOL><INDENT>def _assert_line_message(actual_message, lineno, expected_message):<DEDENT>

assert "<STR_LIT>".format(lineno) in actual_message<EOL>assert expected_message in actual_message<EOL>

Assert that `actual_message` contains line number and `expected_message`


def _perform_request(self, columns, data, course=None, program=None, course_mode=None, notify=True):

with MakeCsvStreamContextManager(columns, data) as stream:<EOL><INDENT>post_data = {ManageLearnersForm.Fields.BULK_UPLOAD: stream}<EOL>if program is not None:<EOL><INDENT>post_data[ManageLearnersForm.Fields.PROGRAM] = program<EOL><DEDENT>if course is not None:<EOL><INDENT>post_data[ManageLearnersForm.Fields.COURSE] = course<EOL><DEDENT>if course_mode is not None:<EOL><INDENT>post_data[ManageLearnersForm.Fields.COURSE_MODE] = course_mode<EOL><DEDENT>post_data[ManageLearnersForm.Fields.NOTIFY] = '<STR_LIT>' if notify else '<STR_LIT>'<EOL>post_data['<STR_LIT>'] = self.enterprise_customer<EOL>response = self.client.post(self.view_url, data=post_data)<EOL><DEDENT>return response<EOL>

Perform bulk upload request with specified columns and data.

Arguments:
    columns (list): CSV column header
    data (list): CSV contents.
    course (str): The course ID entered in the form.
    course_mode (str): The enrollment mode entered in the form.

Returns:
    HttpResponse: View response.


super(BaseTestEnterpriseCustomerTransmitCoursesView, self).setUp()<EOL>self.user = UserFactory.create(is_staff=True, is_active=True, id=<NUM_LIT:1>)<EOL>self.user.set_password('<STR_LIT>')<EOL>self.user.save()<EOL>self.enterprise_channel_worker = UserFactory.create(is_staff=True, is_active=True)<EOL>self.enterprise_customer = EnterpriseCustomerFactory()<EOL>self.default_context = {<EOL>'<STR_LIT>': True,<EOL>'<STR_LIT>': self.enterprise_customer._meta,<EOL>'<STR_LIT:user>': self.user<EOL>}<EOL>self.transmit_courses_metadata_form = TransmitEnterpriseCoursesForm()<EOL>self.view_url = reverse(<EOL>'<STR_LIT>' + enterprise_admin.utils.UrlNames.TRANSMIT_COURSES_METADATA,<EOL>args=(self.enterprise_customer.uuid,)<EOL>)<EOL>self.client = Client()<EOL>self.context_parameters = EnterpriseCustomerTransmitCoursesView.ContextParameters<EOL>


assert self.client.login(username=self.user.username, password='<STR_LIT>')<EOL>


super(TestWithCourseCatalogApiMixin, self).setUp()<EOL>self.catalog_api = mock.Mock(CourseCatalogApiClient)<EOL>patcher = mock.patch('<STR_LIT>', mock.Mock(return_value=self.catalog_api))<EOL>patcher.start()<EOL>self.addCleanup(patcher.stop)<EOL>

Build CourseCatalogApiClient mock and register it for deactivation at cleanup.


@staticmethod<EOL><INDENT>def _make_bound_form(email, file_attached=False, course="<STR_LIT>", program="<STR_LIT>", course_mode="<STR_LIT>", notify="<STR_LIT>"):<DEDENT>

form_data = {<EOL>ManageLearnersForm.Fields.EMAIL_OR_USERNAME: email,<EOL>ManageLearnersForm.Fields.COURSE: course,<EOL>ManageLearnersForm.Fields.PROGRAM: program,<EOL>ManageLearnersForm.Fields.COURSE_MODE: course_mode,<EOL>ManageLearnersForm.Fields.NOTIFY: notify,<EOL>}<EOL>file_data = {}<EOL>if file_attached:<EOL><INDENT>mock_file = mock.Mock(spec=File)<EOL>mock_file.name = "<STR_LIT>"<EOL>mock_file.read.return_value = "<STR_LIT>"<EOL>file_data = {ManageLearnersForm.Fields.BULK_UPLOAD: mock_file}<EOL><DEDENT>customer = EnterpriseCustomerFactory(<EOL>catalog=<NUM_LIT>,<EOL>)<EOL>return ManageLearnersForm(form_data, file_data, enterprise_customer=customer)<EOL>


super(TestEnterpriseCustomerAdminForm, self).setUp()<EOL>self.catalog_id = random.randint(<NUM_LIT:2>, <NUM_LIT>)<EOL>self.patch_enterprise_customer_user()<EOL>self.addCleanup(self.unpatch_enterprise_customer_user)<EOL>


form_data = {<EOL>"<STR_LIT:name>": "<STR_LIT>",<EOL>"<STR_LIT>": identity_provider,<EOL>"<STR_LIT>": <NUM_LIT:1>,<EOL>"<STR_LIT>": <NUM_LIT:1>,<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>}<EOL>return EnterpriseCustomerAdminForm(form_data)<EOL>


EnterpriseCustomerAdminForm.user = None<EOL>


delattr(EnterpriseCustomerAdminForm, '<STR_LIT:user>')<EOL>


super(TestEnterpriseCustomerIdentityProviderAdminForm, self).setUp()<EOL>self.idp_choices = (("<STR_LIT>", "<STR_LIT>"), ('<STR_LIT>', "<STR_LIT>"))<EOL>self.first_site = SiteFactory(domain="<STR_LIT>")<EOL>self.second_site = SiteFactory(domain="<STR_LIT>")<EOL>self.enterprise_customer = EnterpriseCustomerFactory(site=self.first_site)<EOL>self.provider_id = FAKER.slug()<EOL>


super(TestEnterpriseCustomerReportingConfigAdminForm, self).setUp()<EOL>self.ent_customer1 = EnterpriseCustomerFactory()<EOL>self.ent_customer2 = EnterpriseCustomerFactory()<EOL>self.ent_catalogs1 = [<EOL>EnterpriseCustomerCatalogFactory(enterprise_customer=self.ent_customer1)<EOL>for _ in range(<NUM_LIT:3>)<EOL>]<EOL>self.ent_catalogs2 = [<EOL>EnterpriseCustomerCatalogFactory(enterprise_customer=self.ent_customer2)<EOL>for _ in range(<NUM_LIT:2>)<EOL>]<EOL>self.form_data = {<EOL>'<STR_LIT>': self.ent_customer1.uuid,<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT:email>',<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': <NUM_LIT:1>,<EOL>'<STR_LIT:email>': '<STR_LIT>',<EOL>'<STR_LIT>': '<STR_LIT:password>',<EOL>}<EOL>


return [<EOL>{key: value for key, value in zip(header, column_values)}<EOL>for column_values in contents<EOL>]<EOL>

Generates :method:`parse_csv` output from header and contents.

Arguments:
    header (Iterable): Column headers.
    contents (Iterable): CSV contents - each item represents a line.

Returns:
    list: Expected :method:`parse_csv` output


return "<STR_LIT>".format(name=self.name)<EOL>

Dummy method to test exporting callable.


self.action = get_clear_catalog_id_action()<EOL>self.model_admin_mock = mock.Mock()<EOL>self.model_admin_mock.model._meta.fields = [<EOL>self._make_field("<STR_LIT:code>"), self._make_field("<STR_LIT:name>"), self._make_field("<STR_LIT:description>"),<EOL>]<EOL>super(TestClearCatalogAction, self).setUp()<EOL>

Provide the necessary moving parts for the tests.


field = mock.Mock()<EOL>field.name = name<EOL>return field<EOL>


def _make_patch(self, target, **kwargs):

target_mock = mock.Mock(**kwargs)<EOL>patch = mock.patch(target, new=target_mock)<EOL>patch.start()<EOL>self.addCleanup(patch.stop)<EOL>return target_mock<EOL>

Patch `target` with mock for the duration of test run.


def _assert_correct_csv(self, actual_csv, expected_rows):

reader = unicodecsv.reader(actual_csv.getvalue().splitlines(), encoding="<STR_LIT:utf-8>")<EOL>expected_rows = [<EOL>[str(item) for item in row]<EOL>for row in expected_rows<EOL>]<EOL>actual_rows = list(reader)<EOL>self.assertEqual(actual_rows, expected_rows)<EOL>

Asserts that CSV file ``actual_csv`` contains ``expected_rows``


super(TestCSVExportAction, self).setUp()<EOL>self.output_stream = BytesIO()<EOL>response_instance_mock = mock.MagicMock(wraps=self.output_stream)<EOL>self.response_mock = self._make_patch(<EOL>"<STR_LIT>",<EOL>return_value=response_instance_mock<EOL>)<EOL>self.model_admin_mock = mock.Mock()<EOL>self.model_admin_mock.model._meta.fields = [<EOL>self._make_field("<STR_LIT:code>"), self._make_field("<STR_LIT:name>"), self._make_field("<STR_LIT:description>"),<EOL>]<EOL>


for message_type, message in alert_messages:<EOL><INDENT>messages.add_message(request, message_type, message)<EOL><DEDENT>

Add django messages to the request context using `messages` app.

Arguments:
    alert_messages (list): A list of tuples containing message type and message body.
        e.g. [('success', 'This is a dummy success message.'), ]


request = RequestFactory().get('<STR_LIT:/>')<EOL>request.session = cache.SessionStore()<EOL>request._messages = fallback.FallbackStorage(request)<EOL>return request<EOL>


Switch = apps.get_model('<STR_LIT>', '<STR_LIT>')<EOL>Switch.objects.get_or_create(name='<STR_LIT>', defaults={'<STR_LIT>': False})<EOL>

Create and activate the SAP_USE_ENTERPRISE_ENROLLMENT_PAGE switch if it does not already exist.


Switch = apps.get_model('<STR_LIT>', '<STR_LIT>')<EOL>Switch.objects.filter(name='<STR_LIT>').delete()<EOL>

Delete the SAP_USE_ENTERPRISE_ENROLLMENT_PAGE switch.


table_name = '<STR_LIT>'<EOL>if table_name in connection.introspection.table_names():<EOL><INDENT>migrations.DeleteModel(<EOL>name=table_name,<EOL>)<EOL><DEDENT>

Drops the historical sap_success_factors table named herein.


@staticmethod<EOL><INDENT>def get_oauth_access_token(url_base, client_id, client_secret, company_id, user_id, user_type):<DEDENT>

SAPSuccessFactorsGlobalConfiguration = apps.get_model(  <EOL>'<STR_LIT>',<EOL>'<STR_LIT>'<EOL>)<EOL>global_sap_config = SAPSuccessFactorsGlobalConfiguration.current()<EOL>url = url_base + global_sap_config.oauth_api_path<EOL>response = requests.post(<EOL>url,<EOL>json={<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>'<STR_LIT>': {<EOL>'<STR_LIT>': user_id,<EOL>'<STR_LIT>': company_id,<EOL>'<STR_LIT>': user_type,<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>}<EOL>},<EOL>auth=(client_id, client_secret),<EOL>headers={'<STR_LIT>': '<STR_LIT:application/json>'}<EOL>)<EOL>response.raise_for_status()<EOL>data = response.json()<EOL>try:<EOL><INDENT>return data['<STR_LIT>'], datetime.datetime.utcfromtimestamp(data['<STR_LIT>'] + int(time.time()))<EOL><DEDENT>except KeyError:<EOL><INDENT>raise requests.RequestException(response=response)<EOL><DEDENT>

Retrieves OAuth 2.0 access token using the client credentials grant.

        Args:
            url_base (str): Oauth2 access token endpoint
            client_id (str): client ID
            client_secret (str): client secret
            company_id (str): SAP company ID
            user_id (str): SAP user ID
            user_type (str): type of SAP user (admin or user)

        Returns:
            tuple: Tuple containing access token string and expiration datetime.
        Raises:
            HTTPError: If we received a failure response code from SAP SuccessFactors.
            RequestException: If an unexpected response format was received that we could not parse.


super(SAPSuccessFactorsAPIClient, self).__init__(enterprise_configuration)<EOL>self.global_sap_config = apps.get_model('<STR_LIT>', '<STR_LIT>').current()<EOL>self._create_session()<EOL>

Instantiate a new client.

Args:
    enterprise_configuration (SAPSuccessFactorsEnterpriseCustomerConfiguration): An enterprise customers's
    configuration model for connecting with SAP SuccessFactors


session = requests.Session()<EOL>session.timeout = self.SESSION_TIMEOUT<EOL>oauth_access_token, expires_at = SAPSuccessFactorsAPIClient.get_oauth_access_token(<EOL>self.enterprise_configuration.sapsf_base_url,<EOL>self.enterprise_configuration.key,<EOL>self.enterprise_configuration.secret,<EOL>self.enterprise_configuration.sapsf_company_id,<EOL>self.enterprise_configuration.sapsf_user_id,<EOL>self.enterprise_configuration.user_type<EOL>)<EOL>session.headers['<STR_LIT>'] = '<STR_LIT>'.format(oauth_access_token)<EOL>session.headers['<STR_LIT>'] = '<STR_LIT:application/json>'<EOL>self.session = session<EOL>self.expires_at = expires_at<EOL>

Instantiate a new session object for use in connecting with SAP SuccessFactors


def create_course_completion(self, user_id, payload):

url = self.enterprise_configuration.sapsf_base_url + self.global_sap_config.completion_status_api_path<EOL>return self._call_post_with_user_override(user_id, url, payload)<EOL>

Send a completion status payload to the SuccessFactors OCN Completion Status endpoint

Args:
    user_id (str): The sap user id that the completion status is being sent for.
    payload (str): JSON encoded object (serialized from SapSuccessFactorsLearnerDataTransmissionAudit)
        containing completion status fields per SuccessFactors documentation.

Returns:
    The body of the response from SAP SuccessFactors, if successful
Raises:
    HTTPError: if we received a failure response code from SAP SuccessFactors


self._sync_content_metadata(serialized_data)<EOL>

Create content metadata records using the SuccessFactors OCN Course Import API endpoint.

Arguments:
    serialized_data: Serialized JSON string representing a list of content metadata items.

Raises:
    ClientError: If SuccessFactors API call fails.


Update content metadata records using the SuccessFactors OCN Course Import API endpoint.

Arguments:
    serialized_data: Serialized JSON string representing a list of content metadata items.

Raises:
    ClientError: If SuccessFactors API call fails.


Delete content metadata records using the SuccessFactors OCN Course Import API endpoint.

Arguments:
    serialized_data: Serialized JSON string representing a list of content metadata items.

Raises:
    ClientError: If SuccessFactors API call fails.


url = self.enterprise_configuration.sapsf_base_url + self.global_sap_config.course_api_path<EOL>try:<EOL><INDENT>status_code, response_body = self._call_post_with_session(url, serialized_data)<EOL><DEDENT>except requests.exceptions.RequestException as exc:<EOL><INDENT>raise ClientError(<EOL>'<STR_LIT>'.format(<EOL>error=exc.__class__.__name__,<EOL>message=str(exc)<EOL>)<EOL>)<EOL><DEDENT>if status_code >= <NUM_LIT>:<EOL><INDENT>raise ClientError(<EOL>'<STR_LIT>'.format(<EOL>status_code=status_code,<EOL>message=response_body<EOL>)<EOL>)<EOL><DEDENT>

Create/update/delete content metadata records using the SuccessFactors OCN Course Import API endpoint.

Arguments:
    serialized_data: Serialized JSON string representing a list of content metadata items.

Raises:
    ClientError: If SuccessFactors API call fails.


def _call_post_with_user_override(self, sap_user_id, url, payload):

SAPSuccessFactorsEnterpriseCustomerConfiguration = apps.get_model(  <EOL>'<STR_LIT>',<EOL>'<STR_LIT>'<EOL>)<EOL>oauth_access_token, _ = SAPSuccessFactorsAPIClient.get_oauth_access_token(<EOL>self.enterprise_configuration.sapsf_base_url,<EOL>self.enterprise_configuration.key,<EOL>self.enterprise_configuration.secret,<EOL>self.enterprise_configuration.sapsf_company_id,<EOL>sap_user_id,<EOL>SAPSuccessFactorsEnterpriseCustomerConfiguration.USER_TYPE_USER<EOL>)<EOL>response = requests.post(<EOL>url,<EOL>data=payload,<EOL>headers={<EOL>'<STR_LIT>': '<STR_LIT>'.format(oauth_access_token),<EOL>'<STR_LIT>': '<STR_LIT:application/json>'<EOL>}<EOL>)<EOL>return response.status_code, response.text<EOL>

Make a post request with an auth token acquired for a specific user to a SuccessFactors endpoint.

Args:
    sap_user_id (str): The user to use to retrieve an auth token.
    url (str): The url to post to.
    payload (str): The json encoded payload to post.


def _call_post_with_session(self, url, payload):

now = datetime.datetime.utcnow()<EOL>if now >= self.expires_at:<EOL><INDENT>self.session.close()<EOL>self._create_session()<EOL><DEDENT>response = self.session.post(url, data=payload)<EOL>return response.status_code, response.text<EOL>

Make a post request using the session object to a SuccessFactors endpoint.

Args:
    url (str): The url to post to.
    payload (str): The json encoded payload to post.


Make a GET request using the session object to a SuccessFactors endpoint for inactive learners.

Example:
    sap_search_student_url: "/learning/odatav4/searchStudent/v1/Students?
        $filter=criteria/isActive eq False&$select=studentID"

    SAP API response: {
        u'@odata.metadataEtag': u'W/"17090d86-20fa-49c8-8de0-de1d308c8b55"',
        u'value': [
            {
                u'studentID': u'admint6',
            },
            {
                u'studentID': u'adminsap1',
            }
        ]
    }

Returns: List of inactive learners
[
    {
        u'studentID': u'admint6'
    },
    {
        u'studentID': u'adminsap1'
    }
]


def _call_search_students_recursively(self, sap_search_student_url, all_inactive_learners, page_size, start_at):

search_student_paginated_url = '<STR_LIT>'.format(<EOL>sap_search_student_url=sap_search_student_url,<EOL>pagination_criterion='<STR_LIT>'.format(<EOL>page_size=page_size,<EOL>start_at=start_at,<EOL>),<EOL>)<EOL>try:<EOL><INDENT>response = self.session.get(search_student_paginated_url)<EOL>sap_inactive_learners = response.json()<EOL><DEDENT>except (ConnectionError, Timeout):<EOL><INDENT>LOGGER.warning(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>', search_student_paginated_url,<EOL>)<EOL>return None<EOL><DEDENT>if '<STR_LIT:error>' in sap_inactive_learners:<EOL><INDENT>LOGGER.warning(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>',<EOL>self.enterprise_configuration.enterprise_customer.name,<EOL>self.enterprise_configuration.sapsf_base_url,<EOL>sap_inactive_learners['<STR_LIT:error>'].get('<STR_LIT:message>'),<EOL>sap_inactive_learners['<STR_LIT:error>'].get('<STR_LIT:code>'),<EOL>)<EOL>return None<EOL><DEDENT>new_page_start_at = page_size + start_at<EOL>all_inactive_learners += sap_inactive_learners['<STR_LIT:value>']<EOL>if sap_inactive_learners['<STR_LIT>'] > new_page_start_at:<EOL><INDENT>return self._call_search_students_recursively(<EOL>sap_search_student_url,<EOL>all_inactive_learners,<EOL>page_size=page_size,<EOL>start_at=new_page_start_at,<EOL>)<EOL><DEDENT>return all_inactive_learners<EOL>

Make recursive GET calls to traverse the paginated API response for search students.


return obj.enterprise_customer.name<EOL>

Returns: the name for the attached EnterpriseCustomer.

Args:
    obj: The instance of SAPSuccessFactorsEnterpriseCustomerConfiguration
        being rendered with this admin form.


try:<EOL><INDENT>access_token, expires_at = SAPSuccessFactorsAPIClient.get_oauth_access_token(<EOL>obj.sapsf_base_url,<EOL>obj.key,<EOL>obj.secret,<EOL>obj.sapsf_company_id,<EOL>obj.sapsf_user_id,<EOL>obj.user_type<EOL>)<EOL><DEDENT>except (RequestException, ValueError):<EOL><INDENT>return False<EOL><DEDENT>return bool(access_token and expires_at)<EOL>

Confirms the presence and validity of the access token for the SAP SuccessFactors client instance

Returns: a bool value indicating the presence of the access token

Args:
    obj: The instance of SAPSuccessFactorsEnterpriseCustomerConfiguration
        being rendered with this admin form.


def __init__(self, enterprise_configuration, client=SAPSuccessFactorsAPIClient):

super(SapSuccessFactorsContentMetadataTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>

Use the ``SAPSuccessFactorsAPIClient`` for content metadata transmission to SAPSF.


items_to_create, items_to_update, items_to_delete, transmission_map = self._partition_items(payload)<EOL>self._prepare_items_for_delete(items_to_delete)<EOL>prepared_items = {}<EOL>prepared_items.update(items_to_create)<EOL>prepared_items.update(items_to_update)<EOL>prepared_items.update(items_to_delete)<EOL>skip_metadata_transmission = False<EOL>for chunk in chunks(prepared_items, self.enterprise_configuration.transmission_chunk_size):<EOL><INDENT>chunked_items = list(chunk.values())<EOL>if skip_metadata_transmission:<EOL><INDENT>self._remove_failed_items(chunked_items, items_to_create, items_to_update, items_to_delete)<EOL><DEDENT>else:<EOL><INDENT>try:<EOL><INDENT>self.client.update_content_metadata(self._serialize_items(chunked_items))<EOL><DEDENT>except ClientError as exc:<EOL><INDENT>LOGGER.error(<EOL>'<STR_LIT>',<EOL>len(chunked_items),<EOL>self.enterprise_configuration.enterprise_customer.name,<EOL>self.enterprise_configuration.channel_code,<EOL>)<EOL>LOGGER.error(exc)<EOL>self._remove_failed_items(chunked_items, items_to_create, items_to_update, items_to_delete)<EOL>skip_metadata_transmission = True<EOL><DEDENT><DEDENT><DEDENT>self._create_transmissions(items_to_create)<EOL>self._update_transmissions(items_to_update, transmission_map)<EOL>self._delete_transmissions(items_to_delete.keys())<EOL>

Transmit content metadata items to the integrated channel.


def _remove_failed_items(self, failed_items, items_to_create, items_to_update, items_to_delete):

for item in failed_items:<EOL><INDENT>content_metadata_id = item['<STR_LIT>']<EOL>items_to_create.pop(content_metadata_id, None)<EOL>items_to_update.pop(content_metadata_id, None)<EOL>items_to_delete.pop(content_metadata_id, None)<EOL><DEDENT>

Remove content metadata items from the `items_to_create`, `items_to_update`, `items_to_delete` dicts.

Arguments:
    failed_items (list): Failed Items to be removed.
    items_to_create (dict): dict containing the items created successfully.
    items_to_update (dict): dict containing the items updated successfully.
    items_to_delete (dict): dict containing the items deleted successfully.


for item in items_to_delete.values():<EOL><INDENT>item['<STR_LIT:status>'] = '<STR_LIT>'<EOL><DEDENT>

Set status to INACTIVE for items that should be deleted.


super(SapSuccessFactorsLearnerTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>

By default, use the ``SAPSuccessFactorsAPIClient`` for learner data transmission to SAPSF.


kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>super(SapSuccessFactorsLearnerTransmitter, self).transmit(payload, **kwargs)<EOL>

Send a completion status call to SAP SuccessFactors using the client.

Args:
    payload: The learner completion data payload to send to SAP SuccessFactors


def handle_transmission_error(self, learner_data, request_exception):

try:<EOL><INDENT>sys_msg = request_exception.response.content<EOL><DEDENT>except AttributeError:<EOL><INDENT>pass<EOL><DEDENT>else:<EOL><INDENT>if '<STR_LIT>' in sys_msg:<EOL><INDENT>ecu = EnterpriseCustomerUser.objects.get(<EOL>enterprise_enrollments__id=learner_data.enterprise_course_enrollment_id)<EOL>ecu.active = False<EOL>ecu.save()<EOL>LOGGER.warning(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>',<EOL>ecu.username, ecu.user_id, ecu.user_email, ecu.enterprise_customer<EOL>)<EOL>return<EOL><DEDENT><DEDENT>super(SapSuccessFactorsLearnerTransmitter, self).handle_transmission_error(learner_data, request_exception)<EOL>

Handle the case where the employee on SAPSF's side is marked as inactive.


return self.enterprise_configuration.provider_id<EOL>

Return the provider ID from the integrated channel configuration.


return '<STR_LIT>'<EOL>


title_with_locales = []<EOL>for locale in self.enterprise_configuration.get_locales():<EOL><INDENT>title_with_locales.append({<EOL>'<STR_LIT>': locale,<EOL>'<STR_LIT:value>': content_metadata_item.get('<STR_LIT:title>', '<STR_LIT>')<EOL>})<EOL><DEDENT>return title_with_locales<EOL>


description_with_locales = []<EOL>for locale in self.enterprise_configuration.get_locales():<EOL><INDENT>description_with_locales.append({<EOL>'<STR_LIT>': locale,<EOL>'<STR_LIT:value>': (<EOL>content_metadata_item.get('<STR_LIT>') or<EOL>content_metadata_item.get('<STR_LIT>') or<EOL>content_metadata_item.get('<STR_LIT:title>', '<STR_LIT>')<EOL>)<EOL>})<EOL><DEDENT>return description_with_locales<EOL>

Return the description of the content item.


image_url = '<STR_LIT>'<EOL>if content_metadata_item['<STR_LIT>'] in ['<STR_LIT>', '<STR_LIT>']:<EOL><INDENT>image_url = content_metadata_item.get('<STR_LIT>')<EOL><DEDENT>elif content_metadata_item['<STR_LIT>'] == '<STR_LIT>':<EOL><INDENT>image_url = content_metadata_item.get('<STR_LIT>')<EOL><DEDENT>return image_url<EOL>

Return the image URI of the content item.


return [{<EOL>'<STR_LIT>': self.enterprise_configuration.provider_id,<EOL>'<STR_LIT>': content_metadata_item['<STR_LIT>'],<EOL>'<STR_LIT>': content_metadata_item['<STR_LIT:title>'],<EOL>'<STR_LIT>': self.get_content_id(content_metadata_item),<EOL>'<STR_LIT>': <NUM_LIT:3>,  <EOL>'<STR_LIT>': True,  <EOL>'<STR_LIT>': content_metadata_item['<STR_LIT>'],<EOL>}]<EOL>

Return the content metadata item launch points.

SAPSF allows you to transmit an arry of content launch points which
are meant to represent sections of a content item which a learner can
launch into from SAPSF. Currently, we only provide a single launch
point for a content item.


return <NUM_LIT:1><EOL>


return []<EOL>

Return the schedule of the content item.


title = content_metadata_item.get('<STR_LIT:title>') or '<STR_LIT>'<EOL>course_run_start = content_metadata_item.get('<STR_LIT:start>')<EOL>if course_run_start:<EOL><INDENT>if course_available_for_enrollment(content_metadata_item):<EOL><INDENT>title += '<STR_LIT>'.format(<EOL>parse_lms_api_datetime(course_run_start),<EOL>starts=_('<STR_LIT>')<EOL>)<EOL><DEDENT>else:<EOL><INDENT>title += '<STR_LIT>'.format(<EOL>parse_lms_api_datetime(course_run_start),<EOL>enrollment_closed=_('<STR_LIT>')<EOL>)<EOL><DEDENT><DEDENT>title_with_locales = []<EOL>content_metadata_language_code = transform_language_code(content_metadata_item.get('<STR_LIT>', '<STR_LIT>'))<EOL>for locale in self.enterprise_configuration.get_locales(default_locale=content_metadata_language_code):<EOL><INDENT>title_with_locales.append({<EOL>'<STR_LIT>': locale,<EOL>'<STR_LIT:value>': title<EOL>})<EOL><DEDENT>return title_with_locales<EOL>

Return the title of the courserun content item.


description_with_locales = []<EOL>content_metadata_language_code = transform_language_code(content_metadata_item.get('<STR_LIT>', '<STR_LIT>'))<EOL>for locale in self.enterprise_configuration.get_locales(default_locale=content_metadata_language_code):<EOL><INDENT>description_with_locales.append({<EOL>'<STR_LIT>': locale,<EOL>'<STR_LIT:value>': (<EOL>content_metadata_item['<STR_LIT>'] or<EOL>content_metadata_item['<STR_LIT>'] or<EOL>content_metadata_item['<STR_LIT:title>'] or<EOL>'<STR_LIT>'<EOL>)<EOL>})<EOL><DEDENT>return description_with_locales<EOL>

Return the description of the courserun content item.


start = content_metadata_item.get('<STR_LIT:start>') or UNIX_MIN_DATE_STRING<EOL>end = content_metadata_item.get('<STR_LIT:end>') or UNIX_MAX_DATE_STRING<EOL>return [{<EOL>'<STR_LIT>': parse_datetime_to_epoch_millis(start),<EOL>'<STR_LIT>': parse_datetime_to_epoch_millis(end),<EOL>'<STR_LIT>': current_time_is_in_interval(start, end)<EOL>}]<EOL>

Return the schedule of the courseun content item.


return content_metadata_item['<STR_LIT>']<EOL>

Return the identifier of the program content item.


content_id = content_metadata_item.get('<STR_LIT:key>', '<STR_LIT>')<EOL>if content_metadata_item['<STR_LIT>'] == '<STR_LIT>':<EOL><INDENT>content_id = content_metadata_item.get('<STR_LIT>', '<STR_LIT>')<EOL><DEDENT>return content_id<EOL>

Return the id for the given content_metadata_item, `uuid` for programs or `key` for other content


if course_run['<STR_LIT>'] not in ['<STR_LIT>', '<STR_LIT>', '<STR_LIT>']:<EOL><INDENT>return False<EOL><DEDENT>return is_course_run_enrollable(course_run)<EOL>

Check if a course run is available for enrollment.


if code is None:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>components = code.split('<STR_LIT:->', <NUM_LIT:2>)<EOL>language_code = components[<NUM_LIT:0>]<EOL>try:<EOL><INDENT>country_code = components[<NUM_LIT:1>]<EOL><DEDENT>except IndexError:<EOL><INDENT>country_code = '<STR_LIT:_>'<EOL><DEDENT>language_family = SUCCESSFACTORS_OCN_LANGUAGE_CODES.get(language_code)<EOL>if not language_family:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>return language_family.get(country_code, language_family['<STR_LIT:_>'])<EOL>

Transform ISO language code (e.g. en-us) to the language name expected by SAPSF.


def get_learner_data_records(self, enterprise_enrollment, completed_date=None, grade=None, is_passing=False):

completed_timestamp = None<EOL>course_completed = False<EOL>if completed_date is not None:<EOL><INDENT>completed_timestamp = parse_datetime_to_epoch_millis(completed_date)<EOL>course_completed = is_passing<EOL><DEDENT>sapsf_user_id = enterprise_enrollment.enterprise_customer_user.get_remote_id()<EOL>if sapsf_user_id is not None:<EOL><INDENT>SapSuccessFactorsLearnerDataTransmissionAudit = apps.get_model(  <EOL>'<STR_LIT>',<EOL>'<STR_LIT>'<EOL>)<EOL>return [<EOL>SapSuccessFactorsLearnerDataTransmissionAudit(<EOL>enterprise_course_enrollment_id=enterprise_enrollment.id,<EOL>sapsf_user_id=sapsf_user_id,<EOL>course_id=parse_course_key(enterprise_enrollment.course_id),<EOL>course_completed=course_completed,<EOL>completed_timestamp=completed_timestamp,<EOL>grade=grade,<EOL>),<EOL>SapSuccessFactorsLearnerDataTransmissionAudit(<EOL>enterprise_course_enrollment_id=enterprise_enrollment.id,<EOL>sapsf_user_id=sapsf_user_id,<EOL>course_id=enterprise_enrollment.course_id,<EOL>course_completed=course_completed,<EOL>completed_timestamp=completed_timestamp,<EOL>grade=grade,<EOL>),<EOL>]<EOL><DEDENT>else:<EOL><INDENT>LOGGER.debug(<EOL>'<STR_LIT>',<EOL>enterprise_enrollment.enterprise_customer_user.username<EOL>)<EOL><DEDENT>

Return a SapSuccessFactorsLearnerDataTransmissionAudit with the given enrollment and course completion data.

If completed_date is None and the learner isn't passing, then course completion has not been met.

If no remote ID can be found, return None.


self.enterprise_configuration = enterprise_configuration<EOL>self.client = client(enterprise_configuration) if client else None<EOL>

Use the ``SAPSuccessFactorsAPIClient`` for content metadata transmission to SAPSF.

Arguments:
    enterprise_configuration (required): SAPSF configuration connecting an enterprise to an integrated channel.
    client: The REST API client that will fetch data from integrated channel.


sap_inactive_learners = self.client.get_inactive_sap_learners()<EOL>enterprise_customer = self.enterprise_configuration.enterprise_customer<EOL>if not sap_inactive_learners:<EOL><INDENT>LOGGER.info(<EOL>'<STR_LIT>',<EOL>enterprise_customer.name<EOL>)<EOL>return<EOL><DEDENT>provider_id = enterprise_customer.identity_provider<EOL>tpa_provider = get_identity_provider(provider_id)<EOL>if not tpa_provider:<EOL><INDENT>LOGGER.info(<EOL>'<STR_LIT>',<EOL>enterprise_customer.name<EOL>)<EOL>return None<EOL><DEDENT>for sap_inactive_learner in sap_inactive_learners:<EOL><INDENT>social_auth_user = get_user_from_social_auth(tpa_provider, sap_inactive_learner['<STR_LIT>'])<EOL>if not social_auth_user:<EOL><INDENT>continue<EOL><DEDENT>try:<EOL><INDENT>EnterpriseCustomerUser.objects.unlink_user(<EOL>enterprise_customer=enterprise_customer,<EOL>user_email=social_auth_user.email,<EOL>)<EOL><DEDENT>except (EnterpriseCustomerUser.DoesNotExist, PendingEnterpriseCustomerUser.DoesNotExist):<EOL><INDENT>LOGGER.info(<EOL>'<STR_LIT>',<EOL>social_auth_user.email,<EOL>enterprise_customer.name<EOL>)<EOL><DEDENT><DEDENT>

Iterate over each learner and unlink inactive SAP channel learners.

This method iterates over each enterprise learner and unlink learner
from the enterprise if the learner is marked inactive in the related
integrated channel.


return "<STR_LIT>".format(id=self.id)<EOL>

Return a human-readable string representation of the object.


return self.__str__()<EOL>

Return uniquely identifying string representation.


locales = []<EOL>if default_locale is None:<EOL><INDENT>locales.append('<STR_LIT>')<EOL><DEDENT>else:<EOL><INDENT>locales.append(default_locale)<EOL><DEDENT>return set(<EOL>locales + [locale.strip() for locale in self.additional_locales.split("<STR_LIT:U+002C>") if locale.strip()]<EOL>)<EOL>

Get the list of all(default + additional) locales

Args:
    default_locale (str): Value of the default locale

Returns:
    list: available locales


return "<STR_LIT>".format(<EOL>enterprise_name=self.enterprise_customer.name<EOL>)<EOL>

Return human-readable string representation.


return self.__str__()<EOL>


return '<STR_LIT>'<EOL>

Returns an capitalized identifier for this channel class, unique among subclasses.


return SAPSuccessFactorsGlobalConfiguration.current().provider_id<EOL>

Fetch ``provider_id`` from global configuration settings


return SapSuccessFactorsLearnerTransmitter(self)<EOL>

Return a ``SapSuccessFactorsLearnerTransmitter`` instance.


return SapSuccessFactorsLearnerExporter(user, self)<EOL>

Return a ``SapSuccessFactorsLearnerDataExporter`` instance.


return SapSuccessFactorsContentMetadataTransmitter(self)<EOL>

Return a ``SapSuccessFactorsContentMetadataTransmitter`` instance.


return SapSuccessFactorsContentMetadataExporter(user, self)<EOL>

Return a ``SapSuccessFactorsContentMetadataExporter`` instance.


return SapSuccessFactorsLearnerManger(self)<EOL>

Return a ``SapSuccessFactorsLearnerManger`` instance.


sap_learner_manager = self.get_learner_manger()<EOL>sap_learner_manager.unlink_learners()<EOL>

Unlink inactive SAP learners form their related enterprises


return json.dumps(self._payload_data(), sort_keys=True)<EOL>

Return a JSON-serialized representation.

Sort the keys so the result is consistent and testable.

# TODO: When we refactor to use a serialization flow consistent with how course metadata
# is serialized, remove the serialization here and make the learner data exporter handle the work.


return dict(<EOL>userID=self.sapsf_user_id,<EOL>courseID=self.course_id,<EOL>providerID=self.provider_id,<EOL>courseCompleted="<STR_LIT:true>" if self.course_completed else "<STR_LIT:false>",<EOL>completedTimestamp=self.completed_timestamp,<EOL>grade=self.grade,<EOL>)<EOL>

Convert the audit record's fields into SAP SuccessFactors key/value pairs.


<EOL>enterprise_learner = EnterpriseCustomerUser.objects.filter(user_id=obj.id).first()<EOL>return enterprise_learner and enterprise_learner.id<EOL>

Get enterprise user id from user object.

Arguments:
    obj (User): Django User object

Returns:
    (int): Primary Key identifier for enterprise user object.


<EOL>enterprise_learner = EnterpriseCustomerUser.objects.filter(user_id=obj.id).first()<EOL>return enterprise_learner and enterprise_learner.get_remote_id()<EOL>

Get enterprise SSO UID.

Arguments:
    obj (User): Django User object

Returns:
    (str): string containing UUID for enterprise customer's Identity Provider.


duration = obj.end - obj.start if obj.start and obj.end else None<EOL>if duration:<EOL><INDENT>return strfdelta(duration, '<STR_LIT>')<EOL><DEDENT>return '<STR_LIT>'<EOL>

Get course's duration as a timedelta.

Arguments:
    obj (CourseOverview): CourseOverview object

Returns:
    (timedelta): Duration of a course.


self.lrs_configuration = lrs_configuration<EOL>

Initialize xAPI client.

Arguments:
     lrs_configuration (XAPILRSConfiguration): Configuration object for xAPI LRS.


return RemoteLRS(<EOL>version=self.lrs_configuration.version,<EOL>endpoint=self.lrs_configuration.endpoint,<EOL>auth=self.lrs_configuration.authorization_header,<EOL>)<EOL>

LRS client instance to be used for sending statements.


response = self.lrs.save_statement(statement)<EOL>if not response:<EOL><INDENT>raise ClientError('<STR_LIT>')<EOL><DEDENT>

Save xAPI statement.

Arguments:
    statement (EnterpriseStatement): xAPI Statement to send to the LRS.

Raises:
    ClientError: If xAPI statement fails to save.


return obj.enterprise_customer.name<EOL>

Returns: the name for the attached EnterpriseCustomer.

Arguments:
    obj: The instance of XAPILRSConfiguration
        being rendered with this admin form.


@staticmethod<EOL><INDENT>def parse_arguments(*args, **options):  <DEDENT>

days = options.get('<STR_LIT>', <NUM_LIT:1>)<EOL>enterprise_customer_uuid = options.get('<STR_LIT>')<EOL>enterprise_customer = None<EOL>if enterprise_customer_uuid:<EOL><INDENT>try:<EOL><INDENT>enterprise_customer = EnterpriseCustomer.objects.get(uuid=enterprise_customer_uuid)<EOL><DEDENT>except EnterpriseCustomer.DoesNotExist:<EOL><INDENT>raise CommandError('<STR_LIT>'.format(<EOL>enterprise_customer_uuid=enterprise_customer_uuid<EOL>))<EOL><DEDENT><DEDENT>return days, enterprise_customer<EOL>

Parse and validate arguments for send_course_enrollments command.

Arguments:
    *args: Positional arguments passed to the command
    **options: optional arguments passed to the command

Returns:
    A tuple containing parsed values for
    1. days (int): Integer showing number of days to lookup enterprise enrollments,
        course completion etc and send to xAPI LRS
    2. enterprise_customer_uuid (EnterpriseCustomer): Enterprise Customer if present then
        send xAPI statements just for this enterprise.


if not CourseEnrollment:<EOL><INDENT>raise NotConnectedToOpenEdX("<STR_LIT>")<EOL><DEDENT>days, enterprise_customer = self.parse_arguments(*args, **options)<EOL>if enterprise_customer:<EOL><INDENT>try:<EOL><INDENT>lrs_configuration = XAPILRSConfiguration.objects.get(<EOL>active=True,<EOL>enterprise_customer=enterprise_customer<EOL>)<EOL><DEDENT>except XAPILRSConfiguration.DoesNotExist:<EOL><INDENT>raise CommandError('<STR_LIT>'.format(<EOL>enterprise_customer=enterprise_customer.name<EOL>))<EOL><DEDENT>self.send_xapi_statements(lrs_configuration, days)<EOL><DEDENT>else:<EOL><INDENT>for lrs_configuration in XAPILRSConfiguration.objects.filter(active=True):<EOL><INDENT>self.send_xapi_statements(lrs_configuration, days)<EOL><DEDENT><DEDENT>


def send_xapi_statements(self, lrs_configuration, days):

Send xAPI analytics data of the enterprise learners to the given LRS.

Arguments:
    lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations
        of the LRS where to send xAPI  learner analytics.
    days (int): Include course enrollment of this number of days.


def get_course_enrollments(self, enterprise_customer, days):

return CourseEnrollment.objects.filter(<EOL>created__gt=datetime.datetime.now() - datetime.timedelta(days=days)<EOL>).filter(<EOL>user_id__in=enterprise_customer.enterprise_customer_users.values_list('<STR_LIT>', flat=True)<EOL>)<EOL>

Get course enrollments for all the learners of given enterprise customer.

Arguments:
    enterprise_customer (EnterpriseCustomer): Include Course enrollments for learners
        of this enterprise customer.
    days (int): Include course enrollment of this number of days.

Returns:
    (list): A list of CourseEnrollment objects.


Parse and validate arguments for the command.

Arguments:
    *args: Positional arguments passed to the command
    **options: Optional arguments passed to the command

Returns:
    A tuple containing parsed values for
    1. days (int): Integer showing number of days to lookup enterprise enrollments,
        course completion etc and send to xAPI LRS
    2. enterprise_customer_uuid (EnterpriseCustomer): Enterprise Customer if present then
        send xAPI statements just for this enterprise.


if not all((PersistentCourseGrade, CourseOverview, CourseGradeFactory)):<EOL><INDENT>raise NotConnectedToOpenEdX("<STR_LIT>")<EOL><DEDENT>days, enterprise_customer = self.parse_arguments(*args, **options)<EOL>if enterprise_customer:<EOL><INDENT>try:<EOL><INDENT>lrs_configuration = XAPILRSConfiguration.objects.get(<EOL>active=True,<EOL>enterprise_customer=enterprise_customer<EOL>)<EOL><DEDENT>except XAPILRSConfiguration.DoesNotExist:<EOL><INDENT>raise CommandError('<STR_LIT>'.format(<EOL>enterprise_customer=enterprise_customer.name<EOL>))<EOL><DEDENT>self.send_xapi_statements(lrs_configuration, days)<EOL><DEDENT>else:<EOL><INDENT>for lrs_configuration in XAPILRSConfiguration.objects.filter(active=True):<EOL><INDENT>self.send_xapi_statements(lrs_configuration, days)<EOL><DEDENT><DEDENT>


persistent_course_grades = self.get_course_completions(lrs_configuration.enterprise_customer, days)<EOL>users = self.prefetch_users(persistent_course_grades)<EOL>course_overviews = self.prefetch_courses(persistent_course_grades)<EOL>for persistent_course_grade in persistent_course_grades:<EOL><INDENT>try:<EOL><INDENT>user = users.get(persistent_course_grade.user_id)<EOL>course_overview = course_overviews.get(persistent_course_grade.course_id)<EOL>course_grade = CourseGradeFactory().read(user, course_key=persistent_course_grade.course_id)<EOL>send_course_completion_statement(lrs_configuration, user, course_overview, course_grade)<EOL><DEDENT>except ClientError:<EOL><INDENT>LOGGER.exception(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>'.format(<EOL>enterprise_customer=lrs_configuration.enterprise_customer.name<EOL>)<EOL>)<EOL><DEDENT><DEDENT>


def get_course_completions(self, enterprise_customer, days):

Get course completions via PersistentCourseGrade for all the learners of given enterprise customer.

Arguments:
    enterprise_customer (EnterpriseCustomer): Include Course enrollments for learners
        of this enterprise customer.
    days (int): Include course enrollment of this number of days.

Returns:
    (list): A list of PersistentCourseGrade objects.


users = User.objects.filter(<EOL>id__in=[grade.user_id for grade in persistent_course_grades]<EOL>)<EOL>return {<EOL>user.id: user for user in users<EOL>}<EOL>

Prefetch Users from the list of user_ids present in the persistent_course_grades.

Arguments:
    persistent_course_grades (list): A list of PersistentCourseGrade.

Returns:
    (dict): A dictionary containing user_id to user mapping.


return CourseOverview.get_from_ids_if_exists(<EOL>[grade.course_id for grade in persistent_course_grades]<EOL>)<EOL>

Prefetch courses from the list of course_ids present in the persistent_course_grades.

Arguments:
    persistent_course_grades (list): A list of PersistentCourseGrade.

Returns:
    (dict): A dictionary containing course_id to course_overview mapping.


Send xAPI statement for course enrollment.

Arguments:
     lrs_configuration (XAPILRSConfiguration): XAPILRSConfiguration instance where to send statements.
     course_enrollment (CourseEnrollment): Course enrollment object.


def send_course_completion_statement(lrs_configuration, user, course_overview, course_grade):

user_details = LearnerInfoSerializer(user)<EOL>course_details = CourseInfoSerializer(course_overview)<EOL>statement = LearnerCourseCompletionStatement(<EOL>user,<EOL>course_overview,<EOL>user_details.data,<EOL>course_details.data,<EOL>course_grade,<EOL>)<EOL>EnterpriseXAPIClient(lrs_configuration).save_statement(statement)<EOL>

Send xAPI statement for course completion.

Arguments:
     lrs_configuration (XAPILRSConfiguration): XAPILRSConfiguration instance where to send statements.
     user (User): Django User object.
     course_overview (CourseOverview): Course over view object containing course details.
     course_grade (CourseGrade): course grade object.


return '<STR_LIT>'.format(<EOL>enterprise_name=self.enterprise_customer.name<EOL>)<EOL>

Return human-readable string representation.


return self.__str__()<EOL>

Return uniquely identifying string representation.


return '<STR_LIT>'.format(<EOL>base64.b64encode('<STR_LIT>'.format(key=self.key, secret=self.secret).encode()).decode()<EOL>)<EOL>

Authorization header for authenticating requests to LRS.


def __init__(self, user, course_overview, user_details, course_details, course_grade, *args, **kwargs):

Initialize and populate statement with learner info and course info.

Arguments:
    user (User): Auth User object containing information about the learner enrolling in the course.
    course_overview (CourseOverview): course overview object containing course details.
    user_details (dict): A dict object containing learner info we want to send in xAPI statement payload.
    course_details (dict): A dict object containing course info we want to send in xAPI statement payload.
    course_grade (CourseGrade): User grade in the course.


return Verb(<EOL>id=X_API_VERB_COMPLETED,<EOL>display=LanguageMap({'<STR_LIT>': '<STR_LIT>'}),<EOL>)<EOL>


Get result for the statement.

Arguments:
    course_grade (CourseGrade): Course grade.


return Agent(<EOL>name=username,<EOL>mbox='<STR_LIT>'.format(email=email),<EOL>)<EOL>


def get_context(self, user_details, course_details):

return Context(<EOL>extensions=Extensions(<EOL>{<EOL>'<STR_LIT>': user_details,<EOL>'<STR_LIT>': course_details,<EOL>},<EOL>)<EOL>)<EOL>


def get_object(self, name, description):

return Activity(<EOL>id=X_API_ACTIVITY_COURSE,<EOL>definition=ActivityDefinition(<EOL>name=LanguageMap({'<STR_LIT>': (name or '<STR_LIT>').encode("<STR_LIT:ascii>", "<STR_LIT:ignore>").decode('<STR_LIT:ascii>')}),<EOL>description=LanguageMap({'<STR_LIT>': (description or '<STR_LIT>').encode("<STR_LIT:ascii>", "<STR_LIT:ignore>").decode('<STR_LIT:ascii>')}),<EOL>),<EOL>)<EOL>


def __init__(self, user, course_overview, user_details, course_details, *args, **kwargs):

Initialize and populate statement with learner info and course info.

Arguments:
    user (User): Auth User object containing information about the learner enrolling in the course.
    course_overview (CourseOverview): course overview object containing course details.
    user_details (dict): A dict object containing learner info we want to send in xAPI statement payload.
    course_details (dict): A dict object containing course info we want to send in xAPI statement payload.


return Verb(<EOL>id=X_API_VERB_REGISTERED,<EOL>display=LanguageMap({'<STR_LIT>': '<STR_LIT>'}),<EOL>)<EOL>

Get verb for course enrollment statement.


super(DegreedAPIClient, self).__init__(enterprise_configuration)<EOL>self.global_degreed_config = apps.get_model('<STR_LIT>', '<STR_LIT>').current()<EOL>self.session = None<EOL>self.expires_at = None<EOL>

Instantiate a new client.

Args:
    enterprise_configuration (DegreedEnterpriseCustomerConfiguration): An enterprise customers's
    configuration model for connecting with Degreed


def create_course_completion(self, user_id, payload):

Send a completion status payload to the Degreed Completion Status endpoint

Args:
    user_id: Unused.
    payload: JSON encoded object (serialized from DegreedLearnerDataTransmissionAudit)
        containing completion status fields per Degreed documentation.

Returns:
    A tuple containing the status code and the body of the response.
Raises:
    HTTPError: if we received a failure response code from Degreed


def delete_course_completion(self, user_id, payload):

Delete a completion status previously sent to the Degreed Completion Status endpoint

Args:
    user_id: Unused.
    payload: JSON encoded object (serialized from DegreedLearnerDataTransmissionAudit)
        containing the required completion status fields for deletion per Degreed documentation.

Returns:
    A tuple containing the status code and the body of the response.
Raises:
    HTTPError: if we received a failure response code from Degreed


self._sync_content_metadata(serialized_data, '<STR_LIT>')<EOL>

Create content metadata using the Degreed course content API.

Args:
    serialized_data: JSON-encoded object containing content metadata.

Raises:
    ClientError: If Degreed API request fails.


Update content metadata using the Degreed course content API.

Args:
    serialized_data: JSON-encoded object containing content metadata.

Raises:
    ClientError: If Degreed API request fails.


Delete content metadata using the Degreed course content API.

Args:
    serialized_data: JSON-encoded object containing content metadata.

Raises:
    ClientError: If Degreed API request fails.


def _sync_content_metadata(self, serialized_data, http_method):

try:<EOL><INDENT>status_code, response_body = getattr(self, '<STR_LIT:_>' + http_method)(<EOL>urljoin(self.enterprise_configuration.degreed_base_url, self.global_degreed_config.course_api_path),<EOL>serialized_data,<EOL>self.CONTENT_PROVIDER_SCOPE<EOL>)<EOL><DEDENT>except requests.exceptions.RequestException as exc:<EOL><INDENT>raise ClientError(<EOL>'<STR_LIT>'.format(<EOL>error=exc.__class__.__name__,<EOL>message=str(exc)<EOL>)<EOL>)<EOL><DEDENT>if status_code >= <NUM_LIT>:<EOL><INDENT>raise ClientError(<EOL>'<STR_LIT>'.format(<EOL>status_code=status_code,<EOL>message=response_body<EOL>)<EOL>)<EOL><DEDENT>

Synchronize content metadata using the Degreed course content API.

Args:
    serialized_data: JSON-encoded object containing content metadata.
    http_method: The HTTP method to use for the API request.

Raises:
    ClientError: If Degreed API request fails.


self._create_session(scope)<EOL>response = self.session.post(url, data=data)<EOL>return response.status_code, response.text<EOL>

Make a POST request using the session object to a Degreed endpoint.

Args:
    url (str): The url to send a POST request to.
    data (str): The json encoded payload to POST.
    scope (str): Must be one of the scopes Degreed expects:
                - `CONTENT_PROVIDER_SCOPE`
                - `COMPLETION_PROVIDER_SCOPE`


self._create_session(scope)<EOL>response = self.session.delete(url, data=data)<EOL>return response.status_code, response.text<EOL>

Make a DELETE request using the session object to a Degreed endpoint.

Args:
    url (str): The url to send a DELETE request to.
    data (str): The json encoded payload to DELETE.
    scope (str): Must be one of the scopes Degreed expects:
                - `CONTENT_PROVIDER_SCOPE`
                - `COMPLETION_PROVIDER_SCOPE`


now = datetime.datetime.utcnow()<EOL>if self.session is None or self.expires_at is None or now >= self.expires_at:<EOL><INDENT>if self.session:<EOL><INDENT>self.session.close()<EOL><DEDENT>oauth_access_token, expires_at = self._get_oauth_access_token(<EOL>self.enterprise_configuration.key,<EOL>self.enterprise_configuration.secret,<EOL>self.enterprise_configuration.degreed_user_id,<EOL>self.enterprise_configuration.degreed_user_password,<EOL>scope<EOL>)<EOL>session = requests.Session()<EOL>session.timeout = self.SESSION_TIMEOUT<EOL>session.headers['<STR_LIT>'] = '<STR_LIT>'.format(oauth_access_token)<EOL>session.headers['<STR_LIT>'] = '<STR_LIT:application/json>'<EOL>self.session = session<EOL>self.expires_at = expires_at<EOL><DEDENT>

Instantiate a new session object for use in connecting with Degreed


def _get_oauth_access_token(self, client_id, client_secret, user_id, user_password, scope):

response = requests.post(<EOL>urljoin(self.enterprise_configuration.degreed_base_url, self.global_degreed_config.oauth_api_path),<EOL>data={<EOL>'<STR_LIT>': '<STR_LIT:password>',<EOL>'<STR_LIT:username>': user_id,<EOL>'<STR_LIT:password>': user_password,<EOL>'<STR_LIT>': scope,<EOL>},<EOL>auth=(client_id, client_secret),<EOL>headers={'<STR_LIT:Content-Type>': '<STR_LIT>'}<EOL>)<EOL>response.raise_for_status()<EOL>data = response.json()<EOL>try:<EOL><INDENT>expires_at = data['<STR_LIT>'] + int(time.time())<EOL>return data['<STR_LIT>'], datetime.datetime.utcfromtimestamp(expires_at)<EOL><DEDENT>except KeyError:<EOL><INDENT>raise requests.RequestException(response=response)<EOL><DEDENT>

Retrieves OAuth 2.0 access token using the client credentials grant.

        Args:
            client_id (str): API client ID
            client_secret (str): API client secret
            user_id (str): Degreed company ID
            user_password (str): Degreed user password
            scope (str): Must be one of the scopes Degreed expects:
                        - `CONTENT_PROVIDER_SCOPE`
                        - `COMPLETION_PROVIDER_SCOPE`

        Returns:
            tuple: Tuple containing access token string and expiration datetime.
        Raises:
            HTTPError: If we received a failure response code from Degreed.
            RequestException: If an unexpected response format was received that we could not parse.


return obj.enterprise_customer.name<EOL>

Returns: the name for the attached EnterpriseCustomer.

Args:
    obj: The instance of DegreedEnterpriseCustomerConfiguration
        being rendered with this admin form.


def __init__(self, enterprise_configuration, client=DegreedAPIClient):

super(DegreedContentMetadataTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>

Use the ``DegreedAPIClient`` for content metadata transmission to Degreed.


super(DegreedLearnerTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>

By default, use the ``DegreedAPIClient`` for learner data transmission to Degreed.


kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>kwargs['<STR_LIT>'] = '<STR_LIT>'<EOL>super(DegreedLearnerTransmitter, self).transmit(payload, **kwargs)<EOL>

Send a completion status call to Degreed using the client.

Args:
    payload: The learner completion data payload to send to Degreed


full_description = content_metadata_item.get('<STR_LIT>') or '<STR_LIT>'<EOL>if <NUM_LIT:0> < len(full_description) <= self.LONG_STRING_LIMIT:  <EOL><INDENT>return full_description<EOL><DEDENT>return content_metadata_item.get('<STR_LIT>') or content_metadata_item.get('<STR_LIT:title>') or '<STR_LIT>'<EOL>

Return the transformed version of the course description.

We choose one value out of the course's full description, short description, and title
depending on availability and length limits.


code = content_metadata_item.get('<STR_LIT>') or '<STR_LIT>'<EOL>return code.split('<STR_LIT:->')[<NUM_LIT:0>] or '<STR_LIT>'<EOL>

Return the ISO 639-1 language code that Degreed expects for coureseruns.

Example:
    en-us -> en
    None -> en


<EOL>return '<STR_LIT>'<EOL>

Return the ISO 639-1 language code that Degreed expects.

Example:
    en-us -> en
    None -> en


image_url = '<STR_LIT>'<EOL>if content_metadata_item['<STR_LIT>'] in ['<STR_LIT>', '<STR_LIT>']:<EOL><INDENT>image_url = content_metadata_item.get('<STR_LIT>')<EOL><DEDENT>elif content_metadata_item['<STR_LIT>'] == '<STR_LIT>':<EOL><INDENT>image_url = content_metadata_item.get('<STR_LIT>')<EOL><DEDENT>return image_url<EOL>

Return the image URI of the content item.


return content_metadata_item['<STR_LIT>']<EOL>

Return the identifier of the program content item.


<EOL>completed_timestamp = completed_date.strftime("<STR_LIT>") if isinstance(completed_date, datetime) else None<EOL>if enterprise_enrollment.enterprise_customer_user.get_remote_id() is not None:<EOL><INDENT>DegreedLearnerDataTransmissionAudit = apps.get_model(  <EOL>'<STR_LIT>',<EOL>'<STR_LIT>'<EOL>)<EOL>return [<EOL>DegreedLearnerDataTransmissionAudit(<EOL>enterprise_course_enrollment_id=enterprise_enrollment.id,<EOL>degreed_user_email=enterprise_enrollment.enterprise_customer_user.user_email,<EOL>course_id=parse_course_key(enterprise_enrollment.course_id),<EOL>course_completed=completed_date is not None and is_passing,<EOL>completed_timestamp=completed_timestamp,<EOL>),<EOL>DegreedLearnerDataTransmissionAudit(<EOL>enterprise_course_enrollment_id=enterprise_enrollment.id,<EOL>degreed_user_email=enterprise_enrollment.enterprise_customer_user.user_email,<EOL>course_id=enterprise_enrollment.course_id,<EOL>course_completed=completed_date is not None and is_passing,<EOL>completed_timestamp=completed_timestamp,<EOL>)<EOL>]<EOL><DEDENT>else:<EOL><INDENT>LOGGER.debug(<EOL>'<STR_LIT>',<EOL>enterprise_enrollment.enterprise_customer_user.username<EOL>)<EOL><DEDENT>

Return a DegreedLearnerDataTransmissionAudit with the given enrollment and course completion data.

If completed_date is None, then course completion has not been met.

If no remote ID can be found, return None.


return "<STR_LIT>".format(id=self.id)<EOL>

Return a human-readable string representation of the object.


return self.__str__()<EOL>

Return uniquely identifying string representation.


return "<STR_LIT>".format(<EOL>enterprise_name=self.enterprise_customer.name<EOL>)<EOL>

Return human-readable string representation.


return self.__str__()<EOL>


return '<STR_LIT>'<EOL>

Returns an capitalized identifier for this channel class, unique among subclasses.


return DegreedLearnerTransmitter(self)<EOL>

Return a ``DegreedLearnerTransmitter`` instance.


return DegreedLearnerExporter(user, self)<EOL>

Return a ``DegreedLearnerDataExporter`` instance.


return DegreedContentMetadataTransmitter(self)<EOL>

Return a ``DegreedContentMetadataTransmitter`` instance.


return DegreedContentMetadataExporter(user, self)<EOL>

Return a ``DegreedContentMetadataExporter`` instance.


enterprise_configuration = kwargs.get('<STR_LIT>')<EOL>degreed_company_id = enterprise_configuration.degreed_company_idif hasattr(enterprise_configuration, '<STR_LIT>') else '<STR_LIT>'<EOL>return json.dumps(self._payload_data(degreed_company_id), sort_keys=True)<EOL>

Return a JSON-serialized representation.

Sort the keys so the result is consistent and testable.

Can take the following keyword arguments:
    - `enterprise_configuration`: used to get the `degreed_company_id` to be sent in the payload.

# TODO: When we refactor to use a serialization flow consistent with how course metadata
# is serialized, remove the serialization here and make the learner data exporter handle the work.


return {<EOL>'<STR_LIT>': degreed_company_id,<EOL>'<STR_LIT>': [{<EOL>'<STR_LIT:email>': self.degreed_user_email,<EOL>'<STR_LIT:id>': self.course_id,<EOL>'<STR_LIT>': self.completed_timestamp,<EOL>}]<EOL>}<EOL>

Convert the audit record's fields into Degreed key/value pairs.


interval_start = parse_lms_api_datetime(start or UNIX_MIN_DATE_STRING)<EOL>interval_end = parse_lms_api_datetime(end or UNIX_MAX_DATE_STRING)<EOL>return interval_start <= timezone.now() <= interval_end<EOL>

Determine whether the current time is on the interval [start, end].


iterable = iter(dictionary)<EOL>for __ in range(<NUM_LIT:0>, len(dictionary), chunk_size):<EOL><INDENT>yield {key: dictionary[key] for key in islice(iterable, chunk_size)}<EOL><DEDENT>

Yield successive n-sized chunks from dictionary.


def strfdelta(tdelta, fmt='<STR_LIT>', input_type='<STR_LIT>'):

<EOL>if input_type == '<STR_LIT>':<EOL><INDENT>remainder = int(tdelta.total_seconds())<EOL><DEDENT>elif input_type in ['<STR_LIT:s>', '<STR_LIT>']:<EOL><INDENT>remainder = int(tdelta)<EOL><DEDENT>elif input_type in ['<STR_LIT:m>', '<STR_LIT>']:<EOL><INDENT>remainder = int(tdelta) * <NUM_LIT><EOL><DEDENT>elif input_type in ['<STR_LIT:h>', '<STR_LIT>']:<EOL><INDENT>remainder = int(tdelta) * <NUM_LIT><EOL><DEDENT>elif input_type in ['<STR_LIT:d>', '<STR_LIT>']:<EOL><INDENT>remainder = int(tdelta) * <NUM_LIT><EOL><DEDENT>elif input_type in ['<STR_LIT:w>', '<STR_LIT>']:<EOL><INDENT>remainder = int(tdelta) * <NUM_LIT><EOL><DEDENT>else:<EOL><INDENT>raise ValueError(<EOL>'<STR_LIT>'<EOL>)<EOL><DEDENT>f = Formatter()<EOL>desired_fields = [field_tuple[<NUM_LIT:1>] for field_tuple in f.parse(fmt)]<EOL>possible_fields = ('<STR_LIT>', '<STR_LIT:D>', '<STR_LIT:H>', '<STR_LIT:M>', '<STR_LIT:S>')<EOL>constants = {'<STR_LIT>': <NUM_LIT>, '<STR_LIT:D>': <NUM_LIT>, '<STR_LIT:H>': <NUM_LIT>, '<STR_LIT:M>': <NUM_LIT>, '<STR_LIT:S>': <NUM_LIT:1>}<EOL>values = {}<EOL>for field in possible_fields:<EOL><INDENT>if field in desired_fields and field in constants:<EOL><INDENT>values[field], remainder = divmod(remainder, constants[field])<EOL><DEDENT><DEDENT>return f.format(fmt, **values)<EOL>

Convert a datetime.timedelta object or a regular number to a custom-formatted string.

This function works like the strftime() method works for datetime.datetime
objects.

The fmt argument allows custom formatting to be specified.  Fields can
include seconds, minutes, hours, days, and weeks.  Each field is optional.

Arguments:
    tdelta (datetime.timedelta, int): time delta object containing the duration or an integer
        to go with the input_type.
    fmt (str): Expected format of the time delta. place holders can only be one of the following.
        1. D to extract days from time delta
        2. H to extract hours from time delta
        3. M to extract months from time delta
        4. S to extract seconds from timedelta
    input_type (str):  The input_type argument allows tdelta to be a regular number instead of the
        default, which is a datetime.timedelta object.
        Valid input_type strings:
            1. 's', 'seconds',
            2. 'm', 'minutes',
            3. 'h', 'hours',
            4. 'd', 'days',
            5. 'w', 'weeks'
Returns:
    (str): timedelta object interpolated into a string following the given format.

Examples:
    '{D:02}d {H:02}h {M:02}m {S:02}s' --> '05d 08h 04m 02s' (default)
    '{W}w {D}d {H}:{M:02}:{S:02}'     --> '4w 5d 8:04:02'
    '{D:2}d {H:2}:{M:02}:{S:02}'      --> ' 5d  8:04:02'
    '{H}h {S}s'                       --> '72h 800s'


if not enterprise_configuration:<EOL><INDENT>raise ValueError(<EOL>'<STR_LIT>'<EOL>)<EOL><DEDENT>self.enterprise_configuration = enterprise_configuration<EOL>

Instantiate a new base client.

Args:
    enterprise_configuration: An enterprise customers's configuration model for connecting with the channel

Raises:
    ValueError: If an enterprise configuration is not provided.


raise NotImplementedError('<STR_LIT>')<EOL>

Make a POST request to the integrated channel's completion API to update completion status for a user.

:param user_id: The ID of the user for whom completion status must be updated.
:param payload: The JSON encoded payload containing the completion data.


Make a DELETE request to the integrated channel's completion API to update completion status for a user.

:param user_id: The ID of the user for whom completion status must be updated.
:param payload: The JSON encoded payload containing the completion data.


raise NotImplementedError()<EOL>

Create content metadata using the integrated channel's API.


raise NotImplementedError()<EOL>

Update content metadata using the integrated channel's API.


raise NotImplementedError()<EOL>

Delete content metadata using the integrated channel's API.


def __init__(self, enterprise_configuration, client=IntegratedChannelApiClient):

super(ContentMetadataTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>

By default, use the abstract integrated channel API client which raises an error when used if not subclassed.


items_to_create, items_to_update, items_to_delete, transmission_map = self._partition_items(payload)<EOL>self._transmit_delete(items_to_delete)<EOL>self._transmit_create(items_to_create)<EOL>self._transmit_update(items_to_update, transmission_map)<EOL>

Transmit content metadata items to the integrated channel.


items_to_create = {}<EOL>items_to_update = {}<EOL>items_to_delete = {}<EOL>transmission_map = {}<EOL>export_content_ids = channel_metadata_item_map.keys()<EOL>for transmission in self._get_transmissions():<EOL><INDENT>transmission_map[transmission.content_id] = transmission<EOL>if transmission.content_id not in export_content_ids:<EOL><INDENT>items_to_delete[transmission.content_id] = transmission.channel_metadata<EOL><DEDENT><DEDENT>for item in channel_metadata_item_map.values():<EOL><INDENT>content_id = item.content_id<EOL>channel_metadata = item.channel_metadata<EOL>transmitted_item = transmission_map.get(content_id, None)<EOL>if transmitted_item is not None:<EOL><INDENT>if diff(channel_metadata, transmitted_item.channel_metadata):<EOL><INDENT>items_to_update[content_id] = channel_metadata<EOL><DEDENT><DEDENT>else:<EOL><INDENT>items_to_create[content_id] = channel_metadata<EOL><DEDENT><DEDENT>LOGGER.info(<EOL>'<STR_LIT>',<EOL>len(items_to_create),<EOL>self.enterprise_configuration,<EOL>items_to_create.keys(),<EOL>)<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>len(items_to_update),<EOL>self.enterprise_configuration,<EOL>items_to_update.keys(),<EOL>)<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>len(items_to_delete),<EOL>self.enterprise_configuration,<EOL>items_to_delete.keys(),<EOL>)<EOL>return items_to_create, items_to_update, items_to_delete, transmission_map<EOL>

Return items that need to be created, updated, and deleted along with the
current ContentMetadataItemTransmissions.


return channel_metadata_items<EOL>

Perform any necessary modifications to content metadata item
data structure before transmission. This can be overidden by
subclasses to add any data structure wrappers expected by the
integrated channel.


return json.dumps(<EOL>self._prepare_items_for_transmission(channel_metadata_items),<EOL>sort_keys=True<EOL>).encode('<STR_LIT:utf-8>')<EOL>

Serialize content metadata items for a create transmission to the integrated channel.


Transmit content metadata creation to integrated channel.


def _transmit_update(self, channel_metadata_item_map, transmission_map):

for chunk in chunks(channel_metadata_item_map, self.enterprise_configuration.transmission_chunk_size):<EOL><INDENT>serialized_chunk = self._serialize_items(list(chunk.values()))<EOL>try:<EOL><INDENT>self.client.update_content_metadata(serialized_chunk)<EOL><DEDENT>except ClientError as exc:<EOL><INDENT>LOGGER.error(<EOL>'<STR_LIT>',<EOL>len(chunk),<EOL>self.enterprise_configuration.enterprise_customer.name,<EOL>self.enterprise_configuration.channel_code,<EOL>)<EOL>LOGGER.error(exc)<EOL><DEDENT>else:<EOL><INDENT>self._update_transmissions(chunk, transmission_map)<EOL><DEDENT><DEDENT>

Transmit content metadata update to integrated channel.


Transmit content metadata deletion to integrated channel.


Return the ContentMetadataItemTransmision models for previously
transmitted content metadata items.


Create ContentMetadataItemTransmision models for the given content metadata items.


def _update_transmissions(self, content_metadata_item_map, transmission_map):

for content_id, channel_metadata in content_metadata_item_map.items():<EOL><INDENT>transmission = transmission_map[content_id]<EOL>transmission.channel_metadata = channel_metadata<EOL>transmission.save()<EOL><DEDENT>

Update ContentMetadataItemTransmision models for the given content metadata items.


Delete ContentMetadataItemTransmision models associated with the given content metadata items.


def __init__(self, enterprise_configuration, client=None):

self.enterprise_configuration = enterprise_configuration<EOL>self.client = client(enterprise_configuration) if client else None<EOL>

Prepares a configuration and a client to be used to transmit data to an integrated channel.

Arguments:
    * enterprise_configuration - The configuration connecting an enterprise to an integrated channel.
    * client - The REST API client that'll transmit serialized data.


The abstract interface method for sending exported data to an integrated channel through its API client.


super(LearnerTransmitter, self).__init__(<EOL>enterprise_configuration=enterprise_configuration,<EOL>client=client<EOL>)<EOL>


IntegratedChannelLearnerDataTransmissionAudit = apps.get_model(  <EOL>app_label=kwargs.get('<STR_LIT>', '<STR_LIT>'),<EOL>model_name=kwargs.get('<STR_LIT>', '<STR_LIT>'),<EOL>)<EOL>for learner_data in payload.export():<EOL><INDENT>serialized_payload = learner_data.serialize(enterprise_configuration=self.enterprise_configuration)<EOL>LOGGER.debug('<STR_LIT>', serialized_payload)<EOL>enterprise_enrollment_id = learner_data.enterprise_course_enrollment_id<EOL>if learner_data.completed_timestamp is None:<EOL><INDENT>LOGGER.info('<STR_LIT>'.format(enterprise_enrollment_id))<EOL>continue<EOL><DEDENT>previous_transmissions = IntegratedChannelLearnerDataTransmissionAudit.objects.filter(<EOL>enterprise_course_enrollment_id=enterprise_enrollment_id,<EOL>error_message='<STR_LIT>'<EOL>)<EOL>if previous_transmissions.exists():<EOL><INDENT>LOGGER.info('<STR_LIT>'.format(enterprise_enrollment_id))<EOL>continue<EOL><DEDENT>try:<EOL><INDENT>code, body = self.client.create_course_completion(<EOL>getattr(learner_data, kwargs.get('<STR_LIT>')),<EOL>serialized_payload<EOL>)<EOL>LOGGER.info(<EOL>'<STR_LIT>'.format(<EOL>enterprise_enrollment_id,<EOL>)<EOL>)<EOL><DEDENT>except RequestException as request_exception:<EOL><INDENT>code = <NUM_LIT><EOL>body = str(request_exception)<EOL>self.handle_transmission_error(learner_data, request_exception)<EOL><DEDENT>learner_data.status = str(code)<EOL>learner_data.error_message = body if code >= <NUM_LIT> else '<STR_LIT>'<EOL>learner_data.save()<EOL><DEDENT>

Send a completion status call to the integrated channel using the client.

Args:
    payload: The learner completion data payload to send to the integrated channel.
    kwargs: Contains integrated channel-specific information for customized transmission variables.
        - app_label: The app label of the integrated channel for whom to store learner data records for.
        - model_name: The name of the specific learner data record model to use.
        - remote_user_id: The remote ID field name of the learner on the audit model.


def handle_transmission_error(self, learner_data, request_exception):

Handle the case where the transmission fails.


def __init__(self, user, enterprise_configuration):

super(ContentMetadataExporter, self).__init__(user, enterprise_configuration)<EOL>self.enterprise_api = EnterpriseApiClient(self.user)<EOL>


content_metadata_export = {}<EOL>content_metadata_items = self.enterprise_api.get_content_metadata(self.enterprise_customer)<EOL>LOGGER.info('<STR_LIT>', self.enterprise_customer.name)<EOL>for item in content_metadata_items:<EOL><INDENT>transformed = self._transform_item(item)<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>self.enterprise_configuration,<EOL>json.dumps(transformed, indent=<NUM_LIT:4>),<EOL>)<EOL>content_metadata_item_export = ContentMetadataItemExport(item, transformed)<EOL>content_metadata_export[content_metadata_item_export.content_id] = content_metadata_item_export<EOL><DEDENT>return OrderedDict(sorted(content_metadata_export.items()))<EOL>

Return the exported and transformed content metadata as a dictionary.


content_metadata_type = content_metadata_item['<STR_LIT>']<EOL>transformed_item = {}<EOL>for integrated_channel_schema_key, edx_data_schema_key in self.DATA_TRANSFORM_MAPPING.items():<EOL><INDENT>transformer = (<EOL>getattr(<EOL>self,<EOL>'<STR_LIT>'.format(<EOL>content_type=content_metadata_type,<EOL>edx_data_schema_key=edx_data_schema_key<EOL>),<EOL>None<EOL>)<EOL>or<EOL>getattr(<EOL>self,<EOL>'<STR_LIT>'.format(<EOL>edx_data_schema_key=edx_data_schema_key<EOL>),<EOL>None<EOL>)<EOL>)<EOL>if transformer:<EOL><INDENT>transformed_item[integrated_channel_schema_key] = transformer(content_metadata_item)<EOL><DEDENT>else:<EOL><INDENT>try:<EOL><INDENT>transformed_item[integrated_channel_schema_key] = content_metadata_item[edx_data_schema_key]<EOL><DEDENT>except KeyError:<EOL><INDENT>LOGGER.exception(<EOL>'<STR_LIT>',<EOL>edx_data_schema_key,<EOL>self.enterprise_customer.name,<EOL>content_metadata_item,<EOL>)<EOL><DEDENT><DEDENT><DEDENT>return transformed_item<EOL>

Transform the provided content metadata item to the schema expected by the integrated channel.


self.user = user<EOL>self.enterprise_configuration = enterprise_configuration<EOL>self.enterprise_customer = enterprise_configuration.enterprise_customer<EOL>

Store the data needed to export the learner data to the integrated channel.

Arguments:
    * user: User instance with access to the Grades API for the Enterprise Customer's courses.
    * enterprise_configuration - The configuration connecting an enterprise to an integrated channel.


Export (read: serialize) data to be used by a transmitter to transmit to an integrated channel.


<EOL>self.grades_api = None<EOL>self.certificates_api = None<EOL>self.course_api = None<EOL>self.course_enrollment_api = None<EOL>super(LearnerExporter, self).__init__(user, enterprise_configuration)<EOL>

Store the data needed to export the learner data to the integrated channel.

Arguments:

* ``user``: User instance with access to the Grades API for the Enterprise Customer's courses.
* ``enterprise_configuration``: EnterpriseCustomerPluginConfiguration instance for the current channel.


return self.GRADE_PASSING<EOL>

Returns the string used for a passing grade.


return self.GRADE_FAILING<EOL>

Returns the string used for a failing grade.


return self.GRADE_INCOMPLETE<EOL>

Returns the string used for an incomplete course grade.


<EOL>enrollment_queryset = EnterpriseCourseEnrollment.objects.select_related(<EOL>'<STR_LIT>'<EOL>).filter(<EOL>enterprise_customer_user__enterprise_customer=self.enterprise_customer,<EOL>enterprise_customer_user__active=True,<EOL>).order_by('<STR_LIT>')<EOL>course_details = None<EOL>for enterprise_enrollment in enrollment_queryset:<EOL><INDENT>course_id = enterprise_enrollment.course_id<EOL>if course_details is None or course_details['<STR_LIT>'] != course_id:<EOL><INDENT>if self.course_api is None:<EOL><INDENT>self.course_api = CourseApiClient()<EOL><DEDENT>course_details = self.course_api.get_course_details(course_id)<EOL><DEDENT>if course_details is None:<EOL><INDENT>LOGGER.error("<STR_LIT>",<EOL>enterprise_enrollment.pk, course_id)<EOL>continue<EOL><DEDENT>consent = DataSharingConsent.objects.proxied_get(<EOL>username=enterprise_enrollment.enterprise_customer_user.username,<EOL>course_id=enterprise_enrollment.course_id,<EOL>enterprise_customer=enterprise_enrollment.enterprise_customer_user.enterprise_customer<EOL>)<EOL>if not consent.granted or enterprise_enrollment.audit_reporting_disabled:<EOL><INDENT>continue<EOL><DEDENT>if course_details.get('<STR_LIT>') == '<STR_LIT>':<EOL><INDENT>completed_date, grade, is_passing = self._collect_certificate_data(enterprise_enrollment)<EOL><DEDENT>else:<EOL><INDENT>completed_date, grade, is_passing = self._collect_grades_data(enterprise_enrollment, course_details)<EOL><DEDENT>records = self.get_learner_data_records(<EOL>enterprise_enrollment=enterprise_enrollment,<EOL>completed_date=completed_date,<EOL>grade=grade,<EOL>is_passing=is_passing,<EOL>)<EOL>if records:<EOL><INDENT>for record in records:<EOL><INDENT>yield record<EOL><DEDENT><DEDENT><DEDENT>

Collect learner data for the ``EnterpriseCustomer`` where data sharing consent is granted.

Yields a learner data object for each enrollment, containing:

* ``enterprise_enrollment``: ``EnterpriseCourseEnrollment`` object.
* ``completed_date``: datetime instance containing the course/enrollment completion date; None if not complete.
  "Course completion" occurs for instructor-paced courses when course certificates are issued, and
  for self-paced courses, when the course end date is passed, or when the learner achieves a passing grade.
* ``grade``: string grade recorded for the learner in the course.


def get_learner_data_records(self, enterprise_enrollment, completed_date=None, grade=None, is_passing=False):

<EOL>LearnerDataTransmissionAudit = apps.get_model('<STR_LIT>', '<STR_LIT>')<EOL>completed_timestamp = None<EOL>course_completed = False<EOL>if completed_date is not None:<EOL><INDENT>completed_timestamp = parse_datetime_to_epoch_millis(completed_date)<EOL>course_completed = is_passing<EOL><DEDENT>return [<EOL>LearnerDataTransmissionAudit(<EOL>enterprise_course_enrollment_id=enterprise_enrollment.id,<EOL>course_id=enterprise_enrollment.course_id,<EOL>course_completed=course_completed,<EOL>completed_timestamp=completed_timestamp,<EOL>grade=grade,<EOL>)<EOL>]<EOL>

Generate a learner data transmission audit with fields properly filled in.


if self.certificates_api is None:<EOL><INDENT>self.certificates_api = CertificatesApiClient(self.user)<EOL><DEDENT>course_id = enterprise_enrollment.course_id<EOL>username = enterprise_enrollment.enterprise_customer_user.user.username<EOL>try:<EOL><INDENT>certificate = self.certificates_api.get_course_certificate(course_id, username)<EOL>completed_date = certificate.get('<STR_LIT>')<EOL>if completed_date:<EOL><INDENT>completed_date = parse_datetime(completed_date)<EOL><DEDENT>else:<EOL><INDENT>completed_date = timezone.now()<EOL><DEDENT>is_passing = certificate.get('<STR_LIT>')<EOL>grade = self.grade_passing if is_passing else self.grade_failing<EOL><DEDENT>except HttpNotFoundError:<EOL><INDENT>completed_date = None<EOL>grade = self.grade_incomplete<EOL>is_passing = False<EOL><DEDENT>return completed_date, grade, is_passing<EOL>

Collect the learner completion data from the course certificate.

Used for Instructor-paced courses.

If no certificate is found, then returns the completed_date = None, grade = In Progress, on the idea that a
certificate will eventually be generated.

Args:
    enterprise_enrollment (EnterpriseCourseEnrollment): the enterprise enrollment record for which we need to
    collect completion/grade data

Returns:
    completed_date: Date the course was completed, this is None if course has not been completed.
    grade: Current grade in the course.
    is_passing: Boolean indicating if the grade is a passing grade or not.


def _collect_grades_data(self, enterprise_enrollment, course_details):

if self.grades_api is None:<EOL><INDENT>self.grades_api = GradesApiClient(self.user)<EOL><DEDENT>course_id = enterprise_enrollment.course_id<EOL>username = enterprise_enrollment.enterprise_customer_user.user.username<EOL>try:<EOL><INDENT>grades_data = self.grades_api.get_course_grade(course_id, username)<EOL><DEDENT>except HttpNotFoundError as error:<EOL><INDENT>if hasattr(error, '<STR_LIT:content>'):<EOL><INDENT>response_content = json.loads(error.content)<EOL>if response_content.get('<STR_LIT>', '<STR_LIT>') == '<STR_LIT>':<EOL><INDENT>LOGGER.info(<EOL>"<STR_LIT>",<EOL>username,<EOL>course_id,<EOL>enterprise_enrollment.pk<EOL>)<EOL>return None, None, None<EOL><DEDENT><DEDENT>LOGGER.error("<STR_LIT>", enterprise_enrollment.pk, course_id, username)<EOL>return None, None, None<EOL><DEDENT>course_end_date = course_details.get('<STR_LIT:end>')<EOL>if course_end_date is not None:<EOL><INDENT>course_end_date = parse_datetime(course_end_date)<EOL><DEDENT>now = timezone.now()<EOL>is_passing = grades_data.get('<STR_LIT>')<EOL>if course_end_date is not None and course_end_date < now:<EOL><INDENT>completed_date = course_end_date<EOL>grade = self.grade_passing if is_passing else self.grade_failing<EOL><DEDENT>elif is_passing:<EOL><INDENT>completed_date = now<EOL>grade = self.grade_passing<EOL><DEDENT>else:<EOL><INDENT>completed_date = None<EOL>grade = self.grade_incomplete<EOL><DEDENT>return completed_date, grade, is_passing<EOL>

Collect the learner completion data from the Grades API.

Used for self-paced courses.

Args:
    enterprise_enrollment (EnterpriseCourseEnrollment): the enterprise enrollment record for which we need to
    collect completion/grade data
    course_details (dict): the course details for the course in the enterprise enrollment record.

Returns:
    completed_date: Date the course was completed, this is None if course has not been completed.
    grade: Current grade in the course.
    is_passing: Boolean indicating if the grade is a passing grade or not.


@shared_task<EOL>def transmit_content_metadata(username, channel_code, channel_pk):

start = time.time()<EOL>api_user = User.objects.get(username=username)<EOL>integrated_channel = INTEGRATED_CHANNEL_CHOICES[channel_code].objects.get(pk=channel_pk)<EOL>LOGGER.info('<STR_LIT>', integrated_channel)<EOL>try:<EOL><INDENT>integrated_channel.transmit_content_metadata(api_user)<EOL><DEDENT>except Exception:  <EOL><INDENT>LOGGER.exception(<EOL>'<STR_LIT>'<EOL>'<STR_LIT>', username, channel_code, channel_pk<EOL>)<EOL><DEDENT>duration = time.time() - start<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>integrated_channel,<EOL>duration<EOL>)<EOL>

Task to send content metadata to each linked integrated channel.

Arguments:
    username (str): The username of the User to be used for making API requests to retrieve content metadata.
    channel_code (str): Capitalized identifier for the integrated channel.
    channel_pk (str): Primary key for identifying integrated channel.


@shared_task<EOL>def transmit_learner_data(username, channel_code, channel_pk):

start = time.time()<EOL>api_user = User.objects.get(username=username)<EOL>integrated_channel = INTEGRATED_CHANNEL_CHOICES[channel_code].objects.get(pk=channel_pk)<EOL>LOGGER.info('<STR_LIT>', integrated_channel)<EOL>integrated_channel.transmit_learner_data(api_user)<EOL>duration = time.time() - start<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>integrated_channel,<EOL>duration<EOL>)<EOL>

Task to send learner data to each linked integrated channel.

Arguments:
    username (str): The username of the User to be used for making API requests for learner data.
    channel_code (str): Capitalized identifier for the integrated channel
    channel_pk (str): Primary key for identifying integrated channel


start = time.time()<EOL>integrated_channel = INTEGRATED_CHANNEL_CHOICES[channel_code].objects.get(pk=channel_pk)<EOL>LOGGER.info('<STR_LIT>', integrated_channel)<EOL>integrated_channel.unlink_inactive_learners()<EOL>duration = time.time() - start<EOL>LOGGER.info(<EOL>'<STR_LIT>',<EOL>integrated_channel,<EOL>duration<EOL>)<EOL>

Task to unlink inactive learners of provided integrated channel.

Arguments:
    channel_code (str): Capitalized identifier for the integrated channel
    channel_pk (str): Primary key for identifying integrated channel


parser.add_argument(<EOL>'<STR_LIT>',<EOL>dest='<STR_LIT>',<EOL>required=True,<EOL>metavar='<STR_LIT>',<EOL>help=_('<STR_LIT>'),<EOL>)<EOL>super(Command, self).add_arguments(parser)<EOL>

Add required --api_user argument to the parser.


<EOL>api_username = options['<STR_LIT>']<EOL>try:<EOL><INDENT>User.objects.get(username=api_username)<EOL><DEDENT>except User.DoesNotExist:<EOL><INDENT>raise CommandError(_('<STR_LIT>').format(username=api_username))<EOL><DEDENT>for integrated_channel in self.get_integrated_channels(options):<EOL><INDENT>transmit_learner_data.delay(api_username, integrated_channel.channel_code(), integrated_channel.pk)<EOL><DEDENT>

Transmit the learner data for the EnterpriseCustomer(s) to the active integration channels.


parser.add_argument(<EOL>'<STR_LIT>',<EOL>dest='<STR_LIT>',<EOL>required=True,<EOL>metavar='<STR_LIT>',<EOL>help='<STR_LIT>'<EOL>)<EOL>super(Command, self).add_arguments(parser)<EOL>


username = options['<STR_LIT>']<EOL>try:<EOL><INDENT>User.objects.get(username=username)<EOL><DEDENT>except User.DoesNotExist:<EOL><INDENT>raise CommandError('<STR_LIT>'.format(username))<EOL><DEDENT>channels = self.get_integrated_channels(options)<EOL>for channel in channels:<EOL><INDENT>channel_code = channel.channel_code()<EOL>channel_pk = channel.pk<EOL>transmit_content_metadata.delay(username, channel_code, channel_pk)<EOL><DEDENT>

Transmit the courseware data for the EnterpriseCustomer(s) to the active integration channels.


Adds the optional arguments: ``--enterprise_customer``, ``--channel``