Merge pull request #3283 from edx/will/ora2-delete-student-state
Reset submission API scores in beta and legacy instructor dash
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
67
lms/djangoapps/instructor/tests/test_legacy_reset.py
Normal file
67
lms/djangoapps/instructor/tests/test_legacy_reset.py
Normal file
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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 += "<font color='red'>{err}</font> ".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 += "<font color='red'>{err_msg} ({err})</font>".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 += "<font color='red'>{text}</font>".format(
|
||||
text=_("Deleted student module state for {state}!").format(state=module_state_key)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user