diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py
index e62851d6cb..1651e91a18 100644
--- a/lms/djangoapps/instructor/enrollment.py
+++ b/lms/djangoapps/instructor/enrollment.py
@@ -14,6 +14,11 @@ from student.models import CourseEnrollment, CourseEnrollmentAllowed
from courseware.models import StudentModule
from edxmako.shortcuts import render_to_string
+# Submissions is a Django app that is currently installed
+# from the edx-ora2 repo, although it will likely move in the future.
+from submissions import api as sub_api
+from student.models import anonymous_id_for_user
+
from microsite_configuration import microsite
# For determining if a shibboleth course
@@ -175,11 +180,28 @@ def reset_student_attempts(course_id, student, module_state_key, delete_module=F
`problem_to_reset` is the name of a problem e.g. 'L2Node1'.
To build the module_state_key 'problem/' and course information will be appended to `problem_to_reset`.
- Throws ValueError if `problem_state` is invalid JSON.
+ Raises:
+ ValueError: `problem_state` is invalid JSON.
+ StudentModule.DoesNotExist: could not load the student module.
+ submissions.SubmissionError: unexpected error occurred while resetting the score in the submissions API.
+
"""
- module_to_reset = StudentModule.objects.get(student_id=student.id,
- course_id=course_id,
- module_state_key=module_state_key)
+ # Reset the student's score in the submissions API
+ # Currently this is used only by open assessment (ORA 2)
+ # We need to do this *before* retrieving the `StudentModule` model,
+ # because it's possible for a score to exist even if no student module exists.
+ if delete_module:
+ sub_api.reset_score(
+ anonymous_id_for_user(student, course_id),
+ course_id,
+ module_state_key,
+ )
+
+ module_to_reset = StudentModule.objects.get(
+ student_id=student.id,
+ course_id=course_id,
+ module_state_key=module_state_key
+ )
if delete_module:
module_to_reset.delete()
diff --git a/lms/djangoapps/instructor/tests/test_enrollment.py b/lms/djangoapps/instructor/tests/test_enrollment.py
index 8c34c2b95d..b34fd64517 100644
--- a/lms/djangoapps/instructor/tests/test_enrollment.py
+++ b/lms/djangoapps/instructor/tests/test_enrollment.py
@@ -23,6 +23,9 @@ from instructor.enrollment import (
unenroll_email
)
+from submissions import api as sub_api
+from student.models import anonymous_id_for_user
+
class TestSettableEnrollmentState(TestCase):
""" Test the basis class for enrollment tests. """
@@ -306,6 +309,33 @@ class TestInstructorEnrollmentStudentModule(TestCase):
reset_student_attempts(self.course_id, user, msk, delete_module=True)
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_id, module_state_key=msk).count(), 0)
+ def test_delete_submission_scores(self):
+ user = UserFactory()
+ course_id = 'ora2/1/1'
+ item_id = 'i4x://ora2/1/openassessment/b3dce2586c9c4876b73e7f390e42ef8f'
+
+ # Create a student module for the user
+ StudentModule.objects.create(
+ student=user, course_id=course_id, module_state_key=item_id, state=json.dumps({})
+ )
+
+ # Create a submission and score for the student using the submissions API
+ student_item = {
+ 'student_id': anonymous_id_for_user(user, course_id),
+ 'course_id': course_id,
+ 'item_id': item_id,
+ 'item_type': 'openassessment'
+ }
+ submission = sub_api.create_submission(student_item, 'test answer')
+ sub_api.set_score(submission['uuid'], 1, 2)
+
+ # Delete student state using the instructor dash
+ reset_student_attempts(course_id, user, item_id, delete_module=True)
+
+ # Verify that the student's scores have been reset in the submissions API
+ score = sub_api.get_score(student_item)
+ self.assertIs(score, None)
+
class EnrollmentObjects(object):
"""
diff --git a/lms/djangoapps/instructor/tests/test_legacy_reset.py b/lms/djangoapps/instructor/tests/test_legacy_reset.py
new file mode 100644
index 0000000000..ea259bc1fb
--- /dev/null
+++ b/lms/djangoapps/instructor/tests/test_legacy_reset.py
@@ -0,0 +1,67 @@
+"""
+View-level tests for resetting student state in legacy instructor dash.
+"""
+
+import json
+from django.core.urlresolvers import reverse
+from django.test.utils import override_settings
+
+from courseware.tests.helpers import LoginEnrollmentTestCase
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
+from xmodule.modulestore.tests.factories import CourseFactory
+from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
+
+from courseware.models import StudentModule
+
+from submissions import api as sub_api
+from student.models import anonymous_id_for_user
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class InstructorResetStudentStateTest(ModuleStoreTestCase, LoginEnrollmentTestCase):
+ """
+ Reset student state from the legacy instructor dash.
+ """
+
+ def setUp(self):
+ """
+ Log in as an instructor, and create a course/student to reset.
+ """
+ instructor = AdminFactory.create()
+ self.client.login(username=instructor.username, password='test')
+ self.student = UserFactory.create(username='test', email='test@example.com')
+ self.course = CourseFactory.create()
+ CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
+
+ def test_delete_student_state_resets_scores(self):
+ item_id = 'i4x://MITx/999/openassessment/b3dce2586c9c4876b73e7f390e42ef8f'
+
+ # Create a student module for the user
+ StudentModule.objects.create(
+ student=self.student, course_id=self.course.id, module_state_key=item_id, state=json.dumps({})
+ )
+
+ # Create a submission and score for the student using the submissions API
+ student_item = {
+ 'student_id': anonymous_id_for_user(self.student, self.course.id),
+ 'course_id': self.course.id,
+ 'item_id': item_id,
+ 'item_type': 'openassessment'
+ }
+ submission = sub_api.create_submission(student_item, 'test answer')
+ sub_api.set_score(submission['uuid'], 1, 2)
+
+ # Delete student state using the instructor dash
+ url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
+ response = self.client.post(url, {
+ 'action': 'Delete student state for module',
+ 'unique_student_identifier': self.student.email,
+ 'problem_for_student': 'openassessment/b3dce2586c9c4876b73e7f390e42ef8f',
+ })
+
+ self.assertEqual(response.status_code, 200)
+
+ # Verify that the student's scores have been reset in the submissions API
+ score = sub_api.get_score(student_item)
+ self.assertIs(score, None)
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 3e8c618194..bd1573845b 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -51,6 +51,10 @@ import analytics.distributions
import analytics.csvs
import csv
+# Submissions is a Django app that is currently installed
+# from the edx-ora2 repo, although it will likely move in the future.
+from submissions import api as sub_api
+
from bulk_email.models import CourseEmail
from .tools import (
@@ -739,7 +743,11 @@ def reset_student_attempts(request, course_id):
try:
enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module)
except StudentModule.DoesNotExist:
- return HttpResponseBadRequest("Module does not exist.")
+ return HttpResponseBadRequest(_("Module does not exist."))
+ except sub_api.SubmissionError:
+ # Trust the submissions API to log the error
+ error_msg = _("An error occurred while deleting the score.")
+ return HttpResponse(error_msg, status=500)
response_payload['student'] = student_identifier
elif all_students:
instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key)
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 9d515db359..adf9eaa6c7 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -30,6 +30,11 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.html_module import HtmlDescriptor
+# Submissions is a Django app that is currently installed
+# from the edx-ora2 repo, although it will likely move in the future.
+from submissions import api as sub_api
+from student.models import anonymous_id_for_user
+
from bulk_email.models import CourseEmail, CourseAuthorization
from courseware import grades
from courseware.access import has_access
@@ -348,6 +353,23 @@ def instructor_dashboard(request, course_id):
msg += message
student_module = None
if student is not None:
+
+ # Reset the student's score in the submissions API
+ # Currently this is used only by open assessment (ORA 2)
+ # We need to do this *before* retrieving the `StudentModule` model,
+ # because it's possible for a score to exist even if no student module exists.
+ if "Delete student state for module" in action:
+ try:
+ sub_api.reset_score(
+ anonymous_id_for_user(student, course_id),
+ course_id,
+ module_state_key,
+ )
+ except sub_api.SubmissionError:
+ # Trust the submissions API to log the error
+ error_msg = _("An error occurred while deleting the score.")
+ msg += "{err} ".format(err=error_msg)
+
# find the module in question
try:
student_module = StudentModule.objects.get(
@@ -356,6 +378,7 @@ def instructor_dashboard(request, course_id):
module_state_key=module_state_key
)
msg += _("Found module. ")
+
except StudentModule.DoesNotExist as err:
error_msg = _("Couldn't find module with that urlname: {url}. ").format(url=problem_urlname)
msg += "{err_msg} ({err})".format(err_msg=error_msg, err=err)
@@ -366,6 +389,7 @@ def instructor_dashboard(request, course_id):
# delete the state
try:
student_module.delete()
+
msg += "{text}".format(
text=_("Deleted student module state for {state}!").format(state=module_state_key)
)
diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt
index 0f04b0029b..8441cb4804 100644
--- a/requirements/edx/github.txt
+++ b/requirements/edx/github.txt
@@ -26,7 +26,7 @@
-e git+https://github.com/edx/bok-choy.git@25a47b3bf87c503fc4996e52addac83b42ec6f38#egg=bok_choy
-e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash
-e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock
--e git+https://github.com/edx/edx-ora2.git@release-2014-04-14#egg=edx-ora2
+-e git+https://github.com/edx/edx-ora2.git@release-2014-04-16#egg=edx-ora2
# Prototype XBlocks for limited roll-outs and user testing. These are not for general use.
-e git+https://github.com/pmitros/ConceptXBlock.git@2376fde9ebdd83684b78dde77ef96361c3bd1aa0#egg=concept-xblock