-
-
- )}
+ dismissible={false}
+ alertType="danger"
+ dialog={errorItem}
/>
- )}
+ ))}
(
);
ProgramEnrollmentsInspectorPage.propTypes = {
- successes: PropTypes.arrayOf(PropTypes.string),
errors: PropTypes.arrayOf(PropTypes.string),
- learnerInfo: PropTypes.string,
+ learnerInfo: PropTypes.shape({
+ user: PropTypes.shape({
+ external_user_key: PropTypes.string,
+ username: PropTypes.string,
+ }),
+ enrollments: PropTypes.arrayOf(
+ PropTypes.string,
+ ),
+ }),
orgKeys: PropTypes.arrayOf(PropTypes.object),
};
ProgramEnrollmentsInspectorPage.defaultProps = {
- successes: [],
errors: [],
- learnerInfo: '',
+ learnerInfo: {},
orgKeys: [],
};
diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py
index c6a529f72b..81dc7aaa81 100644
--- a/lms/djangoapps/support/tests/test_views.py
+++ b/lms/djangoapps/support/tests/test_views.py
@@ -25,7 +25,10 @@ from common.test.utils import disable_signal
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory
+from lms.djangoapps.support.serializers import ProgramEnrollmentSerializer
from lms.djangoapps.verify_student.models import VerificationDeadline
+from lms.djangoapps.verify_student.services import IDVerificationService
+from lms.djangoapps.verify_student.tests.factories import SSOVerificationFactory
from student.models import ENROLLED_TO_ENROLLED, CourseEnrollment, CourseEnrollmentAttribute, ManualEnrollmentAudit
from student.roles import GlobalStaff, SupportStaffRole
from student.tests.factories import CourseEnrollmentFactory, UserFactory
@@ -545,6 +548,7 @@ class SupportViewLinkProgramEnrollmentsTests(SupportViewTestCase):
assert render_call_dict['errors'] == [msg]
+@ddt.ddt
class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
"""
View tests for Program Enrollments Inspector
@@ -603,10 +607,10 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
user_social_auth = UserSocialAuth.objects.create(
user=user,
uid='{0}:{1}'.format(org_key, external_user_key),
- provider=org_key
+ provider='tpa-saml'
)
user_info['external_user_key'] = external_user_key
- user_info['SSO'] = {
+ user_info['sso'] = {
'uid': user_social_auth.uid,
'provider': user_social_auth.provider
}
@@ -618,27 +622,14 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
If the edx user is provided, it will try to SSO the user with the enrollments
Return the expected info object that should be created based on the model setup
"""
- expected_enrollments = []
+ program_enrollments = []
for program_uuid in program_uuids:
- expected_enrollment = {}
- expected_course_enrollment = {}
course_enrollment = None
program_enrollment = ProgramEnrollmentFactory.create(
external_user_key=external_user_key,
program_uuid=program_uuid,
user=edx_user
)
- expected_enrollment['program_enrollment'] = {
- 'created': self._serialize_datetime(
- program_enrollment.created
- ),
- 'modified': self._serialize_datetime(
- program_enrollment.modified
- ),
- 'program_uuid': program_enrollment.program_uuid,
- 'external_user_key': external_user_key,
- 'status': program_enrollment.status
- }
for course_id in course_ids:
if edx_user:
@@ -648,11 +639,6 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
mode=CourseMode.MASTERS,
is_active=True
)
- expected_course_enrollment = {
- 'course_id': str(course_enrollment.course_id),
- 'is_active': course_enrollment.is_active,
- 'mode': course_enrollment.mode,
- }
program_course_enrollment = ProgramCourseEnrollmentFactory.create(
program_enrollment=program_enrollment,
@@ -660,26 +646,22 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
course_enrollment=course_enrollment,
status='active',
)
- expected_program_course_enrollment = {
- 'created': self._serialize_datetime(
- program_course_enrollment.created
- ),
- 'modified': self._serialize_datetime(
- program_course_enrollment.modified
- ),
- 'status': program_course_enrollment.status,
- 'course_key': str(program_course_enrollment.course_key),
- }
- if expected_course_enrollment:
- expected_program_course_enrollment['course_enrollment'] = expected_course_enrollment
- expected_enrollment.setdefault('program_course_enrollments', []).append(
- expected_program_course_enrollment
- )
+ program_enrollments.append(program_enrollment)
- expected_enrollments.append(expected_enrollment)
+ serialized = ProgramEnrollmentSerializer(program_enrollments, many=True)
+ return serialized.data
- return expected_enrollments
+ def _construct_id_verification(self, user):
+ """
+ Helper function to create the SSO verified record for the user
+ so that the user is ID Verified
+ """
+ SSOVerificationFactory(
+ identity_provider_slug=self.org_key_list[0],
+ user=user,
+ )
+ return IDVerificationService.user_status(user)
@patch_render
def test_search_username_well_connected_user(self, mocked_render):
@@ -688,6 +670,7 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
self.org_key_list[0],
self.external_user_key
)
+ id_verified = self._construct_id_verification(created_user)
expected_enrollments = self._construct_enrollments(
[self.program_uuid],
[self.course.id],
@@ -699,7 +682,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
})
expected_info = {
'user': expected_user_info,
- 'enrollments': expected_enrollments
+ 'enrollments': expected_enrollments,
+ 'id_verification': id_verified
}
render_call_dict = mocked_render.call_args[0][1]
@@ -712,7 +696,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
'edx_user': created_user.email
})
expected_info = {
- 'user': expected_user_info
+ 'user': expected_user_info,
+ 'id_verification': IDVerificationService.user_status(created_user)
}
render_call_dict = mocked_render.call_args[0][1]
@@ -729,7 +714,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
'edx_user': created_user.email
})
expected_info = {
- 'user': expected_user_info
+ 'user': expected_user_info,
+ 'id_verification': IDVerificationService.user_status(created_user),
}
render_call_dict = mocked_render.call_args[0][1]
@@ -753,7 +739,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
})
expected_info = {
'user': expected_user_info,
- 'enrollments': expected_enrollments
+ 'enrollments': expected_enrollments,
+ 'id_verification': IDVerificationService.user_status(created_user),
}
render_call_dict = mocked_render.call_args[0][1]
@@ -774,7 +761,110 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
})
expected_info = {
'user': expected_user_info,
+ 'id_verification': IDVerificationService.user_status(created_user),
}
render_call_dict = mocked_render.call_args[0][1]
assert expected_info == render_call_dict['learner_program_enrollments']
+
+ @patch_render
+ def test_search_username_user_id_verified(self, mocked_render):
+ created_user, expected_user_info = self._construct_user(
+ 'user_not_connected'
+ )
+ id_verified = self._construct_id_verification(created_user)
+ expected_info = {
+ 'user': expected_user_info,
+ 'id_verification': id_verified
+ }
+
+ self.client.get(self.url, data={
+ 'edx_user': created_user.email
+ })
+
+ render_call_dict = mocked_render.call_args[0][1]
+ assert expected_info == render_call_dict['learner_program_enrollments']
+
+ @patch_render
+ def test_search_external_key_well_connected(self, mocked_render):
+ created_user, expected_user_info = self._construct_user(
+ 'test_user_connected',
+ self.org_key_list[0],
+ self.external_user_key
+ )
+ expected_enrollments = self._construct_enrollments(
+ [self.program_uuid],
+ [self.course.id],
+ self.external_user_key,
+ created_user
+ )
+ id_verified = self._construct_id_verification(created_user)
+ self.client.get(self.url, data={
+ 'external_user_key': self.external_user_key,
+ 'IdPSelect': self.org_key_list[0]
+ })
+ expected_info = {
+ 'user': expected_user_info,
+ 'enrollments': expected_enrollments,
+ 'id_verification': id_verified,
+ }
+
+ render_call_dict = mocked_render.call_args[0][1]
+ assert expected_info == render_call_dict['learner_program_enrollments']
+
+ @ddt.data(
+ ('aabbcc', ''),
+ ('', 'test_org')
+ )
+ @ddt.unpack
+ @patch_render
+ def test_search_external_key_no_idp(self, user_key_input, idp_input, mocked_render):
+ self.client.get(self.url, data={
+ 'external_user_key': user_key_input,
+ 'IdPSelect': idp_input,
+ })
+
+ expected_errors = [
+ "To perform a search, you must provide either the student's "
+ "(a) edX username, "
+ "(b) email address associated with their edX account, or "
+ "(c) Identity-providing institution and external key!"
+ ]
+
+ render_call_dict = mocked_render.call_args[0][1]
+ assert {} == render_call_dict['learner_program_enrollments']
+ assert expected_errors == render_call_dict['errors']
+
+ @patch_render
+ def test_search_external_user_not_connected(self, mocked_render):
+ expected_enrollments = self._construct_enrollments(
+ [self.program_uuid],
+ [self.course.id],
+ self.external_user_key,
+ )
+ self.client.get(self.url, data={
+ 'external_user_key': self.external_user_key,
+ 'IdPSelect': self.org_key_list[0]
+ })
+ expected_info = {
+ 'enrollments': expected_enrollments
+ }
+
+ render_call_dict = mocked_render.call_args[0][1]
+ assert expected_info == render_call_dict['learner_program_enrollments']
+
+ @patch_render
+ def test_search_external_user_not_in_system(self, mocked_render):
+ external_user_key = 'not_in_system'
+ self.client.get(self.url, data={
+ 'external_user_key': external_user_key,
+ 'IdPSelect': self.org_key_list[0],
+ })
+
+ expected_errors = [
+ 'No user found for external key {} for institution {}'.format(
+ external_user_key, self.org_key_list[0]
+ )
+ ]
+ render_call_dict = mocked_render.call_args[0][1]
+ assert expected_errors == render_call_dict['errors']
diff --git a/lms/djangoapps/support/views/program_enrollments.py b/lms/djangoapps/support/views/program_enrollments.py
index 5aa9a649f4..acf26fd8d3 100644
--- a/lms/djangoapps/support/views/program_enrollments.py
+++ b/lms/djangoapps/support/views/program_enrollments.py
@@ -15,9 +15,20 @@ from social_django.models import UserSocialAuth
from edxmako.shortcuts import render_to_response
from lms.djangoapps.program_enrollments.api import (
fetch_program_enrollments_by_student,
+ get_users_by_external_keys_and_org_key,
link_program_enrollments
)
+from lms.djangoapps.program_enrollments.exceptions import (
+ BadOrganizationShortNameException,
+ ProviderConfigurationException,
+ ProviderDoesNotExistException
+)
from lms.djangoapps.support.decorators import require_support_permission
+from lms.djangoapps.support.serializers import (
+ ProgramEnrollmentSerializer,
+ serialize_user_info
+)
+from lms.djangoapps.verify_student.services import IDVerificationService
from third_party_auth.models import SAMLProviderConfig
TEMPLATE_PATH = 'support/link_program_enrollments.html'
@@ -133,17 +144,27 @@ class ProgramEnrollmentsInspectorView(View):
if error:
errors.append(error)
elif org_key and external_user_key:
- learner_program_enrollments = {}
- elif not external_user_key and org_key:
+ learner_program_enrollments = self._get_external_user_info(
+ external_user_key,
+ org_key
+ )
+ if not learner_program_enrollments:
+ errors.append(
+ 'No user found for external key {} for institution {}'.format(
+ external_user_key, org_key
+ )
+ )
+ else:
errors.append(
- 'You must provide either the edX username or email, or the '
- 'Learner Account Provider and External Key pair to do search!'
+ "To perform a search, you must provide either the student's "
+ "(a) edX username, "
+ "(b) email address associated with their edX account, or "
+ "(c) Identity-providing institution and external key!"
)
return render_to_response(
self.CONSOLE_TEMPLATE_PATH,
{
- 'successes': [],
'errors': errors,
'learner_program_enrollments': learner_program_enrollments,
'org_keys': self._get_org_keys_with_idp(),
@@ -168,32 +189,59 @@ class ProgramEnrollmentsInspectorView(View):
and program_enrollments_info. If we cannot identify the user, return
empty object and error.
"""
- user_info = {}
- external_key = None
try:
user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
- user_info['username'] = user.username
- user_info['email'] = user.email
+ user_social_auth = None
try:
user_social_auth = UserSocialAuth.objects.get(user=user)
- _, external_key = user_social_auth.uid.split(':', 1)
- user_info['external_user_key'] = external_key
- user_info['SSO'] = {
- 'uid': user_social_auth.uid,
- 'provider': user_social_auth.provider
- }
except UserSocialAuth.DoesNotExist:
pass
-
+ user_info = serialize_user_info(user, user_social_auth)
enrollments = self._get_enrollments(user=user)
result = {'user': user_info}
if enrollments:
result['enrollments'] = enrollments
+ result['id_verification'] = IDVerificationService.user_status(user)
return result, ''
except User.DoesNotExist:
return {}, 'Could not find edx account with {}'.format(username_or_email)
+ def _get_external_user_info(self, external_user_key, org_key):
+ """
+ Provided the external_user_key and org_key, return edx account info
+ and program_enrollments_info if any. If we cannot identify the data,
+ return empty object.
+ """
+ found_user = None
+ result = {}
+ try:
+ users_by_key = get_users_by_external_keys_and_org_key(
+ [external_user_key],
+ org_key
+ )
+ found_user = users_by_key.get(external_user_key)
+ except (
+ BadOrganizationShortNameException,
+ ProviderConfigurationException,
+ ProviderDoesNotExistException
+ ):
+ # We cannot identify edX user from external_user_key and org_key pair
+ pass
+
+ if found_user:
+ try:
+ user_social_auth = UserSocialAuth.objects.get(user=found_user)
+ except UserSocialAuth.DoesNotExist:
+ user_social_auth = None
+ user_info = serialize_user_info(found_user, user_social_auth)
+ result['user'] = user_info
+ result['id_verification'] = IDVerificationService.user_status(found_user)
+ enrollments = self._get_enrollments(external_user_key=external_user_key)
+ if enrollments:
+ result['enrollments'] = enrollments
+ return result
+
def _get_enrollments(self, user=None, external_user_key=None):
"""
With the user or external_user_key passed in,
@@ -204,55 +252,5 @@ class ProgramEnrollmentsInspectorView(View):
user=user,
external_user_key=external_user_key
).prefetch_related('program_course_enrollments')
-
- enrollments_by_program_uuid = {}
- for program_enrollment in program_enrollments:
- serialized_program_enrollment = self._serialize_program_enrollment(program_enrollment)
- enrollment_item = {
- 'program_enrollment': serialized_program_enrollment
- }
- program_course_enrollments = program_enrollment.program_course_enrollments.all()
- for program_course_enrollment in program_course_enrollments.select_related(
- 'course_enrollment'
- ):
- serialized_program_course_enrollment = self._serialize_program_course_enrollment(
- program_course_enrollment
- )
- enrollment_item.setdefault('program_course_enrollments', []).append(
- serialized_program_course_enrollment
- )
- enrollments_by_program_uuid[program_enrollment.program_uuid] = enrollment_item
-
- return list(enrollments_by_program_uuid.values())
-
- def _serialize_program_enrollment(self, program_enrollment):
- if not program_enrollment:
- return {}
-
- return {
- 'created': program_enrollment.created.strftime(DATETIME_FORMAT),
- 'modified': program_enrollment.modified.strftime(DATETIME_FORMAT),
- 'program_uuid': str(program_enrollment.program_uuid),
- 'external_user_key': program_enrollment.external_user_key,
- 'status': program_enrollment.status
- }
-
- def _serialize_program_course_enrollment(self, program_course_enrollment):
- """
- Return a dictionary of ProgramCourseEnrollment serialized
- """
- if not program_course_enrollment:
- return {}
-
- course_enrollment = program_course_enrollment.course_enrollment
- return {
- 'created': program_course_enrollment.created.strftime(DATETIME_FORMAT),
- 'modified': program_course_enrollment.modified.strftime(DATETIME_FORMAT),
- 'course_enrollment': {
- 'course_id': str(course_enrollment.course_id),
- 'is_active': course_enrollment.is_active,
- 'mode': course_enrollment.mode,
- },
- 'status': program_course_enrollment.status,
- 'course_key': str(program_course_enrollment.course_key),
- }
+ serialized = ProgramEnrollmentSerializer(program_enrollments, many=True)
+ return serialized.data
diff --git a/lms/templates/support/program_enrollments_inspector.html b/lms/templates/support/program_enrollments_inspector.html
index a6b5be932e..fe7c162909 100644
--- a/lms/templates/support/program_enrollments_inspector.html
+++ b/lms/templates/support/program_enrollments_inspector.html
@@ -26,7 +26,6 @@ from openedx.core.djangolib.js_utils import js_escaped_string
component="ProgramEnrollmentsInspectorPage",
id="entitlement-support-page",
props={
- 'successes': successes,
'errors': errors,
'learnerInfo': learner_program_enrollments,
'orgKeys': org_keys