Skip to content

Commit 7b739ea

Browse files
authored
feat: rescore entrance exam to drf (#36574)
* feat: rescore entrance exam to drf.
1 parent 498dd56 commit 7b739ea

3 files changed

Lines changed: 66 additions & 45 deletions

File tree

lms/djangoapps/instructor/views/api.py

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@
116116
StudentAttemptsSerializer,
117117
UserSerializer,
118118
UniqueStudentIdentifierSerializer,
119-
ProblemResetSerializer
119+
ProblemResetSerializer,
120+
RescoreEntranceExamSerializer
120121
)
121122
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
122123
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted
@@ -2201,62 +2202,75 @@ def override_problem_score(request, course_id): # lint-amnesty, pylint: disable
22012202
return JsonResponse(response_payload)
22022203

22032204

2204-
@transaction.non_atomic_requests
2205-
@require_POST
2206-
@ensure_csrf_cookie
2207-
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
2208-
@require_course_permission(permissions.RESCORE_EXAMS)
2209-
@common_exceptions_400
2210-
def rescore_entrance_exam(request, course_id):
2205+
@method_decorator(transaction.non_atomic_requests, name='dispatch')
2206+
class RescoreEntranceExamView(DeveloperErrorViewMixin, APIView):
22112207
"""
2212-
Starts a background process a students attempts counter for entrance exam.
2208+
Starts a background process for a student's attempts counter for entrance exam.
22132209
Optionally deletes student state for a problem. Limited to instructor access.
22142210
2215-
Takes either of the following query parameters
2216-
- unique_student_identifier is an email or username
2217-
- all_students is a boolean
2211+
Takes either of the following parameters:
2212+
- unique_student_identifier: an email or username
2213+
- all_students: a boolean
22182214
22192215
all_students and unique_student_identifier cannot both be present.
22202216
"""
2221-
course_id = CourseKey.from_string(course_id)
2222-
course = get_course_with_access(
2223-
request.user, 'staff', course_id, depth=None
2224-
)
2225-
2226-
student_identifier = request.POST.get('unique_student_identifier', None)
2227-
only_if_higher = request.POST.get('only_if_higher', None)
2228-
student = None
2229-
if student_identifier is not None:
2230-
student = get_student_from_identifier(student_identifier)
2217+
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
2218+
permission_name = permissions.RESCORE_EXAMS
2219+
serializer_class = RescoreEntranceExamSerializer
22312220

2232-
all_students = _get_boolean_param(request, 'all_students')
2221+
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
2222+
@method_decorator(ensure_csrf_cookie)
2223+
def post(self, request, course_id):
2224+
"""
2225+
Initiates a Celery task to rescore the entrance exam for a student or all students.
2226+
"""
2227+
serializer = self.serializer_class(data=request.data)
2228+
serializer.is_valid(raise_exception=True)
2229+
data = serializer.validated_data
22332230

2234-
if not course.entrance_exam_id:
2235-
return HttpResponseBadRequest(
2236-
_("Course has no entrance exam section.")
2231+
course_id = CourseKey.from_string(course_id)
2232+
course = get_course_with_access(
2233+
request.user, 'staff', course_id, depth=None
22372234
)
22382235

2239-
if all_students and student:
2240-
return HttpResponseBadRequest(
2241-
_("Cannot rescore with all_students and unique_student_identifier.")
2242-
)
2236+
if not course.entrance_exam_id:
2237+
return Response(
2238+
{"error": _("Course has no entrance exam section.")},
2239+
status=status.HTTP_400_BAD_REQUEST
2240+
)
22432241

2244-
try:
2245-
entrance_exam_key = UsageKey.from_string(course.entrance_exam_id).map_into_course(course_id)
2246-
except InvalidKeyError:
2247-
return HttpResponseBadRequest(_("Course has no valid entrance exam section."))
2242+
student_identifier = data.get('unique_student_identifier')
2243+
only_if_higher = data.get('only_if_higher')
2244+
all_students = data.get('all_students', False)
2245+
student = None
22482246

2249-
response_payload = {}
2250-
if student:
2251-
response_payload['student'] = student_identifier
2252-
else:
2253-
response_payload['student'] = _("All Students")
2247+
if student_identifier:
2248+
student = get_student_from_identifier(student_identifier)
22542249

2255-
task_api.submit_rescore_entrance_exam_for_student(
2256-
request, entrance_exam_key, student, only_if_higher,
2257-
)
2258-
response_payload['task'] = TASK_SUBMISSION_OK
2259-
return JsonResponse(response_payload)
2250+
if all_students and student:
2251+
return Response(
2252+
{"error": _("Cannot rescore with all_students and unique_student_identifier.")},
2253+
status=status.HTTP_400_BAD_REQUEST
2254+
)
2255+
2256+
try:
2257+
entrance_exam_key = UsageKey.from_string(course.entrance_exam_id).map_into_course(course_id)
2258+
except InvalidKeyError:
2259+
return Response(
2260+
{"error": _("Course has no valid entrance exam section.")},
2261+
status=status.HTTP_400_BAD_REQUEST
2262+
)
2263+
2264+
response_payload = {
2265+
'student': student_identifier if student else _("All Students"),
2266+
'task': TASK_SUBMISSION_OK
2267+
}
2268+
2269+
task_api.submit_rescore_entrance_exam_for_student(
2270+
request, entrance_exam_key, student, only_if_higher,
2271+
)
2272+
2273+
return Response(response_payload)
22602274

22612275

22622276
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')

lms/djangoapps/instructor/views/api_urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
path('override_problem_score', api.override_problem_score, name='override_problem_score'),
4141
path('reset_student_attempts_for_entrance_exam', api.reset_student_attempts_for_entrance_exam,
4242
name='reset_student_attempts_for_entrance_exam'),
43-
path('rescore_entrance_exam', api.rescore_entrance_exam, name='rescore_entrance_exam'),
43+
path('rescore_entrance_exam', api.RescoreEntranceExamView.as_view(), name='rescore_entrance_exam'),
4444
path('list_entrance_exam_instructor_tasks', api.ListEntranceExamInstructorTasks.as_view(),
4545
name='list_entrance_exam_instructor_tasks'),
4646
path('mark_student_can_skip_entrance_exam', api.MarkStudentCanSkipEntranceExam.as_view(),

lms/djangoapps/instructor/views/serializer.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,10 @@ def validate_user(self, value):
373373
return None
374374

375375
return user
376+
377+
378+
class RescoreEntranceExamSerializer(serializers.Serializer):
379+
"""Serializer for entrance exam rescoring"""
380+
unique_student_identifier = serializers.CharField(required=False, allow_null=True)
381+
all_students = serializers.BooleanField(required=False)
382+
only_if_higher = serializers.BooleanField(required=False, allow_null=True)

0 commit comments

Comments
 (0)