Commit 6b211933 by Matjaz Gregoric Committed by GitHub

Merge pull request #98 from open-craft/mtyaka/legacy-item-state

Ensure old problems work on new version.
parents e2600bc5 c67d91cd
...@@ -638,25 +638,10 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -638,25 +638,10 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
def _get_user_state(self): def _get_user_state(self):
""" Get all user-specific data, and any applicable feedback """ """ Get all user-specific data, and any applicable feedback """
item_state = self._get_item_state() item_state = self._get_item_state()
for item_id, item in item_state.iteritems(): # In assessment mode, we do not want to leak the correctness info for individual items to the frontend,
# If information about zone is missing # so we remove "correct" from all items when in assessment mode.
# (because problem was completed before a11y enhancements were implemented), if self.mode == self.ASSESSMENT_MODE:
# deduce zone in which item is placed from definition: for item in item_state.values():
if item.get('zone') is None:
valid_zones = self._get_item_zones(int(item_id))
if valid_zones:
# If we get to this point, then the item was placed prior to support for
# multiple correct zones being added. As a result, it can only be correct
# on a single zone, and so we can trust that the item was placed on the
# zone with index 0.
item['zone'] = valid_zones[0]
else:
item['zone'] = 'unknown'
# In assessment mode, if item is placed correctly and than the page is refreshed, "correct"
# will spill to the frontend, making item "disabled", thus allowing students to obtain answer by trial
# and error + refreshing the page. In order to avoid that, we remove "correct" from an item here
if self.mode == self.ASSESSMENT_MODE:
del item["correct"] del item["correct"]
overall_feedback_msgs, __ = self._get_feedback() overall_feedback_msgs, __ = self._get_feedback()
...@@ -679,14 +664,35 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -679,14 +664,35 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
""" """
# IMPORTANT: this method should always return a COPY of self.item_state - it is called from get_user_state # IMPORTANT: this method should always return a COPY of self.item_state - it is called from get_user_state
# handler and manipulated there to hide correctness of items placed # handler and the data it returns is manipulated there to hide correctness of items placed.
state = {} state = {}
for item_id, item in self.item_state.iteritems(): for item_id, raw_item in self.item_state.iteritems():
if isinstance(item, dict): if isinstance(raw_item, dict):
state[item_id] = item.copy() # items are manipulated in _get_user_state, so we protect actual data # Items are manipulated in _get_user_state, so we protect actual data.
item = copy.deepcopy(raw_item)
else: else:
state[item_id] = {'top': item[0], 'left': item[1]} item = {'top': raw_item[0], 'left': raw_item[1]}
# If information about zone is missing
# (because problem was completed before a11y enhancements were implemented),
# deduce zone in which item is placed from definition:
if item.get('zone') is None:
valid_zones = self._get_item_zones(int(item_id))
if valid_zones:
# If we get to this point, then the item was placed prior to support for
# multiple correct zones being added. As a result, it can only be correct
# on a single zone, and so we can trust that the item was placed on the
# zone with index 0.
item['zone'] = valid_zones[0]
else:
item['zone'] = 'unknown'
# If correctness information is missing
# (because problem was completed before assessment mode was implemented),
# assume the item is in correct zone (in standard mode, only items placed
# into correct zone are stored in item state).
if item.get('correct') is None:
item['correct'] = True
state[item_id] = item
return state return state
......
...@@ -109,6 +109,34 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -109,6 +109,34 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
self.assertTrue(self.block.completed) self.assertTrue(self.block.completed)
assert_user_state_empty() assert_user_state_empty()
def test_legacy_state_support(self):
"""
The form of items stored in user item_state has changed several times.
This test makes sure that legacy forms are properly converted to compatible format.
"""
self.assertEqual(self.block.item_state, {})
self.assertEqual(self.call_handler('get_user_state')['items'], {})
self.block.item_state = {
# Legacy tuple (top, left) representation.
'0': [60, 20],
# Legacy dict with absolute values and no correctness or zone info.
'1': {'top': 45, 'left': 99},
# Legacy dict with no correctness info.
'2': {'x_percent': '99%', 'y_percent': '95%', 'zone': BOTTOM_ZONE_ID},
# Current dict form.
'3': {'x_percent': '67%', 'y_percent': '80%', 'zone': BOTTOM_ZONE_ID, 'correct': False},
}
self.block.save()
self.assertEqual(self.call_handler('get_user_state')['items'], {
# Legacy top/left values are converted to x/y percentage on the client.
'0': {'top': 60, 'left': 20, 'correct': True, 'zone': TOP_ZONE_ID},
'1': {'top': 45, 'left': 99, 'correct': True, 'zone': MIDDLE_ZONE_ID},
'2': {'x_percent': '99%', 'y_percent': '95%', 'correct': True, 'zone': BOTTOM_ZONE_ID},
'3': {'x_percent': '67%', 'y_percent': '80%', 'correct': False, "zone": BOTTOM_ZONE_ID},
})
def test_studio_submit(self): def test_studio_submit(self):
body = { body = {
'display_name': "Test Drag & Drop", 'display_name': "Test Drag & Drop",
......
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