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
b3ec504d
Commit
b3ec504d
authored
Jul 30, 2017
by
Braden MacDonald
Committed by
GitHub
Jul 30, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #151 from open-craft/lucas/step-builder-api-data
Add student_view_data to step builder
parents
fa1bcb6a
d191a0e7
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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -906,7 +906,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.
...
...
@@ -1247,3 +1247,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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
...
...
@@ -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 @
b3ec504d
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 @
b3ec504d
...
...
@@ -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