Skip to content

Commit

Permalink
feat: upgrading list_instructor_tasks to DRF ( 10th ) (openedx#35332)
Browse files Browse the repository at this point in the history
* feat: upgrading simple api to drf compatible.
  • Loading branch information
awais786 authored Sep 18, 2024
1 parent 50ceed3 commit 3de0dbd
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 18 deletions.
27 changes: 19 additions & 8 deletions lms/djangoapps/instructor/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4704,15 +4704,19 @@ class TestOauthInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollm
Test endpoints using Oauth2 authentication.
"""

@classmethod
def setUpClass(cls):
super().setUpClass()
cls.course = CourseFactory.create(
entrance_exam_id='i4x://{}/{}/chapter/Entrance_exam'.format('test_org', 'test_course')
)

def setUp(self):
super().setUp()
self.course = CourseFactory.create(
org='test_org',
course='test_course',
run='test_run',
entrance_exam_id='i4x://{}/{}/chapter/Entrance_exam'.format('test_org', 'test_course')
)
self.problem_location = msk_from_problem_urlname(
self.course.id,
'robot-some-problem-urlname'
)
self.problem_urlname = str(self.problem_location)

self.other_user = UserFactory()
dot_application = ApplicationFactory(user=self.other_user, authorization_grant_type='password')
Expand Down Expand Up @@ -4744,7 +4748,14 @@ def setUp(self):
"send-to": ["myself"],
"subject": "This is subject",
"message": "message"
}, 'data_researcher')
}, 'data_researcher'),
('list_instructor_tasks',
{
'problem_location_str': self.problem_urlname,
'unique_student_identifier': self.other_user.email
},
'data_researcher'),
('list_instructor_tasks', {}, 'data_researcher')
]

self.fake_jwt = ('wyJUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjaGFuZ2UtbWUiLCJleHAiOjE3MjU4OTA2NzIsImdyY'
Expand Down
40 changes: 31 additions & 9 deletions lms/djangoapps/instructor/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
from lms.djangoapps.instructor_task.models import ReportStore
from lms.djangoapps.instructor.views.serializer import (
AccessSerializer, BlockDueDateSerializer, RoleNameSerializer, ShowStudentExtensionSerializer, UserSerializer,
SendEmailSerializer, StudentAttemptsSerializer
SendEmailSerializer, StudentAttemptsSerializer, ListInstructorTaskInputSerializer
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted
Expand Down Expand Up @@ -2373,9 +2373,8 @@ def get(self, request, course_id):
return _list_instructor_tasks(request=request, course_id=course_id)


@require_POST
@ensure_csrf_cookie
def list_instructor_tasks(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ListInstructorTasks(APIView):
"""
List instructor tasks.
Expand All @@ -2385,21 +2384,44 @@ def list_instructor_tasks(request, course_id):
- `problem_location_str` and `unique_student_identifier` lists task
history for problem AND student (intersection)
"""
return _list_instructor_tasks(request=request, course_id=course_id)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.SHOW_TASKS
serializer_class = ListInstructorTaskInputSerializer

@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
List instructor tasks.
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)

return _list_instructor_tasks(
request=request, course_id=course_id, serialize_data=serializer.validated_data
)


@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.SHOW_TASKS)
def _list_instructor_tasks(request, course_id):
def _list_instructor_tasks(request, course_id, serialize_data=None):
"""
List instructor tasks.
Internal function with common code for both DRF and and tradition views.
"""
# This method is also used by other APIs with the GET method.
# The query_params attribute is utilized for GET requests,
# where parameters are passed as query strings.

course_id = CourseKey.from_string(course_id)
params = getattr(request, 'query_params', request.POST)
problem_location_str = strip_if_string(params.get('problem_location_str', False))
student = params.get('unique_student_identifier', None)
if serialize_data is not None:
problem_location_str = strip_if_string(serialize_data.get('problem_location_str', False))
student = serialize_data.get('unique_student_identifier', None)
else:
params = getattr(request, 'query_params', request.POST)
problem_location_str = strip_if_string(params.get('problem_location_str', False))
student = params.get('unique_student_identifier', None)

if student is not None:
student = get_student_from_identifier(student)

Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/instructor/views/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
name='list_entrance_exam_instructor_tasks'),
path('mark_student_can_skip_entrance_exam', api.mark_student_can_skip_entrance_exam,
name='mark_student_can_skip_entrance_exam'),
path('list_instructor_tasks', api.list_instructor_tasks, name='list_instructor_tasks'),
path('list_instructor_tasks', api.ListInstructorTasks.as_view(), name='list_instructor_tasks'),
path('list_background_email_tasks', api.list_background_email_tasks, name='list_background_email_tasks'),
path('list_email_content', api.ListEmailContent.as_view(), name='list_email_content'),
path('list_forum_members', api.list_forum_members, name='list_forum_members'),
Expand Down
37 changes: 37 additions & 0 deletions lms/djangoapps/instructor/views/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,43 @@ def validate_unique_student_identifier(self, value):
return user


class ListInstructorTaskInputSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Serializer for handling the input data for the problem response report generation API.
Attributes:
unique_student_identifier (str): The email or username of the student.
This field is optional, but if provided, the `problem_location_str`
must also be provided.
problem_location_str (str): The string representing the location of the problem within the course.
This field is optional, unless `unique_student_identifier` is provided.
"""
unique_student_identifier = serializers.CharField(
max_length=255,
help_text="Email or username of student",
required=False
)
problem_location_str = serializers.CharField(
help_text="Problem location",
required=False
)

def validate(self, data):
"""
Validate the data to ensure that if unique_student_identifier is provided,
problem_location_str must also be provided.
"""
unique_student_identifier = data.get('unique_student_identifier')
problem_location_str = data.get('problem_location_str')

if unique_student_identifier and not problem_location_str:
raise serializers.ValidationError(
"unique_student_identifier must accompany problem_location_str"
)

return data


class ShowStudentExtensionSerializer(serializers.Serializer):
"""
Serializer for validating and processing the student identifier.
Expand Down

0 comments on commit 3de0dbd

Please sign in to comment.