Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
problem-builder
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
problem-builder
Commits
d191a0e7
Commit
d191a0e7
authored
Jul 14, 2017
by
Lucas Teixeira
Committed by
Matjaz Gregoric
Jul 28, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add student_view_data to the Step block and children.
parent
1d04a463
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
296 additions
and
16 deletions
+296
-16
problem_builder/answer.py
+5
-2
problem_builder/choice.py
+10
-0
problem_builder/completion.py
+15
-0
problem_builder/mcq.py
+1
-1
problem_builder/mentoring.py
+21
-1
problem_builder/mrq.py
+15
-0
problem_builder/plot.py
+21
-0
problem_builder/questionnaire.py
+0
-7
problem_builder/slider.py
+12
-0
problem_builder/step.py
+21
-0
problem_builder/step_review.py
+43
-0
problem_builder/tests/unit/test_step.py
+1
-5
problem_builder/tests/unit/test_step_builder.py
+130
-0
run_tests.py
+1
-0
No files found.
problem_builder/answer.py
View file @
d191a0e7
...
...
@@ -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'
}
...
...
problem_builder/choice.py
View file @
d191a0e7
...
...
@@ -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
))
...
...
problem_builder/completion.py
View file @
d191a0e7
...
...
@@ -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
:
...
...
problem_builder/mcq.py
View file @
d191a0e7
...
...
@@ -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.
...
...
problem_builder/mentoring.py
View file @
d191a0e7
...
...
@@ -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
,
}
problem_builder/mrq.py
View file @
d191a0e7
...
...
@@ -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
,
}
problem_builder/plot.py
View file @
d191a0e7
...
...
@@ -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.
...
...
problem_builder/questionnaire.py
View file @
d191a0e7
...
...
@@ -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
}
problem_builder/slider.py
View file @
d191a0e7
...
...
@@ -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
...
...
problem_builder/step.py
View file @
d191a0e7
...
...
@@ -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
,
}
problem_builder/step_review.py
View file @
d191a0e7
...
...
@@ -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
):
...
...
problem_builder/tests/unit/test_step.py
View file @
d191a0e7
...
...
@@ -33,11 +33,7 @@ class Parent(StepParentMixin):
pass
class
BaseClass
(
object
):
pass
class
Step
(
BaseClass
,
QuestionMixin
):
class
Step
(
QuestionMixin
):
def
__init__
(
self
):
pass
...
...
problem_builder/tests/unit/test_step_builder.py
0 → 100644
View file @
d191a0e7
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
)
run_tests.py
View file @
d191a0e7
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment