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
3736fa36
Commit
3736fa36
authored
Feb 23, 2015
by
Braden MacDonald
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Made Rating a separate block, refactor MCQ/MRQ/Rating and make them editable
parent
47130c91
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
496 additions
and
270 deletions
+496
-270
mentoring/components/__init__.py
+1
-1
mentoring/components/choice.py
+61
-5
mentoring/components/common.py
+0
-60
mentoring/components/mcq.py
+69
-15
mentoring/components/mrq.py
+61
-7
mentoring/components/public/css/questionnaire-edit.css
+10
-0
mentoring/components/public/js/questionnaire.js
+4
-0
mentoring/components/questionnaire.py
+101
-34
mentoring/components/templates/html/choice.html
+0
-4
mentoring/components/templates/html/mcqblock.html
+2
-2
mentoring/components/templates/html/mrqblock.html
+2
-2
mentoring/components/templates/html/questionnaire_add_buttons.html
+10
-0
mentoring/components/templates/html/ratingblock.html
+2
-2
mentoring/components/templates/html/ratingblock_edit_preview.html
+13
-0
mentoring/components/templates/html/tip.html
+1
-4
mentoring/components/tip.py
+65
-17
mentoring/templates/xml/mentoring_assessment.xml
+13
-16
mentoring/templates/xml/mentoring_default.xml
+13
-19
mentoring/templates/xml/mentoring_with_only_one_step.xml
+4
-7
mentoring/tests/integration/test_assessment.py
+2
-2
mentoring/tests/integration/test_mcq.py
+4
-4
mentoring/tests/integration/xml/assessment_1.xml
+14
-19
mentoring/tests/integration/xml/assessment_2.xml
+4
-5
mentoring/tests/integration/xml/mcq_1.xml
+10
-12
mentoring/tests/integration/xml/mcq_with_comments_1.xml
+8
-9
mentoring/tests/integration/xml/mcq_with_html_choices.xml
+11
-12
mentoring/tests/integration/xml/mrq_with_html_choices.xml
+11
-12
No files found.
mentoring/components/__init__.py
View file @
3736fa36
from
.answer
import
AnswerBlock
from
.answer
import
AnswerBlock
from
.choice
import
ChoiceBlock
from
.choice
import
ChoiceBlock
from
.mcq
import
MCQBlock
from
.mcq
import
MCQBlock
,
RatingBlock
from
.mrq
import
MRQBlock
from
.mrq
import
MRQBlock
from
.message
import
MentoringMessageBlock
from
.message
import
MentoringMessageBlock
from
.table
import
MentoringTableBlock
,
MentoringTableColumnBlock
,
MentoringTableColumnHeaderBlock
from
.table
import
MentoringTableBlock
,
MentoringTableColumnBlock
,
MentoringTableColumnHeaderBlock
...
...
mentoring/components/choice.py
View file @
3736fa36
...
@@ -23,17 +23,73 @@
...
@@ -23,17 +23,73 @@
# Imports ###########################################################
# Imports ###########################################################
from
.common
import
BlockWithContent
from
lxml
import
etree
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
from
xblock.fields
import
Scope
,
String
from
xblock.fragment
import
Fragment
from
xblock.validation
import
ValidationMessage
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
# Classes ###########################################################
# Classes ###########################################################
class
ChoiceBlock
(
BlockWithContent
):
class
ChoiceBlock
(
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
Custom choice of an answer for a MCQ/MRQ
Custom choice of an answer for a MCQ/MRQ
"""
"""
TEMPLATE
=
'templates/html/choice.html'
value
=
String
(
display_name
=
"Value"
,
help
=
"Value of the choice when selected. Should be unique."
,
scope
=
Scope
.
content
,
default
=
""
,
)
content
=
String
(
display_name
=
"Choice Text"
,
help
=
"Human-readable version of the choice value"
,
scope
=
Scope
.
content
,
default
=
""
,
)
editable_fields
=
(
'value'
,
'content'
)
@property
def
display_name
(
self
):
try
:
status
=
self
.
get_parent
()
.
describe_choice_correctness
(
self
.
value
)
except
Exception
:
status
=
u"Out of Context"
# Parent block should implement describe_choice_correctness()
return
u"Choice ({}) ({})"
.
format
(
self
.
value
,
status
)
def
fallback_view
(
self
,
view_name
,
context
):
return
Fragment
(
u'<span class="choice-text">{}</span>'
.
format
(
self
.
content
))
def
validate_field_data
(
self
,
validation
,
data
):
"""
Validate this block's field data.
"""
super
(
ChoiceBlock
,
self
)
.
validate_field_data
(
validation
,
data
)
def
add_error
(
msg
):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
if
not
data
.
value
.
strip
():
add_error
(
u"No value set yet."
)
if
not
data
.
content
.
strip
():
add_error
(
u"No choice text set yet."
)
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
"""
Construct this XBlock from the given XML node.
"""
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
for
field_name
in
cls
.
editable_fields
:
if
field_name
in
node
.
attrib
:
setattr
(
block
,
field_name
,
node
.
attrib
[
field_name
])
# HTML content:
block
.
content
=
unicode
(
node
.
text
or
u""
)
for
child
in
node
:
block
.
content
+=
etree
.
tostring
(
child
,
encoding
=
'unicode'
)
value
=
String
(
help
=
"Value of the choice when selected"
,
scope
=
Scope
.
content
,
default
=
""
)
return
block
content
=
String
(
help
=
"Human-readable version of the choice value"
,
scope
=
Scope
.
content
,
default
=
""
)
mentoring/components/common.py
deleted
100644 → 0
View file @
47130c91
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015 Harvard, edX & OpenCraft
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
from
xblock.fragment
import
Fragment
from
xblockutils.resources
import
ResourceLoader
# Classes ###########################################################
class
BlockWithContent
(
XBlock
):
"""
A block that can contain simple text content OR <html> blocks
with rich HTML content.
"""
TEMPLATE
=
None
# Override in subclass
content
=
String
(
help
=
"Content"
,
scope
=
Scope
.
content
,
default
=
""
)
has_children
=
True
def
fallback_view
(
self
,
view_name
,
context
=
None
):
"""
Returns a fragment containing the HTML
"""
fragment
=
Fragment
()
child_content
=
u""
for
child_id
in
self
.
children
:
child
=
self
.
runtime
.
get_block
(
child_id
)
child_fragment
=
child
.
render
(
'mentoring_view'
,
{})
fragment
.
add_frag_resources
(
child_fragment
)
child_content
+=
child_fragment
.
content
fragment
.
add_content
(
ResourceLoader
(
__name__
)
.
render_template
(
self
.
TEMPLATE
,
{
'self'
:
self
,
'content'
:
self
.
content
,
'child_content'
:
child_content
,
}))
return
fragment
# TODO: fragment_text_rewriting
def
get_html
(
self
):
""" Render as HTML - not as a Fragment """
return
self
.
fallback_view
(
None
,
None
)
.
content
mentoring/components/mcq.py
View file @
3736fa36
...
@@ -25,7 +25,8 @@
...
@@ -25,7 +25,8 @@
import
logging
import
logging
from
xblock.fields
import
Scope
,
String
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fragment
import
Fragment
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.resources
import
ResourceLoader
from
.questionnaire
import
QuestionnaireAbstractBlock
from
.questionnaire
import
QuestionnaireAbstractBlock
...
@@ -34,6 +35,7 @@ from .questionnaire import QuestionnaireAbstractBlock
...
@@ -34,6 +35,7 @@ from .questionnaire import QuestionnaireAbstractBlock
# Globals ###########################################################
# Globals ###########################################################
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Classes ###########################################################
# Classes ###########################################################
...
@@ -42,22 +44,34 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -42,22 +44,34 @@ class MCQBlock(QuestionnaireAbstractBlock):
"""
"""
An XBlock used to ask multiple-choice questions
An XBlock used to ask multiple-choice questions
"""
"""
type
=
String
(
help
=
"Type of MCQ"
,
scope
=
Scope
.
content
,
default
=
"choices"
)
student_choice
=
String
(
help
=
"Last input submitted by the student"
,
default
=
""
,
scope
=
Scope
.
user_state
)
student_choice
=
String
(
help
=
"Last input submitted by the student"
,
default
=
""
,
scope
=
Scope
.
user_state
)
low
=
String
(
help
=
"Label for low ratings"
,
scope
=
Scope
.
content
,
default
=
"Less"
)
high
=
String
(
help
=
"Label for high ratings"
,
scope
=
Scope
.
content
,
default
=
"More"
)
valid_types
=
(
'rating'
,
'choices'
)
correct_choices
=
List
(
display_name
=
"Correct Choice[s]"
,
help
=
"Enter the value[s] that students may select for this question to be considered correct. "
,
scope
=
Scope
.
content
,
list_editor
=
"comma-separated"
,
)
editable_fields
=
QuestionnaireAbstractBlock
.
editable_fields
+
(
'correct_choices'
,
)
def
describe_choice_correctness
(
self
,
choice_value
):
if
choice_value
in
self
.
correct_choices
:
if
len
(
self
.
correct_choices
)
==
1
:
return
u"Correct"
return
u"Acceptable"
else
:
if
len
(
self
.
correct_choices
)
==
1
:
return
u"Wrong"
return
u"Not Acceptable"
def
submit
(
self
,
submission
):
def
submit
(
self
,
submission
):
log
.
debug
(
u'Received MCQ submission: "
%
s"'
,
submission
)
log
.
debug
(
u'Received MCQ submission: "
%
s"'
,
submission
)
correct
=
True
correct
=
submission
in
self
.
correct_choices
tips_html
=
[]
tips_html
=
[]
for
tip
in
self
.
get_tips
():
for
tip
in
self
.
get_tips
():
correct
=
correct
and
self
.
is_tip_correct
(
tip
,
submission
)
if
submission
in
tip
.
values
:
if
submission
in
tip
.
display_with_defaults
:
tips_html
.
append
(
tip
.
render
(
'mentoring_view'
)
.
content
)
tips_html
.
append
(
tip
.
get_html
())
formatted_tips
=
ResourceLoader
(
__name__
)
.
render_template
(
'templates/html/tip_choice_group.html'
,
{
formatted_tips
=
ResourceLoader
(
__name__
)
.
render_template
(
'templates/html/tip_choice_group.html'
,
{
'self'
:
self
,
'self'
:
self
,
...
@@ -76,11 +90,51 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -76,11 +90,51 @@ class MCQBlock(QuestionnaireAbstractBlock):
log
.
debug
(
u'MCQ submission result:
%
s'
,
result
)
log
.
debug
(
u'MCQ submission result:
%
s'
,
result
)
return
result
return
result
def
is_tip_correct
(
self
,
tip
,
submission
):
def
author_edit_view
(
self
,
context
):
if
not
submission
:
"""
return
False
The options for the 1-5 values of the Likert scale aren't child blocks but we want to
show them in the author edit view, for clarity.
"""
fragment
=
Fragment
(
u"<p>{}</p>"
.
format
(
self
.
question
))
self
.
render_children
(
context
,
fragment
,
can_reorder
=
True
,
can_add
=
False
)
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/questionnaire_add_buttons.html'
,
{}))
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/css/questionnaire-edit.css'
))
return
fragment
if
submission
in
tip
.
reject_with_defaults
:
return
False
return
True
class
RatingBlock
(
MCQBlock
):
"""
An XBlock used to rate something on a five-point scale, e.g. Likert Scale
"""
low
=
String
(
help
=
"Label for low ratings"
,
scope
=
Scope
.
content
,
default
=
"Less"
)
high
=
String
(
help
=
"Label for high ratings"
,
scope
=
Scope
.
content
,
default
=
"More"
)
FIXED_VALUES
=
[
"1"
,
"2"
,
"3"
,
"4"
,
"5"
]
correct_choices
=
List
(
display_name
=
"Accepted Choice[s]"
,
help
=
"Enter the rating value[s] that students may select for this question to be considered correct. "
,
scope
=
Scope
.
content
,
list_editor
=
"comma-separated"
,
default
=
FIXED_VALUES
,
)
editable_fields
=
MCQBlock
.
editable_fields
+
(
'low'
,
'high'
)
@property
def
all_choice_values
(
self
):
return
self
.
FIXED_VALUES
+
[
c
.
value
for
c
in
self
.
custom_choices
]
def
author_edit_view
(
self
,
context
):
"""
The options for the 1-5 values of the Likert scale aren't child blocks but we want to
show them in the author edit view, for clarity.
"""
fragment
=
Fragment
()
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/ratingblock_edit_preview.html'
,
{
'question'
:
self
.
question
,
'low'
:
self
.
low
,
'high'
:
self
.
high
,
'accepted_statuses'
:
[
None
]
+
[
self
.
describe_choice_correctness
(
c
)
for
c
in
"12345"
],
}))
self
.
render_children
(
context
,
fragment
,
can_reorder
=
True
,
can_add
=
False
)
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/questionnaire_add_buttons.html'
,
{}))
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/css/questionnaire-edit.css'
))
return
fragment
mentoring/components/mrq.py
View file @
3736fa36
...
@@ -26,6 +26,7 @@
...
@@ -26,6 +26,7 @@
import
logging
import
logging
from
xblock.fields
import
List
,
Scope
,
Boolean
from
xblock.fields
import
List
,
Scope
,
Boolean
from
xblock.validation
import
ValidationMessage
from
.questionnaire
import
QuestionnaireAbstractBlock
from
.questionnaire
import
QuestionnaireAbstractBlock
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.resources
import
ResourceLoader
...
@@ -42,7 +43,35 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -42,7 +43,35 @@ class MRQBlock(QuestionnaireAbstractBlock):
An XBlock used to ask multiple-response questions
An XBlock used to ask multiple-response questions
"""
"""
student_choices
=
List
(
help
=
"Last submissions by the student"
,
default
=
[],
scope
=
Scope
.
user_state
)
student_choices
=
List
(
help
=
"Last submissions by the student"
,
default
=
[],
scope
=
Scope
.
user_state
)
hide_results
=
Boolean
(
help
=
"Hide results"
,
scope
=
Scope
.
content
,
default
=
False
)
required_choices
=
List
(
display_name
=
"Required Choices"
,
help
=
(
"Enter the value[s] that students must select for this MRQ to be considered correct. "
"Separate multiple required choices with a comma."
),
scope
=
Scope
.
content
,
list_editor
=
"comma-separated"
,
default
=
[],
)
ignored_choices
=
List
(
display_name
=
"Ignored Choices"
,
help
=
(
"Enter the value[s] that are neither correct nor incorrect. "
"Any values not listed as required or ignored will be considered wrong."
),
scope
=
Scope
.
content
,
list_editor
=
"comma-separated"
,
default
=
[],
)
hide_results
=
Boolean
(
display_name
=
"Hide results"
,
scope
=
Scope
.
content
,
default
=
False
)
editable_fields
=
(
'question'
,
'required_choices'
,
'ignored_choices'
,
'message'
,
'weight'
,
'hide_results'
,
)
def
describe_choice_correctness
(
self
,
choice_value
):
if
choice_value
in
self
.
required_choices
:
return
u"Required"
elif
choice_value
in
self
.
ignored_choices
:
return
u"Ignored"
return
u"Not Acceptable"
def
submit
(
self
,
submissions
):
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
...
@@ -54,13 +83,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -54,13 +83,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
choice_completed
=
True
choice_completed
=
True
choice_tips_html
=
[]
choice_tips_html
=
[]
choice_selected
=
choice
.
value
in
submissions
choice_selected
=
choice
.
value
in
submissions
for
tip
in
self
.
get_tips
():
if
choice
.
value
in
self
.
required_choices
:
if
choice
.
value
in
tip
.
display_with_defaults
:
if
not
choice_selected
:
choice_tips_html
.
append
(
tip
.
get_html
())
if
((
not
choice_selected
and
choice
.
value
in
tip
.
require_with_defaults
)
or
(
choice_selected
and
choice
.
value
in
tip
.
reject_with_defaults
)):
choice_completed
=
False
choice_completed
=
False
elif
choice_selected
and
choice
.
value
not
in
self
.
ignored_choices
:
choice_completed
=
False
for
tip
in
self
.
get_tips
():
if
choice
.
value
in
tip
.
values
:
choice_tips_html
.
append
(
tip
.
render
(
'mentoring_view'
)
.
content
)
if
choice_completed
:
if
choice_completed
:
score
+=
1
score
+=
1
...
@@ -96,3 +126,27 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -96,3 +126,27 @@ class MRQBlock(QuestionnaireAbstractBlock):
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
return
result
return
result
def
validate_field_data
(
self
,
validation
,
data
):
"""
Validate this block's field data.
"""
super
(
MRQBlock
,
self
)
.
validate_field_data
(
validation
,
data
)
def
add_error
(
msg
):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
all_values
=
set
(
self
.
all_choice_values
)
required
=
set
(
data
.
required_choices
)
ignored
=
set
(
data
.
ignored_choices
)
if
len
(
required
)
<
len
(
data
.
required_choices
):
add_error
(
u"Duplicate required choices set"
)
if
len
(
ignored
)
<
len
(
data
.
ignored_choices
):
add_error
(
u"Duplicate ignored choices set"
)
for
val
in
required
.
intersection
(
ignored
):
add_error
(
u"A choice is listed as both required and ignored: {}"
.
format
(
val
))
for
val
in
(
required
-
all_values
):
add_error
(
u"A choice value listed as required does not exist: {}"
.
format
(
val
))
for
val
in
(
ignored
-
all_values
):
add_error
(
u"A choice value listed as ignored does not exist: {}"
.
format
(
val
))
mentoring/components/public/css/questionnaire-edit.css
0 → 100644
View file @
3736fa36
/* Custom appearance for our "Add" buttons */
.xblock
.add-xblock-component
.new-component
.new-component-type
.add-xblock-component-button
{
width
:
200px
;
height
:
30px
;
line-height
:
30px
;
}
.wrapper-xblock.level-page
.xblock-render
{
padding
:
10px
;
}
mentoring/components/public/js/questionnaire.js
View file @
3736fa36
...
@@ -137,6 +137,10 @@ function MCQBlock(runtime, element) {
...
@@ -137,6 +137,10 @@ function MCQBlock(runtime, element) {
};
};
}
}
function
RatingBlock
(
runtime
,
element
)
{
return
MCQBlock
(
runtime
,
element
);
}
function
MRQBlock
(
runtime
,
element
,
mentoring
)
{
function
MRQBlock
(
runtime
,
element
,
mentoring
)
{
return
{
return
{
mode
:
null
,
mode
:
null
,
...
...
mentoring/components/questionnaire.py
View file @
3736fa36
...
@@ -25,18 +25,34 @@
...
@@ -25,18 +25,34 @@
from
lxml
import
etree
from
lxml
import
etree
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
Float
from
xblock.fields
import
Scope
,
String
,
Float
,
List
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblock.validation
import
ValidationMessage
from
xblockutils.helpers
import
child_isinstance
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
,
StudioContainerXBlockMixin
from
.choice
import
ChoiceBlock
from
.choice
import
ChoiceBlock
from
.step
import
StepMixin
from
.step
import
StepMixin
from
.tip
import
TipBlock
from
.tip
import
TipBlock
# Globals ###########################################################
loader
=
ResourceLoader
(
__name__
)
# Classes ###########################################################
# Classes ###########################################################
class
QuestionnaireAbstractBlock
(
XBlock
,
StepMixin
):
class
property_with_default
(
property
):
"""
Decorator for creating a dynamic display_name property that looks like an XBlock field. This
is needed for Studio container page blocks as studio will try to read
BlockClass.display_name.default
"""
default
=
u"Question"
class
QuestionnaireAbstractBlock
(
StudioEditableXBlockMixin
,
StudioContainerXBlockMixin
,
StepMixin
,
XBlock
):
"""
"""
An abstract class used for MCQ/MRQ blocks
An abstract class used for MCQ/MRQ blocks
...
@@ -44,51 +60,68 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
...
@@ -44,51 +60,68 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
values entered by the student, and supports multiple types of multiple-choice
values entered by the student, and supports multiple types of multiple-choice
set, with preset choices and author-defined values.
set, with preset choices and author-defined values.
"""
"""
type
=
String
(
help
=
"Type of questionnaire"
,
scope
=
Scope
.
content
,
default
=
"choices"
)
question
=
String
(
question
=
String
(
help
=
"Question to ask the student"
,
scope
=
Scope
.
content
,
default
=
""
)
display_name
=
"Question"
,
message
=
String
(
help
=
"General feedback provided when submiting"
,
scope
=
Scope
.
content
,
default
=
""
)
help
=
"Question to ask the student"
,
weight
=
Float
(
help
=
"Defines the maximum total grade of the light child block."
,
scope
=
Scope
.
content
,
default
=
1
,
scope
=
Scope
.
content
,
enforce_type
=
True
)
default
=
""
)
valid_types
=
(
'choices'
)
message
=
String
(
display_name
=
"Message"
,
help
=
"General feedback provided when submiting"
,
scope
=
Scope
.
content
,
default
=
""
)
weight
=
Float
(
display_name
=
"Weight"
,
help
=
"Defines the maximum total grade of this question."
,
default
=
1
,
scope
=
Scope
.
content
,
enforce_type
=
True
)
editable_fields
=
(
'question'
,
'message'
,
'weight'
)
has_children
=
True
has_children
=
True
@classmethod
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
"""
Custom XML parser that can handle list type fields properly,
as well as the old way of defining 'question' and 'message' field values via tags.
"""
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
# Load XBlock properties from the XML attributes:
# Load XBlock properties from the XML attributes:
for
name
,
value
in
node
.
items
():
for
name
,
value
in
node
.
items
():
setattr
(
block
,
name
,
value
)
field
=
block
.
fields
[
name
]
if
isinstance
(
field
,
List
)
and
not
value
.
startswith
(
'['
):
# This list attribute is just a string of comma separated strings:
setattr
(
block
,
name
,
[
unicode
(
val
)
.
strip
()
for
val
in
value
.
split
(
','
)])
elif
isinstance
(
field
,
String
):
setattr
(
block
,
name
,
value
)
else
:
setattr
(
block
,
name
,
field
.
from_json
(
value
))
for
xml_child
in
node
:
for
xml_child
in
node
:
if
xml_child
.
tag
==
'question'
:
if
xml_child
.
tag
is
not
etree
.
Comment
:
block
.
question
=
xml_child
.
text
elif
xml_child
.
tag
==
'message'
and
xml_child
.
get
(
'type'
)
==
'on-submit'
:
block
.
message
=
(
xml_child
.
text
or
''
)
.
strip
()
elif
xml_child
.
tag
is
not
etree
.
Comment
:
block
.
runtime
.
add_node_as_child
(
block
,
xml_child
,
id_generator
)
block
.
runtime
.
add_node_as_child
(
block
,
xml_child
,
id_generator
)
return
block
return
block
@property_with_default
def
display_name
(
self
):
return
u"Question {}"
.
format
(
self
.
step_number
)
if
not
self
.
lonely_step
else
u"Question"
def
student_view
(
self
,
context
=
None
):
def
student_view
(
self
,
context
=
None
):
name
=
getattr
(
self
,
"unmixed_class"
,
self
.
__class__
)
.
__name__
name
=
getattr
(
self
,
"unmixed_class"
,
self
.
__class__
)
.
__name__
if
str
(
self
.
type
)
not
in
self
.
valid_types
:
template_path
=
'templates/html/{}.html'
.
format
(
name
.
lower
())
raise
ValueError
(
u'Invalid value for {}.type: `{}`'
.
format
(
name
,
self
.
type
))
template_path
=
'templates/html/{}_{}.html'
.
format
(
name
.
lower
(),
self
.
type
)
context
=
context
or
{}
loader
=
ResourceLoader
(
__name__
)
context
[
'self'
]
=
self
context
[
'custom_choices'
]
=
self
.
custom_choices
html
=
loader
.
render_template
(
template_path
,
{
fragment
=
Fragment
(
loader
.
render_template
(
template_path
,
context
))
'self'
:
self
,
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/css/questionnaire.css'
))
'custom_choices'
:
self
.
custom_choices
})
fragment
=
Fragment
(
html
)
fragment
.
add_css
(
loader
.
render_template
(
'public/css/questionnaire.css'
,
{
'self'
:
self
}))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/questionnaire.js'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/questionnaire.js'
))
fragment
.
initialize_js
(
name
)
fragment
.
initialize_js
(
name
)
return
fragment
return
fragment
...
@@ -100,20 +133,22 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
...
@@ -100,20 +133,22 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
def
custom_choices
(
self
):
def
custom_choices
(
self
):
custom_choices
=
[]
custom_choices
=
[]
for
child_id
in
self
.
children
:
for
child_id
in
self
.
children
:
child
=
self
.
runtime
.
get_block
(
child_id
)
if
child_isinstance
(
self
,
child_id
,
ChoiceBlock
):
if
isinstance
(
child
,
ChoiceBlock
):
custom_choices
.
append
(
self
.
runtime
.
get_block
(
child_id
))
custom_choices
.
append
(
child
)
return
custom_choices
return
custom_choices
@property
def
all_choice_values
(
self
):
return
[
c
.
value
for
c
in
self
.
custom_choices
]
def
get_tips
(
self
):
def
get_tips
(
self
):
"""
"""
Returns the tips contained in this block
Returns the tips contained in this block
"""
"""
tips
=
[]
tips
=
[]
for
child_id
in
self
.
children
:
for
child_id
in
self
.
children
:
child
=
self
.
runtime
.
get_block
(
child_id
)
if
child_isinstance
(
self
,
child_id
,
TipBlock
):
if
isinstance
(
child
,
TipBlock
):
tips
.
append
(
self
.
runtime
.
get_block
(
child_id
))
tips
.
append
(
child
)
return
tips
return
tips
def
get_submission_display
(
self
,
submission
):
def
get_submission_display
(
self
,
submission
):
...
@@ -124,3 +159,35 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
...
@@ -124,3 +159,35 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
if
choice
.
value
==
submission
:
if
choice
.
value
==
submission
:
return
choice
.
content
return
choice
.
content
return
submission
return
submission
def
author_edit_view
(
self
,
context
):
"""
Add some HTML to the author view that allows authors to add choices and tips.
"""
fragment
=
super
(
QuestionnaireAbstractBlock
,
self
)
.
author_edit_view
(
context
)
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/questionnaire_add_buttons.html'
,
{}))
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/css/questionnaire-edit.css'
))
return
fragment
def
validate
(
self
):
"""
Validates the state of this XBlock.
"""
validation
=
super
(
QuestionnaireAbstractBlock
,
self
)
.
validate
()
def
add_error
(
msg
):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
# Validate the choice values:
all_choice_values
=
self
.
all_choice_values
all_choice_values_set
=
set
(
all_choice_values
)
if
len
(
all_choice_values
)
!=
len
(
all_choice_values_set
):
add_error
(
u"Some choice values are not unique."
)
# Validate the tips:
values_with_tips
=
set
()
for
tip
in
self
.
get_tips
():
values
=
set
(
tip
.
values
)
for
val
in
(
values
&
values_with_tips
):
add_error
(
u"Multiple tips for value '{}'"
.
format
(
val
))
values_with_tips
.
update
(
values
)
return
validation
mentoring/components/templates/html/choice.html
deleted
100644 → 0
View file @
47130c91
<span
class=
"choice-text"
>
{{ self.content }}
{{ child_content|safe }}
</span>
mentoring/components/templates/html/mcqblock
_choices
.html
→
mentoring/components/templates/html/mcqblock.html
View file @
3736fa36
<fieldset
class=
"choices questionnaire"
>
<fieldset
class=
"choices questionnaire"
>
<legend
class=
"question"
>
<legend
class=
"question"
>
<h3
class=
"question-title"
>
QUESTION {% if not self.lonely_step %}{{ self.step_number }}{% endif %}
</h3>
{% if not hide_header %}
<h3
class=
"question-title"
>
{{ self.display_name }}
</h3>
{% endif %}
<p>
{{ self.question }}
</p>
<p>
{{ self.question }}
</p>
</legend>
</legend>
<div
class=
"choices-list"
>
<div
class=
"choices-list"
>
...
@@ -9,7 +9,7 @@
...
@@ -9,7 +9,7 @@
<div
class=
"choice-result fa icon-2x"
></div>
<div
class=
"choice-result fa icon-2x"
></div>
<label
class=
"choice-label"
>
<label
class=
"choice-label"
>
<input
class=
"choice-selector"
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
choice
.
value
%}
checked
{%
endif
%}
/>
<input
class=
"choice-selector"
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
choice
.
value
%}
checked
{%
endif
%}
/>
{{ choice.get_html|safe }}
<span
class=
"choice-text"
>
{{ choice.content|safe }}
</span>
</label>
</label>
<div
class=
"choice-tips"
></div>
<div
class=
"choice-tips"
></div>
</div>
</div>
...
...
mentoring/components/templates/html/mrqblock
_choices
.html
→
mentoring/components/templates/html/mrqblock.html
View file @
3736fa36
<fieldset
class=
"choices questionnaire"
data-hide_results=
"{{self.hide_results}}"
>
<fieldset
class=
"choices questionnaire"
data-hide_results=
"{{self.hide_results}}"
>
<legend
class=
"question"
>
<legend
class=
"question"
>
<h3
class=
"question-title"
>
QUESTION {% if not self.lonely_step %}{{ self.step_number }}{% endif %}
</h3>
{% if not hide_header %}
<h3
class=
"question-title"
>
{{ self.display_name }}
</h3>
{% endif %}
<p>
{{ self.question }}
</p>
<p>
{{ self.question }}
</p>
</legend>
</legend>
<div
class=
"choices-list"
>
<div
class=
"choices-list"
>
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
<input
class=
"choice-selector"
type=
"checkbox"
name=
"{{ self.name }}"
<input
class=
"choice-selector"
type=
"checkbox"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
value=
"{{ choice.value }}"
{%
if
choice
.
value
in
self
.
student_choices
%}
checked
{%
endif
%}
/>
{%
if
choice
.
value
in
self
.
student_choices
%}
checked
{%
endif
%}
/>
{{ choice.get_html|safe }}
<span
class=
"choice-text"
>
{{ choice.content|safe }}
</span>
</label>
</label>
<div
class=
"choice-tips"
></div>
<div
class=
"choice-tips"
></div>
</div>
</div>
...
...
mentoring/components/templates/html/questionnaire_add_buttons.html
0 → 100644
View file @
3736fa36
{% load i18n %}
<div
class=
"add-xblock-component new-component-item adding"
>
<div
class=
"new-component"
>
<ul
class=
"new-component-type"
>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-choice"
>
Add Custom Choice
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-tip"
>
Add Tip
</a></li>
</ul>
</div>
</div>
mentoring/components/templates/html/
mcqblock_rating
.html
→
mentoring/components/templates/html/
ratingblock
.html
View file @
3736fa36
<fieldset
class=
"rating questionnaire"
>
<fieldset
class=
"rating questionnaire"
>
<legend
class=
"question"
>
<legend
class=
"question"
>
<h3
class=
"question-title"
>
QUESTION {% if not self.lonely_step %}{{ self.step_number }}{% endif %}
</h3>
{% if not hide_header %}
<h3
class=
"question-title"
>
{{ self.display_name }}
</h3>
{% endif %}
<p>
{{ self.question }}
</p>
<p>
{{ self.question }}
</p>
</legend>
</legend>
<div
class=
"choices-list"
>
<div
class=
"choices-list"
>
...
@@ -35,7 +35,7 @@
...
@@ -35,7 +35,7 @@
<div
class=
"choice"
>
<div
class=
"choice"
>
<div
class=
"choice-result fa icon-2x"
></div>
<div
class=
"choice-result fa icon-2x"
></div>
<label><input
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
'{{
choice
.
value
}}'
%}
checked
{%
endif
%}
/>
<label><input
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
'{{
choice
.
value
}}'
%}
checked
{%
endif
%}
/>
{{ choice.get_html|safe }}
<span
class=
"choice-text"
>
{{ choice.content|safe }}
</span>
</label>
</label>
<div
class=
"choice-tips"
></div>
<div
class=
"choice-tips"
></div>
</div>
</div>
...
...
mentoring/components/templates/html/ratingblock_edit_preview.html
0 → 100644
View file @
3736fa36
<p>
{{ question }}
</p>
<h2>
Built-in choices:
</h2>
<ul>
<li>
Choice (1):
<strong>
1 - {{ low }}
</strong>
({{accepted_statuses.1}})
</li>
<li>
Choice (2):
<strong>
2
</strong>
({{accepted_statuses.2}})
</li>
<li>
Choice (3):
<strong>
3
</strong>
({{accepted_statuses.3}})
</li>
<li>
Choice (4):
<strong>
4
</strong>
({{accepted_statuses.4}})
</li>
<li>
Choice (5):
<strong>
5 - {{ high }}
</strong>
({{accepted_statuses.5}})
</li>
</ul>
<h2>
Additional custom choices and tips:
</h2>
mentoring/components/templates/html/tip.html
View file @
3736fa36
...
@@ -3,8 +3,5 @@
...
@@ -3,8 +3,5 @@
{%
if
width
%}
data-width=
"{{width}}"
{%
endif
%}
{%
if
width
%}
data-width=
"{{width}}"
{%
endif
%}
{%
if
height
%}
data-height=
"{{height}}"
{%
endif
%}
{%
if
height
%}
data-height=
"{{height}}"
{%
endif
%}
>
>
{% if self.content %}
<p>
{{ content|safe }}
</p>
<p>
{{ self.content }}
</p>
{% endif %}
{{ child_content|safe }}
</div>
</div>
mentoring/components/tip.py
View file @
3736fa36
...
@@ -23,8 +23,14 @@
...
@@ -23,8 +23,14 @@
# Imports ###########################################################
# Imports ###########################################################
from
.common
import
BlockWithContent
from
lxml
import
etree
from
xblock.fields
import
Scope
,
String
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fragment
import
Fragment
from
xblock.validation
import
ValidationMessage
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
# Functions #########################################################
# Functions #########################################################
...
@@ -41,28 +47,70 @@ def commas_to_set(commas_str):
...
@@ -41,28 +47,70 @@ def commas_to_set(commas_str):
# Classes ###########################################################
# Classes ###########################################################
class
TipBlock
(
BlockWithContent
):
class
TipBlock
(
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
Each choice can define a tip depending on selection
Each choice can define a tip depending on selection
"""
"""
TEMPLATE
=
'templates/html/tip.html'
content
=
String
(
help
=
"Text of the tip to provide if needed"
,
scope
=
Scope
.
content
,
default
=
""
)
content
=
String
(
help
=
"Text of the tip to provide if needed"
,
scope
=
Scope
.
content
,
default
=
""
)
display
=
String
(
help
=
"List of choices to display the tip for"
,
scope
=
Scope
.
content
,
default
=
None
)
values
=
List
(
reject
=
String
(
help
=
"List of choices to reject"
,
scope
=
Scope
.
content
,
default
=
None
)
display_name
=
"For Choices"
,
require
=
String
(
help
=
"List of choices to require"
,
scope
=
Scope
.
content
,
default
=
None
)
help
=
"List of choice value[s] to display the tip for"
,
scope
=
Scope
.
content
,
default
=
[],
)
width
=
String
(
help
=
"Width of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
width
=
String
(
help
=
"Width of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
height
=
String
(
help
=
"Height of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
height
=
String
(
help
=
"Height of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
editable_fields
=
(
'values'
,
'content'
,
'width'
,
'height'
)
@property
@property
def
display_with_defaults
(
self
):
def
display_name
(
self
):
display
=
commas_to_set
(
self
.
display
)
return
u"Tip for {}"
.
format
(
u", "
.
join
([
unicode
(
v
)
for
v
in
self
.
values
]))
return
display
|
self
.
reject_with_defaults
|
self
.
require_with_defaults
@property
def
fallback_view
(
self
,
view_name
,
context
):
def
reject_with_defaults
(
self
):
html
=
ResourceLoader
(
__name__
)
.
render_template
(
"templates/html/tip.html"
,
{
return
commas_to_set
(
self
.
reject
)
'content'
:
self
.
content
,
'width'
:
self
.
width
,
'height'
:
self
.
height
,
})
return
Fragment
(
html
)
@property
def
clean_studio_edits
(
self
,
data
):
def
require_with_defaults
(
self
):
"""
return
commas_to_set
(
self
.
require
)
Clean up the edits during studio_view save
"""
if
"values"
in
data
:
data
[
"values"
]
=
list
([
unicode
(
v
)
for
v
in
set
(
data
[
"values"
])])
def
validate_field_data
(
self
,
validation
,
data
):
"""
Validate this block's field data.
"""
super
(
TipBlock
,
self
)
.
validate_field_data
(
validation
,
data
)
def
add_error
(
msg
):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
try
:
valid_values
=
set
(
self
.
get_parent
()
.
all_choice_values
)
except
Exception
:
pass
else
:
for
val
in
set
(
data
.
values
)
-
valid_values
:
add_error
(
u"A choice value listed for this tip does not exist: {}"
.
format
(
val
))
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
"""
Construct this XBlock from the given XML node.
"""
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
block
.
values
=
[
unicode
(
val
)
.
strip
()
for
val
in
node
.
get
(
'values'
,
''
)
.
split
(
','
)]
block
.
width
=
node
.
get
(
'width'
,
''
)
block
.
height
=
node
.
get
(
'height'
,
''
)
block
.
content
=
unicode
(
node
.
text
or
u""
)
for
child
in
node
:
block
.
content
+=
etree
.
tostring
(
child
,
encoding
=
'unicode'
)
return
block
mentoring/templates/xml/mentoring_assessment.xml
View file @
3736fa36
...
@@ -9,35 +9,32 @@
...
@@ -9,35 +9,32 @@
<question>
What is your goal?
</question>
<question>
What is your goal?
</question>
</answer>
</answer>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
question=
"Do you like this MCQ?"
correct_choices=
"yes"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<tip
display
=
"yes"
>
Great!
</tip>
<tip
values
=
"yes"
>
Great!
</tip>
<tip
reject
=
"maybenot"
>
Ah, damn.
</tip>
<tip
values
=
"maybenot"
>
Ah, damn.
</tip>
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html
></tip>
<tip
values=
"understand"
><div
id=
"test-custom-html"
>
Really?
</div
></tip>
</mcq>
</mcq>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
>
<rating
name=
"mcq_1_2"
low=
"Not good at all"
high=
"Extremely good"
question=
"How much do you rate this MCQ?"
correct_choices=
"4,5"
>
<question>
How much do you rate this MCQ?
</question>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<tip
display
=
"4,5"
>
I love good grades.
</tip>
<tip
values
=
"4,5"
>
I love good grades.
</tip>
<tip
reject
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
values
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
reject
=
"notwant"
>
Your loss!
</tip>
<tip
values
=
"notwant"
>
Your loss!
</tip>
</
mcq
>
</
rating
>
<mrq
name=
"mrq_1_1"
type=
"choices"
>
<mrq
name=
"mrq_1_1"
question=
"What do you like in this MRQ?"
required_choices=
"gracefulness,elegance,beauty"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require
=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
values
=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
require
=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values
=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
reject
=
"bugs"
>
Nah, there isn't any!
</tip>
<tip
values
=
"bugs"
>
Nah, there isn't any!
</tip>
</mrq>
</mrq>
</mentoring>
</mentoring>
mentoring/templates/xml/mentoring_default.xml
View file @
3736fa36
...
@@ -7,38 +7,32 @@
...
@@ -7,38 +7,32 @@
<question>
What is your goal?
</question>
<question>
What is your goal?
</question>
</answer>
</answer>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
question=
"Do you like this MCQ?"
correct_choices=
"yes"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<tip
display
=
"yes"
>
Great!
</tip>
<tip
values
=
"yes"
>
Great!
</tip>
<tip
reject
=
"maybenot"
>
Ah, damn.
</tip>
<tip
values
=
"maybenot"
>
Ah, damn.
</tip>
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html
></tip>
<tip
values=
"understand"
><div
id=
"test-custom-html"
>
Really?
</div
></tip>
</mcq>
</mcq>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
>
<rating
name=
"mcq_1_2"
low=
"Not good at all"
high=
"Extremely good"
question=
"How much do you rate this MCQ?"
correct_choices=
"4,5"
>
<question>
How much do you rate this MCQ?
</question>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<tip
values=
"4,5"
>
I love good grades.
</tip>
<tip
values=
"1,2,3"
>
Will do better next time...
</tip>
<tip
values=
"notwant"
>
Your loss!
</tip>
</rating>
<tip
display=
"4,5"
>
I love good grades.
</tip>
<mrq
name=
"mrq_1_1"
question=
"What do you like in this MRQ?"
required_choices=
"gracefulness,elegance,beauty"
message=
"Thank you for answering!"
>
<tip
reject=
"1,2,3"
>
Will do better next time...
</tip>
<tip
reject=
"notwant"
>
Your loss!
</tip>
</mcq>
<mrq
name=
"mrq_1_1"
type=
"choices"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
values=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
require=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
reject=
"bugs"
>
Nah, there isn't any!
</tip>
<tip
values=
"bugs"
>
Nah, there aren't any!
</tip>
<message
type=
"on-submit"
>
Thank you for answering!
</message>
</mrq>
</mrq>
<message
type=
"completed"
>
<message
type=
"completed"
>
...
...
mentoring/templates/xml/mentoring_with_only_one_step.xml
View file @
3736fa36
...
@@ -3,18 +3,15 @@
...
@@ -3,18 +3,15 @@
<p>
Please answer the questions below.
</p>
<p>
Please answer the questions below.
</p>
</html_demo>
</html_demo>
<mrq
name=
"mrq_1_1"
type=
"choices"
>
<mrq
name=
"mrq_1_1"
question=
"What do you like in this MRQ?"
message=
"Thank you for answering!"
required_choices=
"gracefulness,elegance,beauty"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
values=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
require=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
reject=
"bugs"
>
Nah, there isn't any!
</tip>
<tip
values=
"bugs"
>
Nah, there aren't any!
</tip>
<message
type=
"on-submit"
>
Thank you for answering!
</message>
</mrq>
</mrq>
<message
type=
"completed"
>
<message
type=
"completed"
>
...
...
mentoring/tests/integration/test_assessment.py
View file @
3736fa36
...
@@ -91,9 +91,9 @@ class MentoringAssessmentTest(MentoringBaseTest):
...
@@ -91,9 +91,9 @@ class MentoringAssessmentTest(MentoringBaseTest):
@staticmethod
@staticmethod
def
question_text
(
number
):
def
question_text
(
number
):
if
number
:
if
number
:
return
"Q
UESTION
%
s"
%
number
return
"Q
uestion
%
s"
%
number
else
:
else
:
return
"Q
UESTION
"
return
"Q
uestion
"
def
freeform_answer
(
self
,
number
,
mentoring
,
controls
,
text_input
,
result
,
saved_value
=
""
,
last
=
False
):
def
freeform_answer
(
self
,
number
,
mentoring
,
controls
,
text_input
,
result
,
saved_value
=
""
,
last
=
False
):
question
=
self
.
expect_question_visible
(
number
,
mentoring
)
question
=
self
.
expect_question_visible
(
number
,
mentoring
)
...
...
mentoring/tests/integration/test_mcq.py
View file @
3736fa36
...
@@ -68,8 +68,8 @@ class MCQBlockTest(MentoringBaseTest):
...
@@ -68,8 +68,8 @@ class MCQBlockTest(MentoringBaseTest):
mcq1_legend
=
mcq1
.
find_element_by_css_selector
(
'legend'
)
mcq1_legend
=
mcq1
.
find_element_by_css_selector
(
'legend'
)
mcq2_legend
=
mcq2
.
find_element_by_css_selector
(
'legend'
)
mcq2_legend
=
mcq2
.
find_element_by_css_selector
(
'legend'
)
self
.
assertEqual
(
mcq1_legend
.
text
,
'Q
UESTION
1
\n
Do you like this MCQ?'
)
self
.
assertEqual
(
mcq1_legend
.
text
,
'Q
uestion
1
\n
Do you like this MCQ?'
)
self
.
assertEqual
(
mcq2_legend
.
text
,
'Q
UESTION 2
\n
How much
do you rate this MCQ?'
)
self
.
assertEqual
(
mcq2_legend
.
text
,
'Q
uestion 2
\n
How
do you rate this MCQ?'
)
mcq1_choices
=
mcq1
.
find_elements_by_css_selector
(
'.choices .choice label'
)
mcq1_choices
=
mcq1
.
find_elements_by_css_selector
(
'.choices .choice label'
)
mcq2_choices
=
mcq2
.
find_elements_by_css_selector
(
'.rating .choice label'
)
mcq2_choices
=
mcq2
.
find_elements_by_css_selector
(
'.rating .choice label'
)
...
@@ -144,7 +144,7 @@ class MCQBlockTest(MentoringBaseTest):
...
@@ -144,7 +144,7 @@ class MCQBlockTest(MentoringBaseTest):
self
.
assertFalse
(
submit
.
is_enabled
())
self
.
assertFalse
(
submit
.
is_enabled
())
mcq_legend
=
mcq
.
find_element_by_css_selector
(
'legend'
)
mcq_legend
=
mcq
.
find_element_by_css_selector
(
'legend'
)
self
.
assertEqual
(
mcq_legend
.
text
,
'Q
UESTION
\n
What do you like in this MRQ?'
)
self
.
assertEqual
(
mcq_legend
.
text
,
'Q
uestion
\n
What do you like in this MRQ?'
)
mcq_choices
=
mcq
.
find_elements_by_css_selector
(
'.choices .choice label'
)
mcq_choices
=
mcq
.
find_elements_by_css_selector
(
'.choices .choice label'
)
...
@@ -195,7 +195,7 @@ class MCQBlockTest(MentoringBaseTest):
...
@@ -195,7 +195,7 @@ class MCQBlockTest(MentoringBaseTest):
# this could be a list comprehension, but a bit complicated one - hence explicit loop
# this could be a list comprehension, but a bit complicated one - hence explicit loop
for
choice_wrapper
in
questionnaire
.
find_elements_by_css_selector
(
".choice"
):
for
choice_wrapper
in
questionnaire
.
find_elements_by_css_selector
(
".choice"
):
choice_label
=
choice_wrapper
.
find_element_by_css_selector
(
"label .choice-text"
)
choice_label
=
choice_wrapper
.
find_element_by_css_selector
(
"label .choice-text"
)
result
.
append
(
choice_label
.
find_element_by_css_selector
(
"div.html_child"
)
.
get_attribute
(
'innerHTML'
))
result
.
append
(
choice_label
.
get_attribute
(
'innerHTML'
))
return
result
return
result
...
...
mentoring/tests/integration/xml/assessment_1.xml
View file @
3736fa36
...
@@ -5,39 +5,34 @@
...
@@ -5,39 +5,34 @@
<p>
Please answer the questions below.
</p>
<p>
Please answer the questions below.
</p>
</html_demo>
</html_demo>
<answer
name=
"goal"
>
<answer
name=
"goal"
question=
"What is your goal?"
/>
<question>
What is your goal?
</question>
</answer>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
question=
"Do you like this MCQ?"
correct_choices=
"yes"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<tip
display
=
"yes"
>
Great!
</tip>
<tip
values
=
"yes"
>
Great!
</tip>
<tip
reject
=
"maybenot"
>
Ah, damn.
</tip>
<tip
values
=
"maybenot"
>
Ah, damn.
</tip>
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html
></tip>
<tip
values=
"understand"
><div
id=
"test-custom-html"
>
Really?
</div
></tip>
</mcq>
</mcq>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
>
<rating
name=
"mcq_1_2"
low=
"Not good at all"
high=
"Extremely good"
question=
"How much do you rate this MCQ?"
correct_choices=
"4,5"
>
<question>
How much do you rate this MCQ?
</question>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<tip
display
=
"4,5"
>
I love good grades.
</tip>
<tip
values
=
"4,5"
>
I love good grades.
</tip>
<tip
reject
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
values
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
reject
=
"notwant"
>
Your loss!
</tip>
<tip
values
=
"notwant"
>
Your loss!
</tip>
</
mcq
>
</
rating
>
<mrq
name=
"mrq_1_1"
type=
"choices"
>
<mrq
name=
"mrq_1_1"
question=
"What do you like in this MRQ?"
required_choices=
"gracefulness,elegance,beauty"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require
=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
values
=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
require
=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values
=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
reject
=
"bugs"
>
Nah, there isn't any!
</tip>
<tip
values
=
"bugs"
>
Nah, there isn't any!
</tip>
</mrq>
</mrq>
</mentoring>
</mentoring>
mentoring/tests/integration/xml/assessment_2.xml
View file @
3736fa36
...
@@ -4,14 +4,13 @@
...
@@ -4,14 +4,13 @@
<p>
Please answer the questions below.
</p>
<p>
Please answer the questions below.
</p>
</html_demo>
</html_demo>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
question=
"Do you like this MCQ?"
correct_choices=
"yes"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<tip
display
=
"yes"
>
Great!
</tip>
<tip
values
=
"yes"
>
Great!
</tip>
<tip
reject
=
"maybenot"
>
Ah, damn.
</tip>
<tip
values
=
"maybenot"
>
Ah, damn.
</tip>
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html
></tip>
<tip
values=
"understand"
><div
id=
"test-custom-html"
>
Really?
</div
></tip>
</mcq>
</mcq>
</mentoring>
</mentoring>
mentoring/tests/integration/xml/mcq_1.xml
View file @
3736fa36
<vertical_demo>
<vertical_demo>
<mentoring
url_name=
"mcq_1"
enforce_dependency=
"false"
>
<mentoring
url_name=
"mcq_1"
enforce_dependency=
"false"
>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
question=
"Do you like this MCQ?"
correct_choices=
"yes"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<choice
value=
"understand"
>
I don't understand
</choice>
<tip
display
=
"yes"
>
Great!
</tip>
<tip
values
=
"yes"
>
Great!
</tip>
<tip
reject
=
"maybenot"
>
Ah, damn.
</tip>
<tip
values
=
"maybenot"
>
Ah, damn.
</tip>
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html
></tip>
<tip
values=
"understand"
><div
id=
"test-custom-html"
>
Really?
</div
></tip>
</mcq>
</mcq>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
>
<rating
name=
"mcq_1_2"
low=
"Not good at all"
high=
"Extremely good"
question=
"How do you rate this MCQ?"
correct_choices=
"4,5"
>
<question>
How much do you rate this MCQ?
</question>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
<tip
display
=
"4,5"
>
I love good grades.
</tip>
<tip
values
=
"4,5"
>
I love good grades.
</tip>
<tip
reject
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
values
=
"1,2,3"
>
Will do better next time...
</tip>
<tip
reject
=
"notwant"
>
Your loss!
</tip>
<tip
values
=
"notwant"
>
Your loss!
</tip>
</
mcq
>
</
rating
>
<message
type=
"completed"
>
<message
type=
"completed"
>
All is good now...
All is good now...
<
html><p>
Congratulations!
</p></html
>
<
p>
Congratulations!
</p
>
</message>
</message>
</mentoring>
</mentoring>
</vertical_demo>
</vertical_demo>
mentoring/tests/integration/xml/mcq_with_comments_1.xml
View file @
3736fa36
<vertical_demo>
<vertical_demo>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MRQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MRQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mrq
name=
"mrq_1_1_7"
type=
"choices"
>
<mrq
name=
"mrq_1_1_7"
question=
"What do you like in this MRQ?"
required_choices=
"elegance,gracefulness,beauty"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require
=
"gracefulness"
width =
"200"
height =
"200"
>
This MRQ is indeed very graceful
</tip>
<tip
values
=
"gracefulness"
width =
"200"
height =
"200"
>
This MRQ is indeed very graceful
</tip>
<tip
require
=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values
=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MRQ
</tip>
<tip
require
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
values
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
reject
=
"bugs"
width =
"100"
height =
"200"
>
Nah, there isn\'t any!
</tip>
<tip
values
=
"bugs"
width =
"100"
height =
"200"
>
Nah, there isn\'t any!
</tip>
<!--<message type="on-submit">Th
ank you for answering!
</message> -->
<!--<message type="on-submit">Th
is is deliberately commented out to test parsing of XML comments
</message> -->
</mrq>
</mrq>
<message
type=
"completed"
>
<message
type=
"completed"
>
<
html><p>
Congratulations!
</p></html
>
<
p>
Congratulations!
</p
>
</message>
</message>
<message
type=
"incomplete"
>
<message
type=
"incomplete"
>
<
html><p>
Still some work to do...
</p></html
>
<
p>
Still some work to do...
</p
>
</message>
</message>
</mentoring>
</mentoring>
</vertical_demo>
</vertical_demo>
mentoring/tests/integration/xml/mcq_with_html_choices.xml
View file @
3736fa36
<vertical_demo>
<vertical_demo>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MCQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MCQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mcq
name=
"mrq_1_1_7"
type=
"choices"
>
<mcq
name=
"mrq_1_1_7"
question=
"What do you like in this MCQ?"
correct_choices=
"gracefulness,elegance,beauty"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
><b>
Its elegance
</b></choice>
<choice
value=
"elegance"
><html><b>
Its elegance
</b></html></choice>
<choice
value=
"beauty"
><i>
Its beauty
</i></choice>
<choice
value=
"beauty"
><html><i>
Its beauty
</i></html></choice>
<choice
value=
"gracefulness"
><strong>
Its gracefulness
</strong></choice>
<choice
value=
"gracefulness"
><html><strong>
Its gracefulness
</strong></html></choice>
<choice
value=
"bugs"
><span
style=
"font-color:red"
>
Its bugs
</span></choice>
<choice
value=
"bugs"
><html><span
style=
"font-color:red"
>
Its bugs
</span></html></choice>
<tip
require=
"gracefulness"
width =
"200"
height =
"200"
>
This MR
Q is indeed very graceful
</tip>
<tip
values=
"gracefulness"
width =
"200"
height =
"200"
>
This MC
Q is indeed very graceful
</tip>
<tip
require=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MR
Q
</tip>
<tip
values=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MC
Q
</tip>
<tip
require
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
values
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
reject
=
"bugs"
width =
"100"
height =
"200"
>
Nah, there isn\'t any!
</tip>
<tip
values
=
"bugs"
width =
"100"
height =
"200"
>
Nah, there isn\'t any!
</tip>
</mcq>
</mcq>
<message
type=
"completed"
>
<message
type=
"completed"
>
<
html><p>
Congratulations!
</p></html
>
<
p>
Congratulations!
</p
>
</message>
</message>
<message
type=
"incomplete"
>
<message
type=
"incomplete"
>
<
html><p>
Still some work to do...
</p></html
>
<
p>
Still some work to do...
</p
>
</message>
</message>
</mentoring>
</mentoring>
</vertical_demo>
</vertical_demo>
mentoring/tests/integration/xml/mrq_with_html_choices.xml
View file @
3736fa36
<vertical_demo>
<vertical_demo>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MRQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mentoring
url_name=
"mcq_with_comments"
display_name=
"MRQ With Resizable popups"
weight=
"1"
enforce_dependency=
"false"
>
<mrq
name=
"mrq_1_1_7"
type=
"choices"
>
<mrq
name=
"mrq_1_1_7"
question=
"What do you like in this MRQ?"
required_choices=
"elegance,beauty,gracefulness"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
><b>
Its elegance
</b></choice>
<choice
value=
"elegance"
><html><b>
Its elegance
</b></html></choice>
<choice
value=
"beauty"
><i>
Its beauty
</i></choice>
<choice
value=
"beauty"
><html><i>
Its beauty
</i></html></choice>
<choice
value=
"gracefulness"
><strong>
Its gracefulness
</strong></choice>
<choice
value=
"gracefulness"
><html><strong>
Its gracefulness
</strong></html></choice>
<choice
value=
"bugs"
><span
style=
"font-color:red"
>
Its bugs
</span></choice>
<choice
value=
"bugs"
><html><span
style=
"font-color:red"
>
Its bugs
</span></html></choice>
<tip
require
=
"gracefulness"
width =
"200"
height =
"200"
>
This MRQ is indeed very graceful
</tip>
<tip
values
=
"gracefulness"
width =
"200"
height =
"200"
>
This MRQ is indeed very graceful
</tip>
<tip
require
=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MRQ
</tip>
<tip
values
=
"elegance"
width =
"600"
height =
"800"
>
This is something everyone has to like about this MRQ
</tip>
<tip
require
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
values
=
"beauty"
width =
"400"
height =
"600"
>
This is something everyone has to like about beauty
</tip>
<tip
reject=
"bugs"
width =
"100"
height =
"200"
>
Nah, there is
n\'t any!
</tip>
<tip
values=
"bugs"
width =
"100"
height =
"200"
>
Nah, there are
n\'t any!
</tip>
</mrq>
</mrq>
<message
type=
"completed"
>
<message
type=
"completed"
>
<
html><p>
Congratulations!
</p></html
>
<
p>
Congratulations!
</p
>
</message>
</message>
<message
type=
"incomplete"
>
<message
type=
"incomplete"
>
<
html><p>
Still some work to do...
</p></html
>
<
p>
Still some work to do...
</p
>
</message>
</message>
</mentoring>
</mentoring>
</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