diff --git a/enterprise_learner_portal/api/v1/serializers.py b/enterprise_learner_portal/api/v1/serializers.py index 925d47d641..0f91e59d39 100644 --- a/enterprise_learner_portal/api/v1/serializers.py +++ b/enterprise_learner_portal/api/v1/serializers.py @@ -78,6 +78,7 @@ def to_representation(self, instance): representation['is_revoked'] = instance.license.is_revoked if instance.license else False representation['is_enrollment_active'] = instance.is_active representation['mode'] = instance.mode + representation['resume_course_run_url'] = self.context['course_enrollments_resume_urls'].get(course_run_id) if CourseDetails: course_details = CourseDetails.objects.filter(id=course_run_id).first() diff --git a/enterprise_learner_portal/api/v1/views.py b/enterprise_learner_portal/api/v1/views.py index f9b4d4d85d..7319253e38 100644 --- a/enterprise_learner_portal/api/v1/views.py +++ b/enterprise_learner_portal/api/v1/views.py @@ -20,6 +20,10 @@ from openedx.core.djangoapps.content.course_overviews.api import get_course_overviews except ImportError: get_course_overviews = None +try: + from lms.djangoapps.learner_home.views import get_resume_urls_for_course_enrollments +except ImportError: + get_resume_urls_for_course_enrollments = None class EnterpriseCourseEnrollmentView(APIView): @@ -86,10 +90,22 @@ def get(self, request): course_overviews = get_course_overviews([record.course_id for record in filtered_enterprise_enrollments]) + if get_resume_urls_for_course_enrollments: + course_enrollments_resume_urls = get_resume_urls_for_course_enrollments( + user, + list(filtered_enterprise_enrollments) + ) + data = EnterpriseCourseEnrollmentSerializer( filtered_enterprise_enrollments, many=True, - context={'request': request, 'course_overviews': course_overviews}, + context={ + 'request': request, + 'course_overviews': course_overviews, + 'course_enrollments_resume_urls': ( + course_enrollments_resume_urls if course_enrollments_resume_urls else None + ) + }, ).data if request.query_params.get('is_active'): diff --git a/tests/test_enterprise_learner_portal/api/test_serializers.py b/tests/test_enterprise_learner_portal/api/test_serializers.py index fc04ccbbd0..42e1854c5f 100644 --- a/tests/test_enterprise_learner_portal/api/test_serializers.py +++ b/tests/test_enterprise_learner_portal/api/test_serializers.py @@ -64,6 +64,9 @@ def test_serializer_representation( 'pacing': 'instructor', 'display_org_with_default': 'my university', }] + course_enrollments_resume_urls = { + course_run_id: "http://example.com/resume_url", + } mock_get_cert.return_value = { 'download_url': 'example.com', @@ -93,7 +96,11 @@ def test_serializer_representation( serializer = EnterpriseCourseEnrollmentSerializer( [enterprise_enrollment], many=True, - context={'request': request, 'course_overviews': course_overviews}, + context={ + 'request': request, + 'course_overviews': course_overviews, + 'course_enrollments_resume_urls': course_enrollments_resume_urls + }, ) expected = OrderedDict([ @@ -112,6 +119,7 @@ def test_serializer_representation( ('is_revoked', False), ('is_enrollment_active', True), ('mode', 'verified'), + ('resume_course_run_url', 'http://example.com/resume_url'), ]) actual = serializer.data[0] self.assertDictEqual(actual, expected) diff --git a/tests/test_enterprise_learner_portal/api/test_views.py b/tests/test_enterprise_learner_portal/api/test_views.py index 92fad451c8..36bfe2f1e3 100644 --- a/tests/test_enterprise_learner_portal/api/test_views.py +++ b/tests/test_enterprise_learner_portal/api/test_views.py @@ -69,15 +69,19 @@ def setUp(self): self.client = Client() self.client.login(username=self.user.username, password="QWERTY") + @mock.patch('enterprise_learner_portal.api.v1.views.get_resume_urls_for_course_enrollments') @mock.patch('enterprise_learner_portal.api.v1.views.EnterpriseCourseEnrollmentSerializer') @mock.patch('enterprise_learner_portal.api.v1.views.get_course_overviews') - def test_view_returns_information(self, mock_get_overviews, mock_serializer): + def test_view_returns_information(self, mock_get_overviews, mock_serializer, mock_course_resume_urls): """ View should return data created by EnterpriseCourseEnrollmentSerializer (which we mock in this case) """ mock_get_overviews.return_value = {'overview_info': 'this would be a larger dict'} mock_serializer.return_value = self.MockSerializer() + mock_course_resume_urls.return_value = { + 'course-v1:edX+DemoX+Demo_Course': 'http://example.com/resume_url' + } resp = self.client.get( '{host}{path}?enterprise_id={enterprise_id}'.format( @@ -92,6 +96,7 @@ def test_view_returns_information(self, mock_get_overviews, mock_serializer): SERIALIZED_MOCK_INACTIVE_ENROLLMENT, ] + @mock.patch('enterprise_learner_portal.api.v1.views.get_resume_urls_for_course_enrollments') @mock.patch('enterprise_learner_portal.api.v1.views.EnterpriseCourseEnrollmentSerializer') @mock.patch('enterprise_learner_portal.api.v1.views.get_course_overviews') @ddt.data('true', 'false') @@ -100,6 +105,7 @@ def test_view_get_filters_active_enrollments( active_filter_value, mock_get_overviews, mock_serializer, + mock_course_resume_urls, ): """ View should return data created by EnterpriseCourseEnrollmentSerializer @@ -107,6 +113,9 @@ def test_view_get_filters_active_enrollments( """ mock_get_overviews.return_value = {'overview_info': 'this would be a larger dict'} mock_serializer.return_value = self.MockSerializer() + mock_course_resume_urls.return_value = { + 'course-v1:edX+DemoX+Demo_Course': 'http://example.com/resume_url' + } resp = self.client.get( '{host}{path}?enterprise_id={enterprise_id}&is_active={active_filter_value}'.format( @@ -123,14 +132,23 @@ def test_view_get_filters_active_enrollments( expected_result = [SERIALIZED_MOCK_INACTIVE_ENROLLMENT] assert resp.json() == expected_result + @mock.patch('enterprise_learner_portal.api.v1.views.get_resume_urls_for_course_enrollments') @mock.patch('enterprise_learner_portal.api.v1.views.EnterpriseCourseEnrollmentSerializer') @mock.patch('enterprise_learner_portal.api.v1.views.get_course_overviews') - def test_view_returns_bad_request_without_enterprise(self, mock_get_overviews, mock_serializer): + def test_view_returns_bad_request_without_enterprise( + self, + mock_get_overviews, + mock_serializer, + mock_course_resume_urls + ): """ View should return a 400 because of the missing enterprise_id parameter. """ mock_get_overviews.return_value = {'overview_info': 'this would be a larger dict'} mock_serializer.return_value = self.MockSerializer() + mock_course_resume_urls.return_value = { + 'course-v1:edX+DemoX+Demo_Course': 'http://example.com/resume_url' + } resp = self.client.get( '{host}{path}'.format( @@ -141,14 +159,23 @@ def test_view_returns_bad_request_without_enterprise(self, mock_get_overviews, m assert resp.status_code == 400 assert resp.json() == {'error': 'enterprise_id must be provided as a query parameter'} + @mock.patch('enterprise_learner_portal.api.v1.views.get_resume_urls_for_course_enrollments') @mock.patch('enterprise_learner_portal.api.v1.views.EnterpriseCourseEnrollmentSerializer') @mock.patch('enterprise_learner_portal.api.v1.views.get_course_overviews') - def test_view_returns_not_found_unlinked_enterprise(self, mock_get_overviews, mock_serializer): + def test_view_returns_not_found_unlinked_enterprise( + self, + mock_get_overviews, + mock_serializer, + mock_course_resume_urls + ): """ View should return a 404 because the user is not linked to the enterprise. """ mock_get_overviews.return_value = {'overview_info': 'this would be a larger dict'} mock_serializer.return_value = self.MockSerializer() + mock_course_resume_urls.return_value = { + 'course-v1:edX+DemoX+Demo_Course': 'http://example.com/resume_url' + } resp = self.client.get( '{host}{path}?enterprise_id={enterprise_id}'.format( @@ -160,14 +187,16 @@ def test_view_returns_not_found_unlinked_enterprise(self, mock_get_overviews, mo assert resp.status_code == 404 assert resp.json() == {'detail': 'Not found.'} + @mock.patch('enterprise_learner_portal.api.v1.views.get_resume_urls_for_course_enrollments') @mock.patch('enterprise_learner_portal.api.v1.serializers.get_certificate_for_user', mock.MagicMock()) @mock.patch('enterprise_learner_portal.api.v1.views.get_course_overviews') - def test_view_filters_out_invalid_enterprise_enrollments(self, mock_get_overviews): + def test_view_filters_out_invalid_enterprise_enrollments(self, mock_get_overviews, mock_course_resume_urls): """ View does not fail, and view filters out all enrollments whose course_enrollment field is None """ mock_get_overviews.return_value = {} + mock_course_resume_urls.return_value = {} resp = self.client.get( '{host}{path}?enterprise_id={enterprise_id}&is_active=true'.format(