Rescore problem to drf (#35627)

* feat!: upgrading api to DRF.
This commit is contained in:
Awais Qureshi
2025-04-15 15:24:05 +05:00
committed by GitHub
parent 694bf77993
commit 6680aecbbe
3 changed files with 100 additions and 65 deletions

View File

@@ -115,7 +115,8 @@ from lms.djangoapps.instructor.views.serializer import (
ShowStudentExtensionSerializer,
StudentAttemptsSerializer,
UserSerializer,
UniqueStudentIdentifierSerializer
UniqueStudentIdentifierSerializer,
ProblemResetSerializer
)
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
@@ -2052,84 +2053,92 @@ def reset_student_attempts_for_entrance_exam(request, course_id):
return JsonResponse(response_payload)
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.OVERRIDE_GRADES)
@require_post_params(problem_to_reset="problem urlname to reset")
@common_exceptions_400
def rescore_problem(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
@method_decorator(transaction.non_atomic_requests, name='dispatch')
class RescoreProblem(DeveloperErrorViewMixin, APIView):
"""
Starts a background process a students attempts counter. Optionally deletes student state for a problem.
Rescore for all students is limited to instructor access.
Takes either of the following query parameters
- problem_to_reset is a urlname of a problem
- unique_student_identifier is an email or username
- all_students is a boolean
all_students and unique_student_identifier cannot both be present.
"""
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'staff', course_id)
all_students = _get_boolean_param(request, 'all_students')
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.OVERRIDE_GRADES
serializer_class = ProblemResetSerializer
if all_students and not has_access(request.user, 'instructor', course):
return HttpResponseForbidden("Requires instructor access.")
@method_decorator(ensure_csrf_cookie)
@method_decorator(transaction.non_atomic_requests)
def post(self, request, course_id):
"""
Takes either of the following query parameters
- problem_to_reset is a urlname of a problem
- unique_student_identifier is an email or username
- all_students is a boolean
only_if_higher = _get_boolean_param(request, 'only_if_higher')
problem_to_reset = strip_if_string(request.POST.get('problem_to_reset'))
student_identifier = request.POST.get('unique_student_identifier', None)
student = None
if student_identifier is not None:
student = get_student_from_identifier(student_identifier)
all_students and unique_student_identifier cannot both be present.
"""
if not (problem_to_reset and (all_students or student)):
return HttpResponseBadRequest("Missing query parameters.")
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'staff', course_id)
if all_students and student:
return HttpResponseBadRequest(
"Cannot rescore with all_students and unique_student_identifier."
)
serializer_data = self.serializer_class(data=request.data)
try:
module_state_key = UsageKey.from_string(problem_to_reset).map_into_course(course_id)
except InvalidKeyError:
return HttpResponseBadRequest("Unable to parse problem id")
if not serializer_data.is_valid():
return HttpResponseBadRequest(reason=serializer_data.errors)
response_payload = {'problem_to_reset': problem_to_reset}
problem_to_reset = serializer_data.validated_data.get("problem_to_reset")
all_students = serializer_data.validated_data.get("all_students")
only_if_higher = serializer_data.validated_data.get("only_if_higher")
if student:
response_payload['student'] = student_identifier
try:
task_api.submit_rescore_problem_for_student(
request,
module_state_key,
student,
only_if_higher,
student = serializer_data.validated_data.get("unique_student_identifier")
student_identifier = request.data.get("unique_student_identifier")
if all_students and not has_access(request.user, 'instructor', course):
return HttpResponseForbidden("Requires instructor access.")
if not (problem_to_reset and (all_students or student)):
return HttpResponseBadRequest("Missing query parameters.")
if all_students and student:
return HttpResponseBadRequest(
"Cannot rescore with all_students and unique_student_identifier."
)
except NotImplementedError as exc:
return HttpResponseBadRequest(str(exc))
except ItemNotFoundError as exc:
return HttpResponseBadRequest(f"{module_state_key} not found")
elif all_students:
try:
task_api.submit_rescore_problem_for_all_students(
request,
module_state_key,
only_if_higher,
)
except NotImplementedError as exc:
return HttpResponseBadRequest(str(exc))
except ItemNotFoundError as exc:
return HttpResponseBadRequest(f"{module_state_key} not found")
else:
return HttpResponseBadRequest()
module_state_key = UsageKey.from_string(problem_to_reset).map_into_course(course_id)
except InvalidKeyError:
return HttpResponseBadRequest("Unable to parse problem id")
response_payload['task'] = TASK_SUBMISSION_OK
return JsonResponse(response_payload)
response_payload = {'problem_to_reset': problem_to_reset}
if student:
response_payload['student'] = student_identifier
try:
task_api.submit_rescore_problem_for_student(
request,
module_state_key,
student,
only_if_higher,
)
except NotImplementedError as exc:
return HttpResponseBadRequest(str(exc))
except ItemNotFoundError as exc:
return HttpResponseBadRequest(f"{module_state_key} not found")
elif all_students:
try:
task_api.submit_rescore_problem_for_all_students(
request,
module_state_key,
only_if_higher,
)
except NotImplementedError as exc:
return HttpResponseBadRequest(str(exc))
except ItemNotFoundError as exc:
return HttpResponseBadRequest(f"{module_state_key} not found")
else:
return HttpResponseBadRequest()
response_payload['task'] = TASK_SUBMISSION_OK
return JsonResponse(response_payload)
@transaction.non_atomic_requests

View File

@@ -36,7 +36,7 @@ urlpatterns = [
name="get_student_enrollment_status"),
path('get_student_progress_url', api.StudentProgressUrl.as_view(), name='get_student_progress_url'),
path('reset_student_attempts', api.ResetStudentAttempts.as_view(), name='reset_student_attempts'),
path('rescore_problem', api.rescore_problem, name='rescore_problem'),
path('rescore_problem', api.RescoreProblem.as_view(), name='rescore_problem'),
path('override_problem_score', api.override_problem_score, name='override_problem_score'),
path('reset_student_attempts_for_entrance_exam', api.reset_student_attempts_for_entrance_exam,
name='reset_student_attempts_for_entrance_exam'),

View File

@@ -233,6 +233,32 @@ class BlockDueDateSerializer(serializers.Serializer):
self.fields['due_datetime'].required = False
class ProblemResetSerializer(UniqueStudentIdentifierSerializer):
"""
serializer for resetting problem.
"""
problem_to_reset = serializers.CharField(
help_text=_("The URL name of the problem to reset."),
error_messages={
'blank': _("Problem URL name cannot be blank."),
}
)
all_students = serializers.BooleanField(
default=False,
help_text=_("Whether to reset the problem for all students."),
)
only_if_higher = serializers.BooleanField(
default=False,
)
# Override the unique_student_identifier field to make it optional
unique_student_identifier = serializers.CharField(
required=False, # Make this field optional
allow_null=True,
help_text=_("unique student identifier.")
)
class ModifyAccessSerializer(serializers.Serializer):
"""
serializers for enroll or un-enroll users in beta testing program.