Merge pull request #8679 from open-craft/recursive_state_reset

Make resetting of attempts and student state on blocks recursive. (SOL-858)
This commit is contained in:
Kelketek
2015-07-01 10:05:42 -05:00
3 changed files with 158 additions and 13 deletions

View File

@@ -5,6 +5,7 @@ Does not include any access control, be sure to check access before calling.
"""
import json
import logging
from django.contrib.auth.models import User
from django.conf import settings
from django.core.urlresolvers import reverse
@@ -21,6 +22,11 @@ from student.models import anonymous_id_for_user
from openedx.core.djangoapps.user_api.models import UserPreference
from microsite_configuration import microsite
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__)
class EmailEnrollmentState(object):
@@ -203,6 +209,19 @@ def reset_student_attempts(course_id, student, module_state_key, delete_module=F
submissions.SubmissionError: unexpected error occurred while resetting the score in the submissions API.
"""
try:
# A block may have children. Clear state on children first.
block = modulestore().get_item(module_state_key)
if block.has_children:
for child in block.children:
try:
reset_student_attempts(course_id, student, child, delete_module=delete_module)
except StudentModule.DoesNotExist:
# If a particular child doesn't have any state, no big deal, as long as the parent does.
pass
except ItemNotFoundError:
log.warning("Could not find %s in modulestore when attempting to reset attempts.", 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,

View File

@@ -8,7 +8,6 @@ import random
import pytz
import io
import json
import os
import requests
import shutil
import tempfile

View File

@@ -13,7 +13,8 @@ from django.utils.translation import get_language
from django.utils.translation import override as override_language
from nose.plugins.attrib import attr
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from student.models import CourseEnrollment, CourseEnrollmentAllowed
from instructor.enrollment import (
@@ -295,31 +296,102 @@ class TestInstructorUnenrollDB(TestEnrollmentChangeBase):
@attr('shard_1')
class TestInstructorEnrollmentStudentModule(TestCase):
class TestInstructorEnrollmentStudentModule(ModuleStoreTestCase):
""" Test student module manipulations. """
def setUp(self):
super(TestInstructorEnrollmentStudentModule, self).setUp()
self.course_key = SlashSeparatedCourseKey('fake', 'course', 'id')
store = modulestore()
self.user = UserFactory()
self.course = CourseFactory(
name='fake',
org='course',
run='id',
)
# pylint: disable=no-member
self.course_key = self.course.location.course_key
self.parent = ItemFactory(
category="library_content",
# pylint: disable=no-member
user_id=self.user.id,
parent=self.course,
publish_item=True,
modulestore=store,
)
self.child = ItemFactory(
category="html",
# pylint: disable=no-member
user_id=self.user.id,
parent=self.parent,
publish_item=True,
modulestore=store,
)
self.unrelated = ItemFactory(
category="html",
# pylint: disable=no-member
user_id=self.user.id,
parent=self.course,
publish_item=True,
modulestore=store,
)
parent_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
child_state = json.dumps({'attempts': 10, 'whatever': 'things'})
unrelated_state = json.dumps({'attempts': 12, 'brains': 'zombie'})
StudentModule.objects.create(
student=self.user,
course_id=self.course_key,
module_state_key=self.parent.location,
state=parent_state,
)
StudentModule.objects.create(
student=self.user,
course_id=self.course_key,
module_state_key=self.child.location,
state=child_state,
)
StudentModule.objects.create(
student=self.user,
course_id=self.course_key,
module_state_key=self.unrelated.location,
state=unrelated_state,
)
def test_reset_student_attempts(self):
user = UserFactory()
msk = self.course_key.make_usage_key('dummy', 'module')
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state)
StudentModule.objects.create(
student=self.user,
course_id=self.course_key,
module_state_key=msk,
state=original_state
)
# lambda to reload the module state from the database
module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk)
module = lambda: StudentModule.objects.get(student=self.user, course_id=self.course_key, module_state_key=msk)
self.assertEqual(json.loads(module().state)['attempts'], 32)
reset_student_attempts(self.course_key, user, msk)
reset_student_attempts(self.course_key, self.user, msk)
self.assertEqual(json.loads(module().state)['attempts'], 0)
def test_delete_student_attempts(self):
user = UserFactory()
msk = self.course_key.make_usage_key('dummy', 'module')
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state)
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1)
reset_student_attempts(self.course_key, user, msk, delete_module=True)
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0)
StudentModule.objects.create(
student=self.user,
course_id=self.course_key,
module_state_key=msk,
state=original_state
)
self.assertEqual(
StudentModule.objects.filter(
student=self.user,
course_id=self.course_key,
module_state_key=msk
).count(), 1)
reset_student_attempts(self.course_key, self.user, msk, delete_module=True)
self.assertEqual(
StudentModule.objects.filter(
student=self.user,
course_id=self.course_key,
module_state_key=msk
).count(), 0)
def test_delete_submission_scores(self):
user = UserFactory()
@@ -353,6 +425,61 @@ class TestInstructorEnrollmentStudentModule(TestCase):
score = sub_api.get_score(student_item)
self.assertIs(score, None)
def get_state(self, location):
"""Reload and grab the module state from the database"""
return StudentModule.objects.get(
student=self.user, course_id=self.course_key, module_state_key=location
).state
def test_reset_student_attempts_children(self):
parent_state = json.loads(self.get_state(self.parent.location))
self.assertEqual(parent_state['attempts'], 32)
self.assertEqual(parent_state['otherstuff'], 'alsorobots')
child_state = json.loads(self.get_state(self.child.location))
self.assertEqual(child_state['attempts'], 10)
self.assertEqual(child_state['whatever'], 'things')
unrelated_state = json.loads(self.get_state(self.unrelated.location))
self.assertEqual(unrelated_state['attempts'], 12)
self.assertEqual(unrelated_state['brains'], 'zombie')
reset_student_attempts(self.course_key, self.user, self.parent.location)
parent_state = json.loads(self.get_state(self.parent.location))
self.assertEqual(json.loads(self.get_state(self.parent.location))['attempts'], 0)
self.assertEqual(parent_state['otherstuff'], 'alsorobots')
child_state = json.loads(self.get_state(self.child.location))
self.assertEqual(child_state['attempts'], 0)
self.assertEqual(child_state['whatever'], 'things')
unrelated_state = json.loads(self.get_state(self.unrelated.location))
self.assertEqual(unrelated_state['attempts'], 12)
self.assertEqual(unrelated_state['brains'], 'zombie')
def test_delete_submission_scores_attempts_children(self):
parent_state = json.loads(self.get_state(self.parent.location))
self.assertEqual(parent_state['attempts'], 32)
self.assertEqual(parent_state['otherstuff'], 'alsorobots')
child_state = json.loads(self.get_state(self.child.location))
self.assertEqual(child_state['attempts'], 10)
self.assertEqual(child_state['whatever'], 'things')
unrelated_state = json.loads(self.get_state(self.unrelated.location))
self.assertEqual(unrelated_state['attempts'], 12)
self.assertEqual(unrelated_state['brains'], 'zombie')
reset_student_attempts(self.course_key, self.user, self.parent.location, delete_module=True)
self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.parent.location)
self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.child.location)
unrelated_state = json.loads(self.get_state(self.unrelated.location))
self.assertEqual(unrelated_state['attempts'], 12)
self.assertEqual(unrelated_state['brains'], 'zombie')
class EnrollmentObjects(object):
"""