Commit 0e436960 by Calen Pennington

Merge pull request #1756 from MITx/fix/vik/oe-and-progress

Fix/vik/oe and progress
parents 9c4a88b5 d5376e71
......@@ -6,14 +6,15 @@ from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor
from .x_module import XModule
from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float, List
from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, List
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple
from xmodule.open_ended_grading_classes.xblock_field_types import StringyFloat
log = logging.getLogger("mitx.courseware")
V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload",
"skip_spelling_checks", "due", "graceperiod", "max_score"]
"skip_spelling_checks", "due", "graceperiod"]
V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
"student_attempts", "ready_to_reset"]
......@@ -66,9 +67,9 @@ class CombinedOpenEndedFields(object):
due = String(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings)
max_score = Integer(help="Maximum score for the problem.", default=1, scope=Scope.settings)
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
......@@ -118,7 +119,7 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
Definition file should have one or many task blocks, a rubric block, and a prompt block:
Sample file:
<combinedopenended attempts="10000" max_score="1">
<combinedopenended attempts="10000">
<rubric>
Blah blah rubric.
</rubric>
......@@ -190,8 +191,8 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
def get_score(self):
return self.child_module.get_score()
#def max_score(self):
# return self.child_module.max_score()
def max_score(self):
return self.child_module.max_score()
def get_progress(self):
return self.child_module.get_progress()
......
......@@ -19,10 +19,6 @@ log = logging.getLogger("mitx.courseware")
# attempts specified in xml definition overrides this.
MAX_ATTEMPTS = 1
# Set maximum available number of points.
# Overriden by max_score specified in xml.
MAX_SCORE = 1
#The highest score allowed for the overall xmodule and for each rubric point
MAX_SCORE_ALLOWED = 50
......@@ -88,7 +84,7 @@ class CombinedOpenEndedV1Module():
Definition file should have one or many task blocks, a rubric block, and a prompt block:
Sample file:
<combinedopenended attempts="10000" max_score="1">
<combinedopenended attempts="10000">
<rubric>
Blah blah rubric.
</rubric>
......@@ -153,13 +149,9 @@ class CombinedOpenEndedV1Module():
raise
self.display_due_date = self.timeinfo.display_due_date
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
self._max_score = self.instance_state.get('max_score', MAX_SCORE)
self.rubric_renderer = CombinedOpenEndedRubric(system, True)
rubric_string = stringify_children(definition['rubric'])
self.rubric_renderer.check_if_rubric_is_parseable(rubric_string, location, MAX_SCORE_ALLOWED, self._max_score)
self._max_score = self.rubric_renderer.check_if_rubric_is_parseable(rubric_string, location, MAX_SCORE_ALLOWED)
#Static data is passed to the child modules to render
self.static_data = {
......
......@@ -79,7 +79,7 @@ class CombinedOpenEndedRubric(object):
raise RubricParsingError(error_message)
return {'success': success, 'html': html, 'rubric_scores': rubric_scores}
def check_if_rubric_is_parseable(self, rubric_string, location, max_score_allowed, max_score):
def check_if_rubric_is_parseable(self, rubric_string, location, max_score_allowed):
rubric_dict = self.render_rubric(rubric_string)
success = rubric_dict['success']
rubric_feedback = rubric_dict['html']
......@@ -101,12 +101,7 @@ class CombinedOpenEndedRubric(object):
log.error(error_message)
raise RubricParsingError(error_message)
if int(total) != int(max_score):
#This is a staff_facing_error
error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}. Contact the learning sciences group for assistance.".format(
max_score, location, total)
log.error(error_msg)
raise RubricParsingError(error_msg)
return int(total)
def extract_categories(self, element):
'''
......
from xblock.core import Integer, Float
class StringyFloat(Float):
"""
A model type that converts from string to floats when reading from json
"""
def from_json(self, value):
try:
return float(value)
except:
return None
......@@ -13,6 +13,7 @@ from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from .timeinfo import TimeInfo
from xblock.core import Object, Integer, Boolean, String, Scope
from xmodule.open_ended_grading_classes.xblock_field_types import StringyFloat
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
......@@ -28,13 +29,18 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
class PeerGradingFields(object):
use_for_single_location = Boolean(help="Whether to use this for a single location or as a panel.", default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION, scope=Scope.settings)
is_graded = Boolean(help="Whether or not this module is scored.",default=IS_GRADED, scope=Scope.settings)
use_for_single_location = Boolean(help="Whether to use this for a single location or as a panel.",
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION,
scope=Scope.settings)
is_graded = Boolean(help="Whether or not this module is scored.", default=IS_GRADED, scope=Scope.settings)
display_due_date_string = String(help="Due date that should be displayed.", default=None, scope=Scope.settings)
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE, scope=Scope.settings)
student_data_for_location = Object(help="Student data for a given peer grading problem.", default=json.dumps({}),scope=Scope.student_state)
max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE,
scope=Scope.settings)
student_data_for_location = Object(help="Student data for a given peer grading problem.", default=json.dumps({}),
scope=Scope.student_state)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
class PeerGradingModule(PeerGradingFields, XModule):
......
......@@ -2,12 +2,12 @@
metadata:
display_name: Open Ended Response
max_attempts: 1
max_score: 1
is_graded: False
version: 1
display_name: Open Ended Response
skip_spelling_checks: False
accept_file_upload: False
weight: ""
data: |
<combinedopenended>
<rubric>
......
......@@ -6,6 +6,7 @@ metadata:
link_to_location: None
is_graded: False
max_grade: 1
weight: ""
data: |
<peergrading>
</peergrading>
......
......@@ -5,11 +5,15 @@ import unittest
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
from xmodule.open_ended_grading_classes.open_ended_module import OpenEndedModule
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module
from xmodule.combined_open_ended_module import CombinedOpenEndedModule
from xmodule.modulestore import Location
from lxml import etree
import capa.xqueue_interface as xqueue_interface
from datetime import datetime
import logging
log = logging.getLogger(__name__)
from . import test_system
......@@ -57,7 +61,7 @@ class OpenEndedChildTest(unittest.TestCase):
def setUp(self):
self.test_system = test_system()
self.openendedchild = OpenEndedChild(self.test_system, self.location,
self.definition, self.descriptor, self.static_data, self.metadata)
self.definition, self.descriptor, self.static_data, self.metadata)
def test_latest_answer_empty(self):
......@@ -183,10 +187,12 @@ class OpenEndedModuleTest(unittest.TestCase):
self.test_system.location = self.location
self.mock_xqueue = MagicMock()
self.mock_xqueue.send_to_queue.return_value = (None, "Message")
def constructed_callback(dispatch="score_update"):
return dispatch
self.test_system.xqueue = {'interface': self.mock_xqueue, 'construct_callback': constructed_callback, 'default_queuename': 'testqueue',
self.test_system.xqueue = {'interface': self.mock_xqueue, 'construct_callback': constructed_callback,
'default_queuename': 'testqueue',
'waittime': 1}
self.openendedmodule = OpenEndedModule(self.test_system, self.location,
self.definition, self.descriptor, self.static_data, self.metadata)
......@@ -281,7 +287,18 @@ class OpenEndedModuleTest(unittest.TestCase):
class CombinedOpenEndedModuleTest(unittest.TestCase):
location = Location(["i4x", "edX", "open_ended", "combinedopenended",
"SampleQuestion"])
definition_template = """
<combinedopenended attempts="10000">
{rubric}
{prompt}
<task>
{task1}
</task>
<task>
{task2}
</task>
</combinedopenended>
"""
prompt = "<prompt>This is a question prompt</prompt>"
rubric = '''<rubric><rubric>
<category>
......@@ -335,10 +352,15 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
</openendedparam>
</openended>'''
definition = {'prompt': etree.XML(prompt), 'rubric': etree.XML(rubric), 'task_xml': [task_xml1, task_xml2]}
descriptor = Mock()
full_definition = definition_template.format(prompt=prompt, rubric=rubric, task1=task_xml1, task2=task_xml2)
descriptor = Mock(data=full_definition)
test_system = test_system()
combinedoe_container = CombinedOpenEndedModule(test_system,
location,
descriptor,
model_data={'data': full_definition, 'weight' : '1'})
def setUp(self):
self.test_system = test_system()
# TODO: this constructor call is definitely wrong, but neither branch
# of the merge matches the module constructor. Someone (Vik?) should fix this.
self.combinedoe = CombinedOpenEndedV1Module(self.test_system,
......@@ -368,3 +390,19 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
changed = self.combinedoe.update_task_states()
self.assertTrue(changed)
def test_get_max_score(self):
changed = self.combinedoe.update_task_states()
self.combinedoe.state = "done"
self.combinedoe.is_scored = True
max_score = self.combinedoe.max_score()
self.assertEqual(max_score, 1)
def test_container_get_max_score(self):
#The progress view requires that this function be exposed
max_score = self.combinedoe_container.max_score()
self.assertEqual(max_score, None)
def test_container_weight(self):
weight = self.combinedoe_container.weight
self.assertEqual(weight,1)
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