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
03621b1a
Commit
03621b1a
authored
May 13, 2015
by
Eugeny Kolpakov
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #32 from open-craft/merge-candidate
Merging edx-release into master
parents
5e00f92d
85d12cce
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
159 additions
and
28 deletions
+159
-28
problem_builder/dashboard.py
+109
-16
problem_builder/mentoring.py
+7
-1
problem_builder/public/css/dashboard.css
+13
-1
problem_builder/public/js/mentoring.js
+2
-1
problem_builder/public/js/mentoring_standard_view.js
+1
-1
problem_builder/templates/html/dashboard.html
+11
-5
problem_builder/templates/html/dashboard_report.html
+14
-0
problem_builder/templates/html/mentoring.html
+1
-1
problem_builder/tests/integration/test_dashboard.py
+0
-0
problem_builder/tests/integration/xml_templates/dashboard.xml
+1
-2
No files found.
problem_builder/dashboard.py
View file @
03621b1a
...
@@ -30,13 +30,14 @@ import ast
...
@@ -30,13 +30,14 @@ import ast
import
json
import
json
import
logging
import
logging
import
operator
as
op
import
operator
as
op
from
django.template.defaultfilters
import
floatformat
from
.dashboard_visual
import
DashboardVisualData
from
.dashboard_visual
import
DashboardVisualData
from
.mcq
import
MCQBlock
from
.mcq
import
MCQBlock
from
.sub_api
import
sub_api
from
.sub_api
import
sub_api
from
lazy
import
lazy
from
lazy
import
lazy
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
List
,
String
from
xblock.fields
import
Scope
,
List
,
String
,
Boolean
,
Dict
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblock.validation
import
ValidationMessage
from
xblock.validation
import
ValidationMessage
from
xblockutils.helpers
import
child_isinstance
from
xblockutils.helpers
import
child_isinstance
...
@@ -172,6 +173,20 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -172,6 +173,20 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
)
.
format
(
example_here
=
'["2754b8afc03a439693b9887b6f1d9e36", "215028f7df3d4c68b14fb5fea4da7053"]'
),
)
.
format
(
example_here
=
'["2754b8afc03a439693b9887b6f1d9e36", "215028f7df3d4c68b14fb5fea4da7053"]'
),
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
)
)
exclude_questions
=
Dict
(
display_name
=
_
(
"Questions to be hidden"
),
help
=
_
(
"Optional rules to exclude specific questions both from displaying in dashboard and from the calculated "
"average. Rules must start with the url_name of a mentoring block, followed by list of question numbers "
"to exclude. Rule set must be in JSON format. Question numbers are one-based (the first question being "
"number 1). Must be in JSON format. Examples: {examples_here}"
)
.
format
(
examples_here
=
'{"2754b8afc03a439693b9887b6f1d9e36":[1,2], "215028f7df3d4c68b14fb5fea4da7053":[1,5]}'
),
scope
=
Scope
.
content
,
multiline_editor
=
True
,
resettable_editor
=
False
,
)
color_rules
=
String
(
color_rules
=
String
(
display_name
=
_
(
"Color Coding Rules"
),
display_name
=
_
(
"Color Coding Rules"
),
help
=
_
(
help
=
_
(
...
@@ -207,8 +222,27 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -207,8 +222,27 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
),
),
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
)
)
average_labels
=
Dict
(
display_name
=
_
(
"Label for average value"
),
help
=
_
(
"This settings allows overriding label for the calculated average per mentoring block. Must be in JSON "
"format. Examples: {examples_here}."
)
.
format
(
examples_here
=
'{"2754b8afc03a439693b9887b6f1d9e36": "Avg.", "215028f7df3d4c68b14fb5fea4da7053": "Mean"}'
),
scope
=
Scope
.
content
,
)
show_numbers
=
Boolean
(
display_name
=
_
(
"Display values"
),
default
=
True
,
help
=
_
(
"Toggles if numeric values are displayed"
),
scope
=
Scope
.
content
)
editable_fields
=
(
'display_name'
,
'mentoring_ids'
,
'color_rules'
,
'visual_rules'
,
'visual_title'
,
'visual_desc'
)
editable_fields
=
(
'display_name'
,
'mentoring_ids'
,
'exclude_questions'
,
'average_labels'
,
'show_numbers'
,
'color_rules'
,
'visual_rules'
,
'visual_title'
,
'visual_desc'
)
css_path
=
'public/css/dashboard.css'
css_path
=
'public/css/dashboard.css'
js_path
=
'public/js/dashboard.js'
js_path
=
'public/js/dashboard.js'
...
@@ -321,6 +355,12 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -321,6 +355,12 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
except
Exception
:
except
Exception
:
return
""
return
""
def
_get_problem_questions
(
self
,
mentoring_block
):
""" Generator returning only children of specified block that are MCQs """
for
child_id
in
mentoring_block
.
children
:
if
child_isinstance
(
mentoring_block
,
child_id
,
MCQBlock
):
yield
child_id
def
student_view
(
self
,
context
=
None
):
# pylint: disable=unused-argument
def
student_view
(
self
,
context
=
None
):
# pylint: disable=unused-argument
"""
"""
Standard view of this XBlock.
Standard view of this XBlock.
...
@@ -336,20 +376,36 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -336,20 +376,36 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
'display_name'
:
mentoring_block
.
display_name
,
'display_name'
:
mentoring_block
.
display_name
,
'mcqs'
:
[]
'mcqs'
:
[]
}
}
for
child_id
in
mentoring_block
.
children
:
try
:
if
child_isinstance
(
mentoring_block
,
child_id
,
MCQBlock
):
hide_questions
=
self
.
exclude_questions
.
get
(
mentoring_block
.
url_name
,
[])
# Get the student's submitted answer to this MCQ from the submissions API:
except
Exception
:
# pylint: disable=broad-except-clause
mcq_block
=
self
.
runtime
.
get_block
(
child_id
)
log
.
exception
(
"Cannot parse exclude_questions setting - probably malformed:
%
s"
,
self
.
exclude_questions
)
mcq_submission_key
=
self
.
_get_submission_key
(
child_id
)
hide_questions
=
[]
try
:
value
=
sub_api
.
get_submissions
(
mcq_submission_key
,
limit
=
1
)[
0
][
"answer"
]
for
question_number
,
child_id
in
enumerate
(
self
.
_get_problem_questions
(
mentoring_block
),
1
):
except
IndexError
:
try
:
value
=
None
if
question_number
in
hide_questions
:
block
[
'mcqs'
]
.
append
({
continue
"display_name"
:
mcq_block
.
display_name_with_default
,
except
TypeError
:
"value"
:
value
,
log
.
exception
(
"color"
:
self
.
color_for_value
(
value
)
if
value
is
not
None
else
None
,
"Cannot check question number - expected list of ints got:
%
s"
,
})
hide_questions
)
# Get the student's submitted answer to this MCQ from the submissions API:
mcq_block
=
self
.
runtime
.
get_block
(
child_id
)
mcq_submission_key
=
self
.
_get_submission_key
(
child_id
)
try
:
value
=
sub_api
.
get_submissions
(
mcq_submission_key
,
limit
=
1
)[
0
][
"answer"
]
except
IndexError
:
value
=
None
block
[
'mcqs'
]
.
append
({
"display_name"
:
mcq_block
.
display_name_with_default
,
"value"
:
value
,
"accessible_value"
:
_
(
"Score: {score}"
)
.
format
(
score
=
value
)
if
value
else
_
(
"No value yet"
),
"color"
:
self
.
color_for_value
(
value
)
if
value
is
not
None
else
None
,
})
# If the values are numeric, display an average:
# If the values are numeric, display an average:
numeric_values
=
[
numeric_values
=
[
float
(
mcq
[
'value'
])
for
mcq
in
block
[
'mcqs'
]
float
(
mcq
[
'value'
])
for
mcq
in
block
[
'mcqs'
]
...
@@ -358,6 +414,11 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -358,6 +414,11 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
if
numeric_values
:
if
numeric_values
:
average_value
=
sum
(
numeric_values
)
/
len
(
numeric_values
)
average_value
=
sum
(
numeric_values
)
/
len
(
numeric_values
)
block
[
'average'
]
=
average_value
block
[
'average'
]
=
average_value
# average block is shown only if average value exists, so accessible text for no data is not required
block
[
'accessible_average'
]
=
_
(
"Score: {score}"
)
.
format
(
score
=
floatformat
(
average_value
)
)
block
[
'average_label'
]
=
self
.
average_labels
.
get
(
mentoring_block
.
url_name
,
_
(
"Average"
))
block
[
'has_average'
]
=
True
block
[
'has_average'
]
=
True
block
[
'average_color'
]
=
self
.
color_for_value
(
average_value
)
block
[
'average_color'
]
=
self
.
color_for_value
(
average_value
)
blocks
.
append
(
block
)
blocks
.
append
(
block
)
...
@@ -384,6 +445,7 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -384,6 +445,7 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
'blocks'
:
blocks
,
'blocks'
:
blocks
,
'display_name'
:
self
.
display_name
,
'display_name'
:
self
.
display_name
,
'visual_repr'
:
visual_repr
,
'visual_repr'
:
visual_repr
,
'show_numbers'
:
self
.
show_numbers
,
})
})
fragment
=
Fragment
(
html
)
fragment
=
Fragment
(
html
)
...
@@ -406,6 +468,37 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -406,6 +468,37 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
except
InvalidUrlName
as
e
:
except
InvalidUrlName
as
e
:
add_error
(
_
(
u'Invalid block url_name given: "{bad_url_name}"'
)
.
format
(
bad_url_name
=
unicode
(
e
)))
add_error
(
_
(
u'Invalid block url_name given: "{bad_url_name}"'
)
.
format
(
bad_url_name
=
unicode
(
e
)))
if
data
.
exclude_questions
:
for
key
,
value
in
data
.
exclude_questions
.
iteritems
():
if
not
isinstance
(
value
,
list
):
add_error
(
_
(
u"'Questions to be hidden' is malformed: value for key {key} is {value}, "
u"expected list of integers"
)
.
format
(
key
=
key
,
value
=
value
)
)
if
key
not
in
data
.
mentoring_ids
:
add_error
(
_
(
u"'Questions to be hidden' is malformed: mentoring url_name {url_name} "
u"is not added to Dashboard"
)
.
format
(
url_name
=
key
)
)
if
data
.
average_labels
:
for
key
,
value
in
data
.
average_labels
.
iteritems
():
if
not
isinstance
(
value
,
basestring
):
add_error
(
_
(
u"'Label for average value' is malformed: value for key {key} is {value}, expected string"
)
.
format
(
key
=
key
,
value
=
value
)
)
if
key
not
in
data
.
mentoring_ids
:
add_error
(
_
(
u"'Label for average value' is malformed: mentoring url_name {url_name} "
u"is not added to Dashboard"
)
.
format
(
url_name
=
key
)
)
if
data
.
color_rules
:
if
data
.
color_rules
:
try
:
try
:
self
.
parse_color_rules_str
(
data
.
color_rules
,
ignore_errors
=
False
)
self
.
parse_color_rules_str
(
data
.
color_rules
,
ignore_errors
=
False
)
...
...
problem_builder/mentoring.py
View file @
03621b1a
...
@@ -142,6 +142,12 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
...
@@ -142,6 +142,12 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
default
=
_
(
"Mentoring Questions"
),
default
=
_
(
"Mentoring Questions"
),
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
feedback_label
=
String
(
display_name
=
_
(
"Feedback Header"
),
help
=
_
(
"Header for feedback messages"
),
default
=
_
(
"Feedback"
),
scope
=
Scope
.
content
)
# User state
# User state
attempted
=
Boolean
(
attempted
=
Boolean
(
...
@@ -187,7 +193,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
...
@@ -187,7 +193,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
editable_fields
=
(
editable_fields
=
(
'display_name'
,
'mode'
,
'followed_by'
,
'max_attempts'
,
'enforce_dependency'
,
'display_name'
,
'mode'
,
'followed_by'
,
'max_attempts'
,
'enforce_dependency'
,
'display_submit'
,
'weight'
,
'extended_feedback'
'display_submit'
,
'
feedback_label'
,
'
weight'
,
'extended_feedback'
)
)
icon_class
=
'problem'
icon_class
=
'problem'
has_score
=
True
has_score
=
True
...
...
problem_builder/public/css/dashboard.css
View file @
03621b1a
.pb-dashboard
table
{
.pb-dashboard
table
{
max-width
:
800px
;
max-width
:
800px
;
width
:
700px
;
table-layout
:
auto
;
border-collapse
:
collapse
;
border-collapse
:
collapse
;
margin-left
:
auto
;
margin-right
:
auto
;
margin-bottom
:
15px
;
margin-bottom
:
15px
;
}
}
...
@@ -9,6 +13,10 @@
...
@@ -9,6 +13,10 @@
font-weight
:
bold
;
font-weight
:
bold
;
}
}
.pb-dashboard
.avg-row
.desc
{
font-weight
:
600
;
}
.pb-dashboard
table
td
,
.pb-dashboard
table
tbody
th
{
.pb-dashboard
table
td
,
.pb-dashboard
table
tbody
th
{
border-top
:
1px
solid
#ddd
;
border-top
:
1px
solid
#ddd
;
border-bottom
:
1px
solid
#ddd
;
border-bottom
:
1px
solid
#ddd
;
...
@@ -24,9 +32,13 @@
...
@@ -24,9 +32,13 @@
min-width
:
4em
;
min-width
:
4em
;
text-align
:
right
;
text-align
:
right
;
padding-right
:
5px
;
padding-right
:
5px
;
border-right
:
0.6
em
solid
transparent
;
border-right
:
2
em
solid
transparent
;
}
}
.pb-dashboard
table
.avg-row
td
.desc
{
.pb-dashboard
table
.avg-row
td
.desc
{
font-style
:
italic
;
font-style
:
italic
;
}
}
.pb-dashboard-visual
{
text-align
:
center
;
}
problem_builder/public/js/mentoring.js
View file @
03621b1a
...
@@ -22,7 +22,8 @@ function MentoringBlock(runtime, element) {
...
@@ -22,7 +22,8 @@ function MentoringBlock(runtime, element) {
hideAllSteps
:
hideAllSteps
,
hideAllSteps
:
hideAllSteps
,
step
:
step
,
step
:
step
,
steps
:
steps
,
steps
:
steps
,
publish_event
:
publish_event
publish_event
:
publish_event
,
data
:
data
};
};
function
publish_event
(
data
)
{
function
publish_event
(
data
)
{
...
...
problem_builder/public/js/mentoring_standard_view.js
View file @
03621b1a
...
@@ -25,7 +25,7 @@ function MentoringStandardView(runtime, element, mentoring) {
...
@@ -25,7 +25,7 @@ function MentoringStandardView(runtime, element, mentoring) {
// Messages should only be displayed upon hitting 'submit', not on page reload
// Messages should only be displayed upon hitting 'submit', not on page reload
mentoring
.
setContent
(
messagesDOM
,
response
.
message
);
mentoring
.
setContent
(
messagesDOM
,
response
.
message
);
if
(
messagesDOM
.
html
().
trim
())
{
if
(
messagesDOM
.
html
().
trim
())
{
messagesDOM
.
prepend
(
'<div class="title1">'
+
gettext
(
'Feedback'
)
+
'</div>'
);
messagesDOM
.
prepend
(
'<div class="title1">'
+
mentoring
.
data
.
feedback_label
+
'</div>'
);
messagesDOM
.
show
();
messagesDOM
.
show
();
}
}
}
}
...
...
problem_builder/templates/html/dashboard.html
View file @
03621b1a
...
@@ -42,16 +42,22 @@
...
@@ -42,16 +42,22 @@
{% for mcq in block.mcqs %}
{% for mcq in block.mcqs %}
<tr>
<tr>
<th
class=
"desc"
>
{{ mcq.display_name }}
</th>
<th
class=
"desc"
>
{{ mcq.display_name }}
</th>
<td
class=
"value"
{%
if
mcq
.
color
%}
style=
"border-right-color: {{mcq.color}};"
{%
endif
%}
>
<td
class=
"value"
{%
if
mcq
.
color
%}
style=
"border-right-color: {{mcq.color}};"
{%
endif
%}
>
{% if mcq.value %}{{ mcq.value }}{% endif %}
{% if mcq.value and show_numbers %}
<span
aria-hidden=
"true"
>
{{ mcq.value }}
</span>
{% endif %}
<span
class=
"sr"
>
{{ mcq.accessible_value }}
</span>
</td>
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
{% if block.has_average %}
{% if block.has_average %}
<tr
class=
"avg-row"
>
<tr
class=
"avg-row"
>
<th
class=
"desc"
>
{% trans "Average" %}
</th>
<th
class=
"desc"
>
{{ block.average_label }}
</th>
<td
class=
"value"
{%
if
block
.
average_color
%}
style=
"border-right-color: {{block.average_color}};"
{%
endif
%}
>
<td
class=
"value"
{%
if
block
.
average_color
%}
style=
"border-right-color: {{block.average_color}};"
{%
endif
%}
>
{{ block.average|floatformat }}
{% if show_numbers %}
<span
aria-hidden=
"true"
>
{{ block.average|floatformat }}
</span>
{% endif %}
<span
class=
"sr"
>
{{ block.accessible_average }}
</span>
</td>
</td>
</tr>
</tr>
{% endif %}
{% endif %}
...
...
problem_builder/templates/html/dashboard_report.html
View file @
03621b1a
...
@@ -9,6 +9,20 @@
...
@@ -9,6 +9,20 @@
body
{
body
{
font-family
:
'Open Sans'
,
'Helvetica Neue'
,
Helvetica
,
Arial
,
sans-serif
;
font-family
:
'Open Sans'
,
'Helvetica Neue'
,
Helvetica
,
Arial
,
sans-serif
;
}
}
.pb-dashboard
table
{
text-align
:
left
;
}
/* screen reader class from edx-platform */
.sr
{
border
:
0
;
clip
:
rect
(
1px
1px
1px
1px
);
height
:
1px
;
margin
:
-1px
;
overflow
:
hidden
;
padding
:
0
;
position
:
absolute
;
width
:
1px
;
}
{
{css
}
}
{
{css
}
}
</style>
</style>
</head>
</head>
...
...
problem_builder/templates/html/mentoring.html
View file @
03621b1a
{% load i18n %}
{% load i18n %}
<div
class=
"mentoring themed-xblock"
data-mode=
"{{ self.mode }}"
data-step=
"{{ self.step }}"
>
<div
class=
"mentoring themed-xblock"
data-mode=
"{{ self.mode }}"
data-step=
"{{ self.step }}"
data-feedback_label=
"{{ self.feedback_label}}"
>
<div
class=
"missing-dependency warning"
data-missing=
"{{ self.has_missing_dependency }}"
>
<div
class=
"missing-dependency warning"
data-missing=
"{{ self.has_missing_dependency }}"
>
{% with url=missing_dependency_url|safe %}
{% with url=missing_dependency_url|safe %}
{% blocktrans with link_start="
<a
href=
'"|add:url|add:"'
>
" link_end="
</a>
" %}
{% blocktrans with link_start="
<a
href=
'"|add:url|add:"'
>
" link_end="
</a>
" %}
...
...
problem_builder/tests/integration/test_dashboard.py
View file @
03621b1a
This diff is collapsed.
Click to expand it.
problem_builder/tests/integration/xml_templates/dashboard.xml
View file @
03621b1a
...
@@ -53,6 +53,5 @@
...
@@ -53,6 +53,5 @@
<pb-choice
value=
"C"
>
Option C
</pb-choice>
<pb-choice
value=
"C"
>
Option C
</pb-choice>
</pb-mcq>
</pb-mcq>
</problem-builder>
</problem-builder>
<pb-dashboard
mentoring_ids=
'["dummy-value"]'
>
{{ dashboard|safe }}
</pb-dashboard>
</vertical_demo>
</vertical_demo>
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