diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 21aac0a87f..7080824125 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -17,6 +17,7 @@ from common.djangoapps.student.models import ( CourseAccessRole, CourseEnrollment, CourseEnrollmentAllowed, + CourseEnrollmentAttribute, CourseEnrollmentCelebration, PendingEmailChange, Registration, @@ -196,6 +197,13 @@ class CourseEnrollmentCelebrationFactory(DjangoModelFactory): enrollment = factory.SubFactory(CourseEnrollmentFactory) +class CourseEnrollmentAttributeFactory(DjangoModelFactory): + class Meta: + model = CourseEnrollmentAttribute + + enrollment = factory.SubFactory(CourseEnrollmentFactory) + + class CourseAccessRoleFactory(DjangoModelFactory): # lint-amnesty, pylint: disable=missing-class-docstring class Meta: model = CourseAccessRole diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index 3aa56a0a76..dcbfe3c82b 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -45,7 +45,11 @@ from common.djangoapps.student.models import ( ManualEnrollmentAudit ) from common.djangoapps.student.roles import GlobalStaff, SupportStaffRole -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from common.djangoapps.student.tests.factories import ( + CourseEnrollmentFactory, + CourseEnrollmentAttributeFactory, + UserFactory, +) from common.djangoapps.third_party_auth.tests.factories import SAMLProviderConfigFactory from common.test.utils import disable_signal from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory @@ -299,7 +303,9 @@ class SupportViewEnrollmentsTests(SharedModuleStoreTestCase, SupportViewTestCase ) self.verification_deadline.save() - CourseEnrollmentFactory.create(mode=CourseMode.AUDIT, user=self.student, course_id=self.course.id) + self.enrollment = CourseEnrollmentFactory.create( + mode=CourseMode.AUDIT, user=self.student, course_id=self.course.id + ) self.url = reverse('support:enrollment_list', kwargs={'username_or_email': self.student.username}) @@ -331,6 +337,27 @@ class SupportViewEnrollmentsTests(SharedModuleStoreTestCase, SupportViewTestCase assert {CourseMode.VERIFIED, CourseMode.AUDIT, CourseMode.HONOR, CourseMode.NO_ID_PROFESSIONAL_MODE, CourseMode.PROFESSIONAL, CourseMode.CREDIT_MODE} == {mode['slug'] for mode in data[0]['course_modes']} assert 'enterprise_course_enrollments' not in data[0] + assert data[0]['order_number'] == '' + + @ddt.data(*itertools.product(['username', 'email'], [(3, 'ORD-003'), (1, 'ORD-001')])) + @ddt.unpack + def test_order_number_information(self, search_string_type, order_details): + for count in range(order_details[0]): + CourseEnrollmentAttributeFactory( + enrollment=self.enrollment, + namespace='order', + name='order_number', + value='ORD-00{}'.format(count + 1) + ) + url = reverse( + 'support:enrollment_list', + kwargs={'username_or_email': getattr(self.student, search_string_type)} + ) + response = self.client.get(url) + assert response.status_code == 200 + data = json.loads(response.content.decode('utf-8')) + assert len(data) == 1 + assert data[0]['order_number'] == order_details[1] @override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) @enterprise_is_enabled() diff --git a/lms/djangoapps/support/views/enrollments.py b/lms/djangoapps/support/views/enrollments.py index fba5cb801f..f9622fcb3c 100644 --- a/lms/djangoapps/support/views/enrollments.py +++ b/lms/djangoapps/support/views/enrollments.py @@ -1,7 +1,7 @@ """ Support tool for changing course enrollments. """ - +import logging from collections import defaultdict import markupsafe @@ -31,7 +31,7 @@ from lms.djangoapps.support.decorators import require_support_permission from lms.djangoapps.support.serializers import ManualEnrollmentSerializer from lms.djangoapps.verify_student.models import VerificationDeadline from openedx.core.djangoapps.credit.email_utils import get_credit_provider_attribute_values -from openedx.core.djangoapps.enrollments.api import get_enrollments, update_enrollment +from openedx.core.djangoapps.enrollments.api import get_enrollments, get_enrollment_attributes, update_enrollment from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError from openedx.core.djangoapps.enrollments.serializers import ModeSerializer from openedx.features.enterprise_support.api import ( @@ -42,6 +42,9 @@ from openedx.features.enterprise_support.api import ( from openedx.features.enterprise_support.serializers import EnterpriseCourseEnrollmentSerializer +logger = logging.getLogger(__name__) + + class EnrollmentSupportView(View): """ View for viewing and changing learner enrollments, used by the @@ -118,6 +121,8 @@ class EnrollmentSupportListView(GenericAPIView): enrollment['course_modes'] = self.get_course_modes(course_key) # Add the price of the course's verified mode. self.include_verified_mode_info(enrollment, course_key) + # Add order number associated with the enrollment if available + self.include_order_number(enrollment) # Add manual enrollment history, if it exists enrollment['manual_enrollment'] = self.manual_enrollment_data(enrollment, course_key) @@ -258,6 +263,33 @@ class EnrollmentSupportListView(GenericAPIView): enrollment_data['verified_upgrade_deadline'] = mode['expiration_datetime'] enrollment_data['verification_deadline'] = VerificationDeadline.deadline_for_course(course_key) + @staticmethod + def include_order_number(enrollment): + """ + For a provided enrollment data dictionary, include order_number from CourseEnrollmentAttribute if available. + + From all the attributes of a course enrollment: + + * Filter order_number attributes namespaced under `order` + * Use the last/latest order number attr if multiple attrs found + * This is to keep the usage consistent with get_order_attribute_value method in CourseEnrollment + """ + username = enrollment['user'] + course_id = enrollment['course_id'] + enrollment_attributes = get_enrollment_attributes(username, course_id) + order_attribute = [ + enrollment_attribute.get('value', '') for enrollment_attribute in enrollment_attributes if + enrollment_attribute['namespace'] == 'order' and enrollment_attribute['name'] == 'order_number' + ] + if len(order_attribute) > 1: + # logging this warning for info/debug purpose + logger.warning( + "Found multiple order name attributes for CourseEnrollment for user %s with course %s", + username, + course_id + ) + enrollment['order_number'] = order_attribute[-1] if order_attribute else '' + @staticmethod def manual_enrollment_data(enrollment_data, course_key): """