From e3b0bffa503f7942b9e3aa31b3eee0be293b0dec Mon Sep 17 00:00:00 2001
From: Sarina Canelake <sarina@edx.org>
Date: Tue, 23 Feb 2021 09:03:31 -0500
Subject: [PATCH] Revert "Remove the "role of users being enrolled" field from
 Instructor Dashboard"

---
 cms/envs/common.py                            |  5 ++
 common/djangoapps/student/api.py              | 12 ++++-
 common/djangoapps/student/models_api.py       |  4 +-
 lms/djangoapps/instructor/tests/test_api.py   |  7 +--
 .../tests/views/test_instructor_dashboard.py  | 47 +++++++++++++++++++
 lms/djangoapps/instructor/views/api.py        | 13 ++++-
 .../instructor/views/instructor_dashboard.py  |  3 ++
 lms/envs/common.py                            |  4 ++
 .../instructor_dashboard_2/membership.html    | 11 +++++
 9 files changed, 100 insertions(+), 6 deletions(-)

diff --git a/cms/envs/common.py b/cms/envs/common.py
index 7a4194a92ce..04c8ee5e520 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -2023,6 +2023,11 @@ INTEGRATED_CHANNELS_API_CHUNK_TRANSMISSION_LIMIT = {}
 
 BASE_COOKIE_DOMAIN = 'localhost'
 
+# This limits the type of roles that are submittable via the `student` app's manual enrollment
+# audit API. While this isn't used in CMS, it is used via Enterprise which is installed in
+# the CMS. Without this, we get errors.
+MANUAL_ENROLLMENT_ROLE_CHOICES = ['Learner', 'Support', 'Partner']
+
 ############## Settings for the Discovery App ######################
 
 COURSE_CATALOG_URL_ROOT = 'http://localhost:8008'
diff --git a/common/djangoapps/student/api.py b/common/djangoapps/student/api.py
index 4ae91a43d12..6ccdcef7489 100644
--- a/common/djangoapps/student/api.py
+++ b/common/djangoapps/student/api.py
@@ -45,6 +45,11 @@ TRANSITION_STATES = (
     DEFAULT_TRANSITION_STATE,
 )
 
+MANUAL_ENROLLMENT_ROLE_CHOICES = configuration_helpers.get_value(
+    'MANUAL_ENROLLMENT_ROLE_CHOICES',
+    settings.MANUAL_ENROLLMENT_ROLE_CHOICES
+)
+
 COURSE_DASHBOARD_PLUGIN_VIEW_NAME = "course_dashboard"
 
 
@@ -54,6 +59,7 @@ def create_manual_enrollment_audit(
     transition_state,
     reason,
     course_run_key=None,
+    role=None
 ):
     """
     Creates an audit item for a manual enrollment.
@@ -63,10 +69,13 @@ def create_manual_enrollment_audit(
         transition_state: <str> state of enrollment transition state from _TRANSITIONS_STATES
         reason: <str> Reason why user was manually enrolled
         course_run_key: <str> Used to link the audit enrollment to the actual enrollment
+        role: <str> role of the enrolled user from MANUAL_ENROLLMENT_ROLE_CHOICES
 
     Note: We purposefully *exclude* passing items like CourseEnrollment objects to prevent callers from needed to
     know about model level code.
     """
+    if role and role not in MANUAL_ENROLLMENT_ROLE_CHOICES:
+        raise ValueError("Role `{}` not in allowed roles: `{}".format(role, MANUAL_ENROLLMENT_ROLE_CHOICES))
     if transition_state not in TRANSITION_STATES:
         raise ValueError("State `{}` not in allow states: `{}`".format(transition_state, TRANSITION_STATES))
 
@@ -86,7 +95,8 @@ def create_manual_enrollment_audit(
         user_email,
         transition_state,
         reason,
-        enrollment
+        enrollment,
+        role
     )
 
 
diff --git a/common/djangoapps/student/models_api.py b/common/djangoapps/student/models_api.py
index 20e0363f465..e5a7e51ce55 100644
--- a/common/djangoapps/student/models_api.py
+++ b/common/djangoapps/student/models_api.py
@@ -35,7 +35,8 @@ def create_manual_enrollment_audit(  # lint-amnesty, pylint: disable=missing-fun
     user_email,
     state_transition,
     reason,
-    course_enrollment
+    course_enrollment,
+    role
 ):
     _ManualEnrollmentAudit.create_manual_enrollment_audit(
         user=enrolled_by,
@@ -43,6 +44,7 @@ def create_manual_enrollment_audit(  # lint-amnesty, pylint: disable=missing-fun
         state_transition=state_transition,
         reason=reason,
         enrollment=course_enrollment,
+        role=role,
     )
 
 
diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 7123b9758ef..5a74c5f81c4 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -1776,18 +1776,19 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest
         )
         assert course_enrollment.mode == CourseMode.DEFAULT_MODE_SLUG
 
-    def test_reason_is_persisted(self):
+    def test_role_and_reason_are_persisted(self):
         """
-        test that reason field is persisted in the database
+        test that role and reason fields are persisted in the database
         """
         paid_course = self.create_paid_course()
         url = reverse('students_update_enrollment', kwargs={'course_id': str(paid_course.id)})
         params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': False,
-                  'auto_enroll': False, 'reason': 'testing'}
+                  'auto_enroll': False, 'reason': 'testing', 'role': 'Learner'}
         response = self.client.post(url, params)
 
         manual_enrollment = ManualEnrollmentAudit.objects.first()
         assert manual_enrollment.reason == 'testing'
+        assert manual_enrollment.role == 'Learner'
         assert response.status_code == 200
 
     def _change_student_enrollment(self, user, course, action):
diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
index fa1477d7223..783f5da8f7c 100644
--- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
+++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
@@ -245,6 +245,53 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
         else:
             self.assertNotContains(response, reason_field)
 
+    def test_membership_site_configuration_role(self):
+        """
+        Verify that the role choices set via site configuration are loaded in the membership tab
+        of the instructor dashboard
+        """
+
+        configuration_values = {
+            "MANUAL_ENROLLMENT_ROLE_CHOICES": [
+                "role1",
+                "role2",
+            ]
+        }
+        site = Site.objects.first()
+        SiteConfiguration.objects.create(
+            site=site,
+            site_values=configuration_values,
+            enabled=True
+        )
+        url = reverse(
+            'instructor_dashboard',
+            kwargs={
+                'course_id': str(self.course_info.id)
+            }
+        )
+
+        response = self.client.get(url)
+        self.assertContains(response, '<option value="role1">role1</option>')
+        self.assertContains(response, '<option value="role2">role2</option>')
+
+    def test_membership_default_role(self):
+        """
+        Verify that in the absence of site configuration role choices, default values of role choices are loaded
+        in the membership tab of the instructor dashboard
+        """
+
+        url = reverse(
+            'instructor_dashboard',
+            kwargs={
+                'course_id': str(self.course_info.id)
+            }
+        )
+
+        response = self.client.get(url)
+        self.assertContains(response, '<option value="Learner">Learner</option>')
+        self.assertContains(response, '<option value="Support">Support</option>')
+        self.assertContains(response, '<option value="Partner">Partner</option>')
+
     def test_student_admin_staff_instructor(self):
         """
         Verify that staff users are not able to see course-wide options, while still
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 0213c06cfc7..88cb6c93105 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -649,6 +649,17 @@ def students_update_enrollment(request, course_id):  # lint-amnesty, pylint: dis
     auto_enroll = _get_boolean_param(request, 'auto_enroll')
     email_students = _get_boolean_param(request, 'email_students')
     reason = request.POST.get('reason')
+    role = request.POST.get('role')
+
+    allowed_role_choices = configuration_helpers.get_value('MANUAL_ENROLLMENT_ROLE_CHOICES',
+                                                           settings.MANUAL_ENROLLMENT_ROLE_CHOICES)
+    if role and role not in allowed_role_choices:
+        return JsonResponse(
+            {
+                'action': action,
+                'results': [{'error': True, 'message': 'Not a valid role choice'}],
+                'auto_enroll': auto_enroll,
+            }, status=400)
 
     enrollment_obj = None
     state_transition = DEFAULT_TRANSITION_STATE
@@ -741,7 +752,7 @@ def students_update_enrollment(request, course_id):  # lint-amnesty, pylint: dis
 
         else:
             ManualEnrollmentAudit.create_manual_enrollment_audit(
-                request.user, email, state_transition, reason, enrollment_obj
+                request.user, email, state_transition, reason, enrollment_obj, role
             )
             results.append({
                 'identifier': identifier,
diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py
index 9ee10ab442a..29d83fdb9ec 100644
--- a/lms/djangoapps/instructor/views/instructor_dashboard.py
+++ b/lms/djangoapps/instructor/views/instructor_dashboard.py
@@ -456,6 +456,8 @@ def _section_membership(course, access):
     """ Provide data for the corresponding dashboard section """
     course_key = course.id
     ccx_enabled = settings.FEATURES.get('CUSTOM_COURSES_EDX', False) and course.enable_ccx
+    enrollment_role_choices = configuration_helpers.get_value('MANUAL_ENROLLMENT_ROLE_CHOICES',
+                                                              settings.MANUAL_ENROLLMENT_ROLE_CHOICES)
 
     section_data = {
         'section_key': 'membership',
@@ -482,6 +484,7 @@ def _section_membership(course, access):
             'update_forum_role_membership',
             kwargs={'course_id': str(course_key)}
         ),
+        'enrollment_role_choices': enrollment_role_choices,
         'is_reason_field_enabled': configuration_helpers.get_value('ENABLE_MANUAL_ENROLLMENT_REASON_FIELD', False)
     }
     return section_data
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 0e126d2c6f9..51f3a823803 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -86,6 +86,10 @@ LMS_ROOT_URL = 'https://localhost:18000'
 LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL
 LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"
 
+# Default choices for role dropdown in the membership tab of the instructor dashboard
+# This setting is used when a site does not define its own choices via site configuration
+MANUAL_ENROLLMENT_ROLE_CHOICES = ['Learner', 'Support', 'Partner']
+
 # List of logout URIs for each IDA that the learner should be logged out of when they logout of the LMS. Only applies to
 # IDA for which the social auth flow uses DOT (Django OAuth Toolkit).
 IDA_LOGOUT_URI_LIST = []
diff --git a/lms/templates/instructor/instructor_dashboard_2/membership.html b/lms/templates/instructor/instructor_dashboard_2/membership.html
index baa11767a9e..7945827b673 100644
--- a/lms/templates/instructor/instructor_dashboard_2/membership.html
+++ b/lms/templates/instructor/instructor_dashboard_2/membership.html
@@ -13,6 +13,17 @@ from openedx.core.djangolib.markup import HTML, Text
         <textarea rows="6" name="student-ids" placeholder="${_("Email Addresses/Usernames")}" spellcheck="false"></textarea>
     </label>
 
+    <div class="role">
+        <label>
+            ${_("Role of the users being enrolled.")}
+            <select name="role">
+                % for role in section_data['enrollment_role_choices']:
+                  <option value="${role}">${role}</option>
+                % endfor
+            </select>
+        </label>
+    </div>
+
     % if section_data['is_reason_field_enabled']:
         <label>
             ${_("Enter the reason why the students are to be manually enrolled or unenrolled.")}
-- 
GitLab