From 88f2856c823ddec51e5153855491edc11784a7d2 Mon Sep 17 00:00:00 2001 From: Brian Buck Date: Wed, 3 Dec 2025 11:56:52 -0700 Subject: [PATCH] feat: Adds the bulk_email tab for staff level users in CourseInformationSerializer v2 serializer. Rename v2 InstructorTaskSerializer to InstructorTaskSerializerV2 Rename v2 CourseInformationSerializer to CourseInformationSerializerV2 --- .../instructor/tests/test_api_v2.py | 35 +++++++++++++++++++ lms/djangoapps/instructor/views/api.py | 7 ++-- lms/djangoapps/instructor/views/api_v2.py | 6 ++-- .../instructor/views/serializers_v2.py | 6 ++-- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/instructor/tests/test_api_v2.py b/lms/djangoapps/instructor/tests/test_api_v2.py index 4747b447d4..7440dc1bfd 100644 --- a/lms/djangoapps/instructor/tests/test_api_v2.py +++ b/lms/djangoapps/instructor/tests/test_api_v2.py @@ -347,6 +347,41 @@ class CourseMetadataViewTest(SharedModuleStoreTestCase): tab_ids = [tab['tab_id'] for tab in tabs] self.assertIn('certificates', tab_ids) + @patch('lms.djangoapps.instructor.views.serializers_v2.is_bulk_email_feature_enabled') + @ddt.data('staff', 'instructor', 'admin') + def test_bulk_email_tab_when_enabled(self, user_attribute, mock_bulk_email_enabled): + """ + Test that the bulk_email tab appears for all staff-level users when is_bulk_email_feature_enabled is True. + """ + mock_bulk_email_enabled.return_value = True + + user = getattr(self, user_attribute) + tabs = self._get_tabs_from_response(user) + tab_ids = [tab['tab_id'] for tab in tabs] + + self.assertIn('bulk_email', tab_ids) + + @patch('lms.djangoapps.instructor.views.serializers_v2.is_bulk_email_feature_enabled') + @ddt.data( + (False, 'staff'), + (False, 'instructor'), + (False, 'admin'), + (True, 'data_researcher'), + ) + @ddt.unpack + def test_bulk_email_tab_not_visible(self, feature_enabled, user_attribute, mock_bulk_email_enabled): + """ + Test that the bulk_email tab does not appear when is_bulk_email_feature_enabled is False or the user is not + a user with staff permissions. + """ + mock_bulk_email_enabled.return_value = feature_enabled + + user = getattr(self, user_attribute) + tabs = self._get_tabs_from_response(user) + tab_ids = [tab['tab_id'] for tab in tabs] + + self.assertNotIn('bulk_email', tab_ids) + def test_tabs_have_sort_order(self): """ Test that all tabs include a sort_order field. diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 32f8d8e02a..946b9be000 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -2448,7 +2448,7 @@ class ListEmailContent(APIView): return JsonResponse(response_payload) -class InstructorTaskSerializer(serializers.Serializer): # pylint: disable=abstract-method +class InstructorTaskSerializerV2(serializers.Serializer): # pylint: disable=abstract-method """ Serializer that describes the format of a single instructor task. """ @@ -2467,16 +2467,13 @@ class InstructorTaskSerializer(serializers.Serializer): # pylint: disable=abstr duration_sec = serializers.CharField(help_text=_("Task duration information, if known")) task_message = serializers.CharField(help_text=_("User-friendly task status information, if available.")) - class Meta: - ref_name = "instructor.InstructorTask.v1" - class InstructorTasksListSerializer(serializers.Serializer): # pylint: disable=abstract-method """ Serializer to describe the response of the instructor tasks list API. """ tasks = serializers.ListSerializer( - child=InstructorTaskSerializer(), + child=InstructorTaskSerializerV2(), help_text=_("List of instructor tasks.") ) diff --git a/lms/djangoapps/instructor/views/api_v2.py b/lms/djangoapps/instructor/views/api_v2.py index cc76cd80cd..5e4230b066 100644 --- a/lms/djangoapps/instructor/views/api_v2.py +++ b/lms/djangoapps/instructor/views/api_v2.py @@ -29,7 +29,7 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.lib.courses import get_course_by_id from .serializers_v2 import ( InstructorTaskListSerializer, - CourseInformationSerializer, + CourseInformationSerializerV2, BlockDueDateSerializerV2, ) from .tools import ( @@ -125,7 +125,7 @@ class CourseMetadataView(DeveloperErrorViewMixin, APIView): ), ], responses={ - 200: CourseInformationSerializer, + 200: CourseInformationSerializerV2, 401: "The requesting user is not authenticated.", 403: "The requesting user lacks instructor access to the course.", 404: "The requested course does not exist.", @@ -216,7 +216,7 @@ class CourseMetadataView(DeveloperErrorViewMixin, APIView): 'user': request.user, 'request': request } - serializer = CourseInformationSerializer(context) + serializer = CourseInformationSerializerV2(context) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/lms/djangoapps/instructor/views/serializers_v2.py b/lms/djangoapps/instructor/views/serializers_v2.py index bba3d95143..339c2c9e99 100644 --- a/lms/djangoapps/instructor/views/serializers_v2.py +++ b/lms/djangoapps/instructor/views/serializers_v2.py @@ -34,7 +34,7 @@ from xmodule.modulestore.django import modulestore from .tools import get_student_from_identifier, parse_datetime, DashboardError -class CourseInformationSerializer(serializers.Serializer): +class CourseInformationSerializerV2(serializers.Serializer): """ Serializer for comprehensive course information. @@ -208,6 +208,7 @@ class CourseInformationSerializer(serializers.Serializer): 'open_responses', 'certificates', 'cohorts', + 'bulk_email', 'special_exams', ] order_index = {tab: i for i, tab in enumerate(tabs_order)} @@ -370,9 +371,6 @@ class InstructorTaskSerializer(serializers.Serializer): task_input = serializers.CharField() task_output = serializers.CharField(allow_null=True) - class Meta: - ref_name = "instructor.InstructorTask.v2" - class InstructorTaskListSerializer(serializers.Serializer): tasks = InstructorTaskSerializer(many=True)