|
|
|
|
@@ -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
|
|
|
|
|
|