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:
@@ -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,
|
||||
|
||||
@@ -8,7 +8,6 @@ import random
|
||||
import pytz
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user