Commit 412f1205 by Jonathan Piacenti

Make resetting of attempts and student state on blocks recursive.

parent 754eb9af
...@@ -5,6 +5,7 @@ Does not include any access control, be sure to check access before calling. ...@@ -5,6 +5,7 @@ Does not include any access control, be sure to check access before calling.
""" """
import json import json
import logging
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -21,6 +22,11 @@ from student.models import anonymous_id_for_user ...@@ -21,6 +22,11 @@ from student.models import anonymous_id_for_user
from openedx.core.djangoapps.user_api.models import UserPreference from openedx.core.djangoapps.user_api.models import UserPreference
from microsite_configuration import microsite from microsite_configuration import microsite
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__)
class EmailEnrollmentState(object): class EmailEnrollmentState(object):
...@@ -203,6 +209,19 @@ def reset_student_attempts(course_id, student, module_state_key, delete_module=F ...@@ -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. 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 # Reset the student's score in the submissions API
# Currently this is used only by open assessment (ORA 2) # Currently this is used only by open assessment (ORA 2)
# We need to do this *before* retrieving the `StudentModule` model, # We need to do this *before* retrieving the `StudentModule` model,
......
...@@ -8,7 +8,6 @@ import random ...@@ -8,7 +8,6 @@ import random
import pytz import pytz
import io import io
import json import json
import os
import requests import requests
import shutil import shutil
import tempfile import tempfile
......
...@@ -13,7 +13,8 @@ from django.utils.translation import get_language ...@@ -13,7 +13,8 @@ from django.utils.translation import get_language
from django.utils.translation import override as override_language from django.utils.translation import override as override_language
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from student.tests.factories import UserFactory 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 student.models import CourseEnrollment, CourseEnrollmentAllowed
from instructor.enrollment import ( from instructor.enrollment import (
...@@ -295,31 +296,102 @@ class TestInstructorUnenrollDB(TestEnrollmentChangeBase): ...@@ -295,31 +296,102 @@ class TestInstructorUnenrollDB(TestEnrollmentChangeBase):
@attr('shard_1') @attr('shard_1')
class TestInstructorEnrollmentStudentModule(TestCase): class TestInstructorEnrollmentStudentModule(ModuleStoreTestCase):
""" Test student module manipulations. """ """ Test student module manipulations. """
def setUp(self): def setUp(self):
super(TestInstructorEnrollmentStudentModule, self).setUp() 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): def test_reset_student_attempts(self):
user = UserFactory()
msk = self.course_key.make_usage_key('dummy', 'module') msk = self.course_key.make_usage_key('dummy', 'module')
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) 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 # 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) 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) self.assertEqual(json.loads(module().state)['attempts'], 0)
def test_delete_student_attempts(self): def test_delete_student_attempts(self):
user = UserFactory()
msk = self.course_key.make_usage_key('dummy', 'module') msk = self.course_key.make_usage_key('dummy', 'module')
original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) 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(
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1) student=self.user,
reset_student_attempts(self.course_key, user, msk, delete_module=True) course_id=self.course_key,
self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0) 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): def test_delete_submission_scores(self):
user = UserFactory() user = UserFactory()
...@@ -353,6 +425,61 @@ class TestInstructorEnrollmentStudentModule(TestCase): ...@@ -353,6 +425,61 @@ class TestInstructorEnrollmentStudentModule(TestCase):
score = sub_api.get_score(student_item) score = sub_api.get_score(student_item)
self.assertIs(score, None) 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): class EnrollmentObjects(object):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment