diff --git a/lms/djangoapps/program_enrollments/api/__init__.py b/lms/djangoapps/program_enrollments/api/__init__.py index 5e170092b9..9c048a1484 100644 --- a/lms/djangoapps/program_enrollments/api/__init__.py +++ b/lms/djangoapps/program_enrollments/api/__init__.py @@ -28,7 +28,8 @@ from .reading import ( get_saml_provider_for_organization, get_org_key_for_program, get_users_by_external_keys, - get_users_by_external_keys_and_org_key + get_users_by_external_keys_and_org_key, + is_course_staff_enrollment ) from .writing import ( change_program_course_enrollment_status, diff --git a/lms/djangoapps/program_enrollments/api/reading.py b/lms/djangoapps/program_enrollments/api/reading.py index 5abbc7c65d..c443c03730 100644 --- a/lms/djangoapps/program_enrollments/api/reading.py +++ b/lms/djangoapps/program_enrollments/api/reading.py @@ -10,8 +10,10 @@ from organizations.models import Organization from social_django.models import UserSocialAuth from openedx.core.djangoapps.catalog.utils import get_programs +from student.roles import CourseStaffRole from third_party_auth.models import SAMLProviderConfig +from ..constants import ProgramCourseEnrollmentRoles from ..exceptions import ( BadOrganizationShortNameException, ProgramDoesNotExistException, @@ -509,3 +511,21 @@ def get_provider_slug(provider_config): Returns: str """ return provider_config.provider_id.strip('saml-') + + +def is_course_staff_enrollment(program_course_enrollment): + """ + Returns whether the provided program_course_enrollment have the + course staff role on the course. + + Arguments: + program_course_enrollment: ProgramCourseEnrollment + + returns: bool + """ + associated_user = program_course_enrollment.program_enrollment.user + if associated_user: + return CourseStaffRole(program_course_enrollment.course_key).has_user(associated_user) + return program_course_enrollment.courseaccessroleassignment_set.filter( + role=ProgramCourseEnrollmentRoles.COURSE_STAFF + ).exists() diff --git a/lms/djangoapps/program_enrollments/api/tests/test_reading.py b/lms/djangoapps/program_enrollments/api/tests/test_reading.py index ba5bd55093..794852f636 100644 --- a/lms/djangoapps/program_enrollments/api/tests/test_reading.py +++ b/lms/djangoapps/program_enrollments/api/tests/test_reading.py @@ -22,13 +22,18 @@ from lms.djangoapps.program_enrollments.exceptions import ( ProviderConfigurationException, ProviderDoesNotExistException ) -from lms.djangoapps.program_enrollments.models import ProgramEnrollment -from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory +from lms.djangoapps.program_enrollments.models import ProgramEnrollment, ProgramCourseEnrollment +from lms.djangoapps.program_enrollments.tests.factories import ( + CourseAccessRoleAssignmentFactory, + ProgramCourseEnrollmentFactory, + ProgramEnrollmentFactory +) from openedx.core.djangoapps.catalog.cache import PROGRAM_CACHE_KEY_TPL from openedx.core.djangoapps.catalog.tests.factories import OrganizationFactory as CatalogOrganizationFactory from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangolib.testing.utils import CacheIsolationTestCase +from student.roles import CourseStaffRole from student.tests.factories import CourseEnrollmentFactory, UserFactory from third_party_auth.tests.factories import SAMLProviderConfigFactory @@ -40,7 +45,8 @@ from ..reading import ( get_external_key_by_user_and_course, get_program_course_enrollment, get_program_enrollment, - get_users_by_external_keys + get_users_by_external_keys, + is_course_staff_enrollment, ) User = get_user_model() @@ -612,3 +618,84 @@ class GetUsersByExternalKeysTests(CacheIsolationTestCase): ) with self.assertRaises(ProviderConfigurationException): get_users_by_external_keys(self.program_uuid, []) + + +@ddt.ddt +class IsCourseStaffEnrollmentTest(TestCase): + """ + Tests for the is_course_staff_enrollment function + """ + program_uuid_x = UUID('dddddddd-5f48-493d-9410-84e1d36c657f') + program_uuid_y = UUID('eeeeeeee-f803-43f6-bbf3-5ae15d393649') + curriculum_uuid_a = UUID('aaaaaaaa-bd26-43d0-94b8-b0063858210b') + curriculum_uuid_b = UUID('bbbbbbbb-145f-43db-ad05-f9ad65eec285') + course_key_p = CourseKey.from_string('course-v1:TestX+ProEnroll+P') + course_key_q = CourseKey.from_string('course-v1:TestX+ProEnroll+Q') + username_0 = 'user-0' + ext_3 = 'student-3' + ext_4 = 'student-4' + + @classmethod + def setUpTestData(cls): + super(IsCourseStaffEnrollmentTest, cls).setUpTestData() + cls.user_0 = UserFactory(username=cls.username_0) # No enrollments + CourseOverviewFactory(id=cls.course_key_p) + CourseOverviewFactory(id=cls.course_key_q) + enrollment_test_data = [ # ID + (cls.user_0, None, cls.program_uuid_x, cls.curriculum_uuid_a, PEStatuses.ENROLLED), # 1 + (None, cls.ext_3, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.PENDING), # 2 + (None, cls.ext_4, cls.program_uuid_y, cls.curriculum_uuid_a, PEStatuses.ENROLLED), # 3 + (cls.user_0, None, cls.program_uuid_y, cls.curriculum_uuid_b, PEStatuses.SUSPENDED), # 4 + ] + for user, external_user_key, program_uuid, curriculum_uuid, status in enrollment_test_data: + ProgramEnrollmentFactory( + user=user, + external_user_key=external_user_key, + program_uuid=program_uuid, + curriculum_uuid=curriculum_uuid, + status=status, + ) + course_enrollment_test_data = [ # ID + (1, cls.course_key_p, PCEStatuses.ACTIVE, True), # 1 + (2, cls.course_key_q, PCEStatuses.ACTIVE, False), # 2 + (3, cls.course_key_p, PCEStatuses.ACTIVE, True), # 3 + (4, cls.course_key_q, PCEStatuses.ACTIVE, False), # 4 + ] + for program_enrollment_id, course_key, status, course_staff in course_enrollment_test_data: + program_enrollment = ProgramEnrollment.objects.get(id=program_enrollment_id) + course_enrollment = ( + CourseEnrollmentFactory( + course_id=course_key, + user=program_enrollment.user, + mode=CourseMode.MASTERS, + ) + if program_enrollment.user + else None + ) + + program_course_enrollment = ProgramCourseEnrollmentFactory( + program_enrollment=program_enrollment, + course_enrollment=course_enrollment, + course_key=course_key, + status=status, + ) + if course_staff: + if program_enrollment.user: + CourseStaffRole(course_key).add_users(program_enrollment.user) + else: + CourseAccessRoleAssignmentFactory( + enrollment=program_course_enrollment + ) + + @ddt.data( + (1, True), + (2, False), + (3, True), + (4, False), + ) + @ddt.unpack + def test_is_course_staff_enrollment(self, program_course_enrollment_id, is_course_staff): + program_course_enrollment = ProgramCourseEnrollment.objects.get( + id=program_course_enrollment_id + ) + assert is_course_staff == is_course_staff_enrollment(program_course_enrollment) diff --git a/lms/djangoapps/program_enrollments/rest_api/v1/serializers.py b/lms/djangoapps/program_enrollments/rest_api/v1/serializers.py index 7f29306283..202dffb5f9 100644 --- a/lms/djangoapps/program_enrollments/rest_api/v1/serializers.py +++ b/lms/djangoapps/program_enrollments/rest_api/v1/serializers.py @@ -6,6 +6,7 @@ API Serializers from rest_framework import serializers from six import text_type +from lms.djangoapps.program_enrollments.api import is_course_staff_enrollment from lms.djangoapps.program_enrollments.models import ProgramCourseEnrollment, ProgramEnrollment from .constants import CourseRunProgressStatuses @@ -78,6 +79,7 @@ class ProgramCourseEnrollmentSerializer(serializers.Serializer): status = serializers.CharField() account_exists = serializers.SerializerMethodField() curriculum_uuid = serializers.SerializerMethodField() + course_staff = serializers.SerializerMethodField() class Meta(object): model = ProgramCourseEnrollment @@ -91,6 +93,9 @@ class ProgramCourseEnrollmentSerializer(serializers.Serializer): def get_curriculum_uuid(self, obj): return text_type(obj.program_enrollment.curriculum_uuid) + def get_course_staff(self, obj): + return is_course_staff_enrollment(obj) + class ProgramCourseEnrollmentRequestSerializer(serializers.Serializer, InvalidStatusMixin): """ diff --git a/lms/djangoapps/program_enrollments/rest_api/v1/tests/test_views.py b/lms/djangoapps/program_enrollments/rest_api/v1/tests/test_views.py index b4bc1daa28..650d953b08 100644 --- a/lms/djangoapps/program_enrollments/rest_api/v1/tests/test_views.py +++ b/lms/djangoapps/program_enrollments/rest_api/v1/tests/test_views.py @@ -35,7 +35,11 @@ from lms.djangoapps.program_enrollments.constants import ProgramCourseOperationS from lms.djangoapps.program_enrollments.constants import ProgramOperationStatuses as ProgramStatuses from lms.djangoapps.program_enrollments.exceptions import ProviderDoesNotExistException from lms.djangoapps.program_enrollments.models import ProgramCourseEnrollment, ProgramEnrollment -from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory +from lms.djangoapps.program_enrollments.tests.factories import ( + CourseAccessRoleAssignmentFactory, + ProgramCourseEnrollmentFactory, + ProgramEnrollmentFactory, +) from openedx.core.djangoapps.catalog.cache import PROGRAM_CACHE_KEY_TPL, PROGRAMS_BY_ORGANIZATION_CACHE_KEY_TPL from openedx.core.djangoapps.catalog.tests.factories import ( CourseFactory, @@ -890,18 +894,27 @@ class ProgramCourseEnrollmentsGetTests(EnrollmentsDataMixin, APITestCase): program_uuid=self.program_uuid, curriculum_uuid=self.curriculum_uuid, external_user_key='user-0', ) program_enrollment_2 = ProgramEnrollmentFactory.create( - program_uuid=self.program_uuid, curriculum_uuid=self.other_curriculum_uuid, external_user_key='user-0', + program_uuid=self.program_uuid, + curriculum_uuid=self.other_curriculum_uuid, + external_user_key='user-0', + user=None ) + ProgramCourseEnrollmentFactory.create( program_enrollment=program_enrollment_1, course_key=self.course_id, status='active', ) - ProgramCourseEnrollmentFactory.create( + CourseStaffRole(self.course_id).add_users(program_enrollment_1.user) + + program_course_enrollment_2 = ProgramCourseEnrollmentFactory.create( program_enrollment=program_enrollment_2, course_key=self.course_id, status='inactive', ) + CourseAccessRoleAssignmentFactory.create( + enrollment=program_course_enrollment_2 + ) self.addCleanup(self.destroy_course_enrollments) @@ -959,11 +972,11 @@ class ProgramCourseEnrollmentsGetTests(EnrollmentsDataMixin, APITestCase): 'results': [ { 'student_key': 'user-0', 'status': 'active', 'account_exists': True, - 'curriculum_uuid': text_type(self.curriculum_uuid), + 'curriculum_uuid': text_type(self.curriculum_uuid), 'course_staff': True }, { - 'student_key': 'user-0', 'status': 'inactive', 'account_exists': True, - 'curriculum_uuid': text_type(self.other_curriculum_uuid), + 'student_key': 'user-0', 'status': 'inactive', 'account_exists': False, + 'curriculum_uuid': text_type(self.other_curriculum_uuid), 'course_staff': True }, ], } @@ -980,7 +993,7 @@ class ProgramCourseEnrollmentsGetTests(EnrollmentsDataMixin, APITestCase): expected_results = [ { 'student_key': 'user-0', 'status': 'active', 'account_exists': True, - 'curriculum_uuid': text_type(self.curriculum_uuid), + 'curriculum_uuid': text_type(self.curriculum_uuid), 'course_staff': True }, ] assert expected_results == response.data['results'] @@ -994,12 +1007,12 @@ class ProgramCourseEnrollmentsGetTests(EnrollmentsDataMixin, APITestCase): assert status.HTTP_200_OK == next_response.status_code next_expected_results = [ { - 'student_key': 'user-0', 'status': 'inactive', 'account_exists': True, - 'curriculum_uuid': text_type(self.other_curriculum_uuid), + 'student_key': 'user-0', 'status': 'inactive', 'account_exists': False, + 'curriculum_uuid': text_type(self.other_curriculum_uuid), 'course_staff': True }, ] assert next_expected_results == next_response.data['results'] - assert next_response.data['next'] is None + # there's going to be a 'cursor' query param, but we have no way of knowing it's value assert next_response.data['previous'] is not None assert self.get_url(course_id=self.course_id) in next_response.data['previous']