Commit d191a0e7 by Lucas Teixeira Committed by Matjaz Gregoric

Add student_view_data to the Step block and children.

parent 1d04a463
......@@ -261,14 +261,17 @@ class AnswerBlock(SubmittingXBlockMixin, AnswerMixin, QuestionMixin, StudioEdita
return {'data': {'name': uuid.uuid4().hex[:7]}}
return {'metadata': {}, 'data': {}}
def student_view_data(self):
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
"""
return {
'id': self.name,
'type': self.CATEGORY,
'weight': self.weight,
'question': self.question,
'name': self.name,
'name': self.name, # For backwards compatibility; same as 'id'
}
......
......@@ -66,6 +66,16 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlockWithPreviewMixin, XBlockWithT
status = self._(u"Out of Context") # Parent block should implement describe_choice_correctness()
return self._(u"Choice ({status})").format(status=status)
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
"""
return {
'value': self.value,
'content': self.content,
}
def mentoring_view(self, context=None):
""" Render this choice string within a mentoring block question. """
return Fragment(u'<span class="choice-text">{}</span>'.format(self.content))
......
......@@ -105,6 +105,21 @@ class CompletionBlock(
student_view = mentoring_view
preview_view = mentoring_view
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course XBlock API.
"""
return {
'id': self.name,
'type': self.CATEGORY,
'question': self.question,
'answer': self.answer,
'checked': self.student_value if self.student_value is not None else False,
'title': self.display_name_with_default,
'hide_header': self.show_title,
}
def get_last_result(self):
""" Return the current/last result in the required format """
if self.student_value is None:
......
......@@ -167,7 +167,7 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock):
self._(u"A choice value listed as correct does not exist: {choice}").format(choice=choice_name(val))
)
def student_view_data(self):
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
......
......@@ -905,7 +905,7 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin,
"""
return loader.load_scenarios_from_path('templates/xml')
def student_view_data(self):
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
......@@ -1245,3 +1245,23 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js'))
fragment.initialize_js('ProblemBuilderContainerEdit')
return fragment
def student_view_data(self, context=None):
components = []
for child_id in self.children:
child = self.runtime.get_block(child_id)
if hasattr(child, 'student_view_data'):
components.append(child.student_view_data(context))
return {
'title': self.display_name,
'show_title': self.show_title,
'weight': self.weight,
'extended_feedback': self.extended_feedback,
'active_step': self.active_step_safe,
'max_attempts': self.max_attempts,
'num_attempts': self.num_attempts,
'hide_prev_answer': True,
'components': components,
}
......@@ -188,3 +188,18 @@ class MRQBlock(QuestionnaireAbstractBlock):
add_error(self._(u"A choice value listed as required does not exist: {}").format(choice_name(val)))
for val in (ignored - all_values):
add_error(self._(u"A choice value listed as ignored does not exist: {}").format(choice_name(val)))
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
"""
return {
'id': self.name,
'title': self.display_name,
'type': self.CATEGORY,
'weight': self.weight,
'question': self.question,
'message': self.message,
'hide_results': self.hide_results,
}
......@@ -346,6 +346,27 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
fragment.initialize_js('PlotBlock')
return fragment
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course XBlock API.
"""
return {
'type': self.CATEGORY,
'title': self.display_name,
'q1_label': self.q1_label,
'q2_label': self.q2_label,
'q3_label': self.q3_label,
'q4_label': self.q4_label,
'default_claims_json': self.default_claims_json(),
'point_color_default': self.point_color_default,
'plot_label': self.plot_label,
'average_claims_json': self.average_claims_json(),
'point_color_average': self.point_color_average,
'overlay_data': self.overlay_data,
'hide_header': True,
}
def author_edit_view(self, context):
"""
Add some HTML to the author view that allows authors to add child blocks.
......
......@@ -235,10 +235,3 @@ class QuestionnaireAbstractBlock(
format_html = getattr(self.runtime, 'replace_urls', lambda html: html)
return format_html(self.message)
return ""
def student_view_data(self):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API.
"""
return {'question': self.question}
......@@ -121,6 +121,18 @@ class SliderBlock(
student_view = mentoring_view
preview_view = mentoring_view
def student_view_data(self, context=None):
return {
'id': self.name,
'type': self.CATEGORY,
'question': self.question,
'initial_value': int(self.student_value*100) if self.student_value is not None else 50,
'min_label': self.min_label,
'max_label': self.max_label,
'title': self.display_name_with_default,
'hide_header': not self.show_title,
}
def author_view(self, context):
"""
Add some HTML to the author view that allows authors to see the ID of the block, so they
......
......@@ -265,3 +265,24 @@ class MentoringStepBlock(
fragment.initialize_js('MentoringStepBlock')
return fragment
def student_view_data(self, context=None):
"""
Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course XBlock API.
"""
components = []
for child_id in self.children:
child = self.runtime.get_block(child_id)
if hasattr(child, 'student_view_data'):
components.append(child.student_view_data(context))
return {
'type': self.CATEGORY,
'title': self.display_name_with_default,
'show_title': self.show_title,
'next_button_label': self.next_button_label,
'message': self.message,
'components': components,
}
......@@ -109,6 +109,14 @@ class ConditionalMessageBlock(
return True
def student_view_data(self, context=None):
return {
'type': self.CATEGORY,
'content': self.content,
'score_condition': self.score_condition,
'num_attempts_condition': self.num_attempts_condition,
}
def student_view(self, _context=None):
""" Render this message. """
html = u'<div class="review-conditional-message">{content}</div>'.format(
......@@ -151,6 +159,14 @@ class ScoreSummaryBlock(XBlockWithTranslationServiceMixin, XBlockWithPreviewMixi
html = loader.render_template("templates/html/sb-review-score.html", context.get("score_summary", {}))
return Fragment(html)
def student_view_data(self, context=None):
context = context or {}
return {
'type': self.CATEGORY,
'score_summary': context.get('score_summary', {}),
}
embedded_student_view = student_view
def author_view(self, context=None):
......@@ -199,6 +215,15 @@ class PerQuestionFeedbackBlock(XBlockWithTranslationServiceMixin, XBlockWithPrev
html = u""
return Fragment(html)
def student_view_data(self, context=None):
context = context or {}
review_tips = context.get('score_summary', {}).get('review_tips')
return {
'type': self.CATEGORY,
'tips': review_tips
}
embedded_student_view = student_view
def author_view(self, context=None):
......@@ -279,6 +304,24 @@ class ReviewStepBlock(
return fragment
def student_view_data(self, context=None):
context = context.copy() if context else {}
components = []
for child_id in self.children:
child = self.runtime.get_block(child_id)
if hasattr(child, 'student_view_data'):
if hasattr(context, 'score_summary') and hasattr(child, 'is_applicable'):
if not child.is_applicable(context):
continue
components.append(child.student_view_data(context))
return {
'type': self.CATEGORY,
'title': self.display_name,
'components': components,
}
mentoring_view = student_view
def author_edit_view(self, context):
......
......@@ -33,11 +33,7 @@ class Parent(StepParentMixin):
pass
class BaseClass(object):
pass
class Step(BaseClass, QuestionMixin):
class Step(QuestionMixin):
def __init__(self):
pass
......
import unittest
from mock import Mock
from xblock.field_data import DictFieldData
from problem_builder.mentoring import MentoringWithExplicitStepsBlock
from problem_builder.step import MentoringStepBlock
from problem_builder.step_review import ReviewStepBlock, ConditionalMessageBlock, ScoreSummaryBlock
from .utils import BlockWithChildrenTestMixin
class TestMentoringBlock(BlockWithChildrenTestMixin, unittest.TestCase):
def test_student_view_data(self):
blocks_by_id = {}
mock_runtime = Mock(
get_block=lambda block_id: blocks_by_id[block_id],
load_block_type=lambda block: block.__class__,
id_reader=Mock(
get_definition_id=lambda block_id: block_id,
get_block_type=lambda block_id: blocks_by_id[block_id],
),
)
def make_block(block_type, data, **kwargs):
usage_id = str(make_block.id_counter)
make_block.id_counter += 1
mock_scope_ids = Mock(usage_id=usage_id)
block = block_type(
mock_runtime,
field_data=DictFieldData(data),
scope_ids=mock_scope_ids,
**kwargs
)
blocks_by_id[usage_id] = block
parent = kwargs.get('for_parent')
if parent:
parent.children.append(usage_id)
block.parent = parent.scope_ids.usage_id
return block
make_block.id_counter = 1
# Create top-level Step Builder block.
step_builder_data = {
'display_name': 'My Step Builder',
'show_title': False,
'weight': 5.0,
'max_attempts': 3,
'num_attempts': 2,
'extended_feedback': True,
'active_step': 0,
}
step_builder = make_block(MentoringWithExplicitStepsBlock, step_builder_data)
# Create a 'Step' block (as child of 'Step Builder') and add two mock children to it.
# One of the mocked children implements `student_view_data`, while the other one does not.
child_a = Mock(spec=['student_view_data'])
child_a.scope_ids = Mock(usage_id='child_a')
child_a.student_view_data.return_value = 'child_a_json'
blocks_by_id['child_a'] = child_a
child_b = Mock(spec=[])
child_b.scope_ids = Mock(usage_id='child_b')
blocks_by_id['child_b'] = child_b
step_data = {
'display_name': 'First Step',
'show_title': True,
'next_button_label': 'Next Question',
'message': 'This is the message.',
'children': [child_a.scope_ids.usage_id, child_b.scope_ids.usage_id],
}
make_block(MentoringStepBlock, step_data, for_parent=step_builder)
# Create a 'Step Review' block (as child of 'Step Builder').
review_step_data = {
'display_name': 'My Review Step',
}
review_step = make_block(ReviewStepBlock, review_step_data, for_parent=step_builder)
# Create 'Score Summary' block as child of 'Step Review'.
make_block(ScoreSummaryBlock, {}, for_parent=review_step)
# Create 'Conditional Message' block as child of 'Step Review'.
conditional_message_data = {
'content': 'This message is conditional',
'score_condition': 'perfect',
'num_attempts_condition': 'can_try_again',
}
make_block(ConditionalMessageBlock, conditional_message_data, for_parent=review_step)
expected = {
'title': step_builder_data['display_name'],
'show_title': step_builder_data['show_title'],
'weight': step_builder_data['weight'],
'max_attempts': step_builder_data['max_attempts'],
'num_attempts': step_builder_data['num_attempts'],
'extended_feedback': step_builder_data['extended_feedback'],
'active_step': step_builder_data['active_step'],
'hide_prev_answer': True,
'components': [
{
'type': 'sb-step',
'title': step_data['display_name'],
'show_title': step_data['show_title'],
'next_button_label': step_data['next_button_label'],
'message': step_data['message'],
'components': ['child_a_json'],
},
{
'type': 'sb-review-step',
'title': review_step_data['display_name'],
'components': [
{
'type': 'sb-review-score',
'score_summary': {},
},
{
'type': 'sb-conditional-message',
'content': conditional_message_data['content'],
'score_condition': conditional_message_data['score_condition'],
'num_attempts_condition': conditional_message_data['num_attempts_condition'],
},
],
},
],
}
self.assertEqual(step_builder.student_view_data(), expected)
......@@ -17,6 +17,7 @@ logging_level_overrides = {
'workbench.runtime': logging.ERROR,
}
def patch_broken_pipe_error():
"""Monkey Patch BaseServer.handle_error to not write a stacktrace to stderr on broken pipe.
This message is automatically suppressed in Django 1.8, so this monkey patch can be
......
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