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 ...@@ -261,14 +261,17 @@ class AnswerBlock(SubmittingXBlockMixin, AnswerMixin, QuestionMixin, StudioEdita
return {'data': {'name': uuid.uuid4().hex[:7]}} return {'data': {'name': uuid.uuid4().hex[:7]}}
return {'metadata': {}, 'data': {}} 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, Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API. retrievable from the Course Block API.
""" """
return { return {
'id': self.name,
'type': self.CATEGORY,
'weight': self.weight,
'question': self.question, 'question': self.question,
'name': self.name, 'name': self.name, # For backwards compatibility; same as 'id'
} }
......
...@@ -66,6 +66,16 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlockWithPreviewMixin, XBlockWithT ...@@ -66,6 +66,16 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlockWithPreviewMixin, XBlockWithT
status = self._(u"Out of Context") # Parent block should implement describe_choice_correctness() status = self._(u"Out of Context") # Parent block should implement describe_choice_correctness()
return self._(u"Choice ({status})").format(status=status) 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): def mentoring_view(self, context=None):
""" Render this choice string within a mentoring block question. """ """ Render this choice string within a mentoring block question. """
return Fragment(u'<span class="choice-text">{}</span>'.format(self.content)) return Fragment(u'<span class="choice-text">{}</span>'.format(self.content))
......
...@@ -105,6 +105,21 @@ class CompletionBlock( ...@@ -105,6 +105,21 @@ class CompletionBlock(
student_view = mentoring_view student_view = mentoring_view
preview_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): def get_last_result(self):
""" Return the current/last result in the required format """ """ Return the current/last result in the required format """
if self.student_value is None: if self.student_value is None:
......
...@@ -167,7 +167,7 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock): ...@@ -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)) 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, Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API. retrievable from the Course Block API.
......
...@@ -905,7 +905,7 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin, ...@@ -905,7 +905,7 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin,
""" """
return loader.load_scenarios_from_path('templates/xml') 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, Returns a JSON representation of the student_view of this XBlock,
retrievable from the Course Block API. retrievable from the Course Block API.
...@@ -1245,3 +1245,23 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes ...@@ -1245,3 +1245,23 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js'))
fragment.initialize_js('ProblemBuilderContainerEdit') fragment.initialize_js('ProblemBuilderContainerEdit')
return fragment 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): ...@@ -188,3 +188,18 @@ class MRQBlock(QuestionnaireAbstractBlock):
add_error(self._(u"A choice value listed as required does not exist: {}").format(choice_name(val))) add_error(self._(u"A choice value listed as required does not exist: {}").format(choice_name(val)))
for val in (ignored - all_values): for val in (ignored - all_values):
add_error(self._(u"A choice value listed as ignored does not exist: {}").format(choice_name(val))) 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 ...@@ -346,6 +346,27 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
fragment.initialize_js('PlotBlock') fragment.initialize_js('PlotBlock')
return fragment 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): def author_edit_view(self, context):
""" """
Add some HTML to the author view that allows authors to add child blocks. Add some HTML to the author view that allows authors to add child blocks.
......
...@@ -235,10 +235,3 @@ class QuestionnaireAbstractBlock( ...@@ -235,10 +235,3 @@ class QuestionnaireAbstractBlock(
format_html = getattr(self.runtime, 'replace_urls', lambda html: html) format_html = getattr(self.runtime, 'replace_urls', lambda html: html)
return format_html(self.message) return format_html(self.message)
return "" 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( ...@@ -121,6 +121,18 @@ class SliderBlock(
student_view = mentoring_view student_view = mentoring_view
preview_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): def author_view(self, context):
""" """
Add some HTML to the author view that allows authors to see the ID of the block, so they 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( ...@@ -265,3 +265,24 @@ class MentoringStepBlock(
fragment.initialize_js('MentoringStepBlock') fragment.initialize_js('MentoringStepBlock')
return fragment 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( ...@@ -109,6 +109,14 @@ class ConditionalMessageBlock(
return True 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): def student_view(self, _context=None):
""" Render this message. """ """ Render this message. """
html = u'<div class="review-conditional-message">{content}</div>'.format( html = u'<div class="review-conditional-message">{content}</div>'.format(
...@@ -151,6 +159,14 @@ class ScoreSummaryBlock(XBlockWithTranslationServiceMixin, XBlockWithPreviewMixi ...@@ -151,6 +159,14 @@ class ScoreSummaryBlock(XBlockWithTranslationServiceMixin, XBlockWithPreviewMixi
html = loader.render_template("templates/html/sb-review-score.html", context.get("score_summary", {})) html = loader.render_template("templates/html/sb-review-score.html", context.get("score_summary", {}))
return Fragment(html) 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 embedded_student_view = student_view
def author_view(self, context=None): def author_view(self, context=None):
...@@ -199,6 +215,15 @@ class PerQuestionFeedbackBlock(XBlockWithTranslationServiceMixin, XBlockWithPrev ...@@ -199,6 +215,15 @@ class PerQuestionFeedbackBlock(XBlockWithTranslationServiceMixin, XBlockWithPrev
html = u"" html = u""
return Fragment(html) 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 embedded_student_view = student_view
def author_view(self, context=None): def author_view(self, context=None):
...@@ -279,6 +304,24 @@ class ReviewStepBlock( ...@@ -279,6 +304,24 @@ class ReviewStepBlock(
return fragment 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 mentoring_view = student_view
def author_edit_view(self, context): def author_edit_view(self, context):
......
...@@ -33,11 +33,7 @@ class Parent(StepParentMixin): ...@@ -33,11 +33,7 @@ class Parent(StepParentMixin):
pass pass
class BaseClass(object): class Step(QuestionMixin):
pass
class Step(BaseClass, QuestionMixin):
def __init__(self): def __init__(self):
pass 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 = { ...@@ -17,6 +17,7 @@ logging_level_overrides = {
'workbench.runtime': logging.ERROR, 'workbench.runtime': logging.ERROR,
} }
def patch_broken_pipe_error(): def patch_broken_pipe_error():
"""Monkey Patch BaseServer.handle_error to not write a stacktrace to stderr on broken pipe. """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 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