Commit 3736fa36 by Braden MacDonald

Made Rating a separate block, refactor MCQ/MRQ/Rating and make them editable

parent 47130c91
from .answer import AnswerBlock
from .choice import ChoiceBlock
from .mcq import MCQBlock
from .mcq import MCQBlock, RatingBlock
from .mrq import MRQBlock
from .message import MentoringMessageBlock
from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock
......
......@@ -23,17 +23,73 @@
# Imports ###########################################################
from .common import BlockWithContent
from lxml import etree
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.studio_editable import StudioEditableXBlockMixin
# Classes ###########################################################
class ChoiceBlock(BlockWithContent):
class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
"""
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="")
content = String(help="Human-readable version of the choice value", scope=Scope.content, default="")
return block
# -*- 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
......@@ -25,7 +25,8 @@
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 .questionnaire import QuestionnaireAbstractBlock
......@@ -34,6 +35,7 @@ from .questionnaire import QuestionnaireAbstractBlock
# Globals ###########################################################
log = logging.getLogger(__name__)
loader = ResourceLoader(__name__)
# Classes ###########################################################
......@@ -42,22 +44,34 @@ class MCQBlock(QuestionnaireAbstractBlock):
"""
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)
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):
log.debug(u'Received MCQ submission: "%s"', submission)
correct = True
correct = submission in self.correct_choices
tips_html = []
for tip in self.get_tips():
correct = correct and self.is_tip_correct(tip, submission)
if submission in tip.display_with_defaults:
tips_html.append(tip.get_html())
if submission in tip.values:
tips_html.append(tip.render('mentoring_view').content)
formatted_tips = ResourceLoader(__name__).render_template('templates/html/tip_choice_group.html', {
'self': self,
......@@ -76,11 +90,51 @@ class MCQBlock(QuestionnaireAbstractBlock):
log.debug(u'MCQ submission result: %s', result)
return result
def is_tip_correct(self, tip, submission):
if not submission:
return False
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(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
......@@ -26,6 +26,7 @@
import logging
from xblock.fields import List, Scope, Boolean
from xblock.validation import ValidationMessage
from .questionnaire import QuestionnaireAbstractBlock
from xblockutils.resources import ResourceLoader
......@@ -42,7 +43,35 @@ class MRQBlock(QuestionnaireAbstractBlock):
An XBlock used to ask multiple-response questions
"""
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):
log.debug(u'Received MRQ submissions: "%s"', submissions)
......@@ -54,13 +83,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
choice_completed = True
choice_tips_html = []
choice_selected = choice.value in submissions
for tip in self.get_tips():
if choice.value in tip.display_with_defaults:
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)):
if choice.value in self.required_choices:
if not choice_selected:
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:
score += 1
......@@ -96,3 +126,27 @@ class MRQBlock(QuestionnaireAbstractBlock):
log.debug(u'MRQ submissions result: %s', 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))
/* 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;
}
......@@ -137,6 +137,10 @@ function MCQBlock(runtime, element) {
};
}
function RatingBlock(runtime, element) {
return MCQBlock(runtime, element);
}
function MRQBlock(runtime, element, mentoring) {
return {
mode: null,
......
......@@ -25,18 +25,34 @@
from lxml import etree
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.validation import ValidationMessage
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin
from .choice import ChoiceBlock
from .step import StepMixin
from .tip import TipBlock
# Globals ###########################################################
loader = ResourceLoader(__name__)
# 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
......@@ -44,51 +60,68 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
values entered by the student, and supports multiple types of multiple-choice
set, with preset choices and author-defined values.
"""
type = String(help="Type of questionnaire", scope=Scope.content, default="choices")
question = String(help="Question to ask the student", scope=Scope.content, default="")
message = String(help="General feedback provided when submiting", scope=Scope.content, default="")
weight = Float(help="Defines the maximum total grade of the light child block.",
default=1, scope=Scope.content, enforce_type=True)
valid_types = ('choices')
question = String(
display_name="Question",
help="Question to ask the student",
scope=Scope.content,
default=""
)
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
@classmethod
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)
# Load XBlock properties from the XML attributes:
for name, value in node.items():
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:
if xml_child.tag == 'question':
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:
if xml_child.tag is not etree.Comment:
block.runtime.add_node_as_child(block, xml_child, id_generator)
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):
name = getattr(self, "unmixed_class", self.__class__).__name__
if str(self.type) not in self.valid_types:
raise ValueError(u'Invalid value for {}.type: `{}`'.format(name, self.type))
template_path = 'templates/html/{}.html'.format(name.lower())
template_path = 'templates/html/{}_{}.html'.format(name.lower(), self.type)
loader = ResourceLoader(__name__)
context = context or {}
context['self'] = self
context['custom_choices'] = self.custom_choices
html = loader.render_template(template_path, {
'self': self,
'custom_choices': self.custom_choices
})
fragment = Fragment(html)
fragment.add_css(loader.render_template('public/css/questionnaire.css', {
'self': self
}))
fragment = Fragment(loader.render_template(template_path, context))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/questionnaire.js'))
fragment.initialize_js(name)
return fragment
......@@ -100,20 +133,22 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
def custom_choices(self):
custom_choices = []
for child_id in self.children:
child = self.runtime.get_block(child_id)
if isinstance(child, ChoiceBlock):
custom_choices.append(child)
if child_isinstance(self, child_id, ChoiceBlock):
custom_choices.append(self.runtime.get_block(child_id))
return custom_choices
@property
def all_choice_values(self):
return [c.value for c in self.custom_choices]
def get_tips(self):
"""
Returns the tips contained in this block
"""
tips = []
for child_id in self.children:
child = self.runtime.get_block(child_id)
if isinstance(child, TipBlock):
tips.append(child)
if child_isinstance(self, child_id, TipBlock):
tips.append(self.runtime.get_block(child_id))
return tips
def get_submission_display(self, submission):
......@@ -124,3 +159,35 @@ class QuestionnaireAbstractBlock(XBlock, StepMixin):
if choice.value == submission:
return choice.content
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
<span class="choice-text">
{{ self.content }}
{{ child_content|safe }}
</span>
<fieldset class="choices questionnaire">
<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>
</legend>
<div class="choices-list">
......@@ -9,7 +9,7 @@
<div class="choice-result fa icon-2x"></div>
<label class="choice-label">
<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>
<div class="choice-tips"></div>
</div>
......
<fieldset class="choices questionnaire" data-hide_results="{{self.hide_results}}">
<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>
</legend>
<div class="choices-list">
......@@ -11,7 +11,7 @@
<input class="choice-selector" type="checkbox" name="{{ self.name }}"
value="{{ choice.value }}"
{% if choice.value in self.student_choices %} checked{% endif %} />
{{ choice.get_html|safe }}
<span class="choice-text">{{ choice.content|safe }}</span>
</label>
<div class="choice-tips"></div>
</div>
......
{% 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>
<fieldset class="rating questionnaire">
<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>
</legend>
<div class="choices-list">
......@@ -35,7 +35,7 @@
<div class="choice">
<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 %} />
{{ choice.get_html|safe }}
<span class="choice-text">{{ choice.content|safe }}</span>
</label>
<div class="choice-tips"></div>
</div>
......
<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>
......@@ -3,8 +3,5 @@
{% if width %}data-width="{{width}}"{% endif %}
{% if height %}data-height="{{height}}"{% endif %}
>
{% if self.content %}
<p>{{ self.content }}</p>
{% endif %}
{{ child_content|safe }}
<p>{{ content|safe }}</p>
</div>
......@@ -23,8 +23,14 @@
# Imports ###########################################################
from .common import BlockWithContent
from xblock.fields import Scope, String
from lxml import etree
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 #########################################################
......@@ -41,28 +47,70 @@ def commas_to_set(commas_str):
# Classes ###########################################################
class TipBlock(BlockWithContent):
class TipBlock(StudioEditableXBlockMixin, XBlock):
"""
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="")
display = String(help="List of choices to display the tip for", scope=Scope.content, default=None)
reject = String(help="List of choices to reject", scope=Scope.content, default=None)
require = String(help="List of choices to require", scope=Scope.content, default=None)
values = List(
display_name="For Choices",
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='')
height = String(help="Height of the tip popup", scope=Scope.content, default='')
editable_fields = ('values', 'content', 'width', 'height')
@property
def display_with_defaults(self):
display = commas_to_set(self.display)
return display | self.reject_with_defaults | self.require_with_defaults
def display_name(self):
return u"Tip for {}".format(u", ".join([unicode(v) for v in self.values]))
@property
def reject_with_defaults(self):
return commas_to_set(self.reject)
def fallback_view(self, view_name, context):
html = ResourceLoader(__name__).render_template("templates/html/tip.html", {
'content': self.content,
'width': self.width,
'height': self.height,
})
return Fragment(html)
@property
def require_with_defaults(self):
return commas_to_set(self.require)
def clean_studio_edits(self, data):
"""
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
......@@ -9,35 +9,32 @@
<question>What is your goal?</question>
</answer>
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
<mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes">
<choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice>
<tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
<tip values="yes">Great!</tip>
<tip values="maybenot">Ah, damn.</tip>
<tip values="understand"><div id="test-custom-html">Really?</div></tip>
</mcq>
<mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this MCQ?</question>
<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">
<choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip>
</mcq>
<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>
<mrq name="mrq_1_1" type="choices">
<question>What do you like in this MRQ?</question>
<mrq name="mrq_1_1" question="What do you like in this MRQ?" required_choices="gracefulness,elegance,beauty">
<choice value="elegance">Its elegance</choice>
<choice value="beauty">Its beauty</choice>
<choice value="gracefulness">Its gracefulness</choice>
<choice value="bugs">Its bugs</choice>
<tip require="gracefulness">This MRQ is indeed very graceful</tip>
<tip require="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip reject="bugs">Nah, there isn't any!</tip>
<tip values="gracefulness">This MRQ is indeed very graceful</tip>
<tip values="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip values="bugs">Nah, there isn't any!</tip>
</mrq>
</mentoring>
......@@ -7,38 +7,32 @@
<question>What is your goal?</question>
</answer>
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
<mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes">
<choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice>
<tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
<tip values="yes">Great!</tip>
<tip values="maybenot">Ah, damn.</tip>
<tip values="understand"><div id="test-custom-html">Really?</div></tip>
</mcq>
<mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this MCQ?</question>
<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">
<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>
<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>
<mrq name="mrq_1_1" question="What do you like in this MRQ?" required_choices="gracefulness,elegance,beauty" message="Thank you for answering!">
<choice value="elegance">Its elegance</choice>
<choice value="beauty">Its beauty</choice>
<choice value="gracefulness">Its gracefulness</choice>
<choice value="bugs">Its bugs</choice>
<tip require="gracefulness">This MRQ is indeed very graceful</tip>
<tip require="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip reject="bugs">Nah, there isn't any!</tip>
<message type="on-submit">Thank you for answering!</message>
<tip values="gracefulness">This MRQ is indeed very graceful</tip>
<tip values="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip values="bugs">Nah, there aren't any!</tip>
</mrq>
<message type="completed">
......
......@@ -3,18 +3,15 @@
<p>Please answer the questions below.</p>
</html_demo>
<mrq name="mrq_1_1" type="choices">
<question>What do you like in this MRQ?</question>
<mrq name="mrq_1_1" question="What do you like in this MRQ?" message="Thank you for answering!" required_choices="gracefulness,elegance,beauty">
<choice value="elegance">Its elegance</choice>
<choice value="beauty">Its beauty</choice>
<choice value="gracefulness">Its gracefulness</choice>
<choice value="bugs">Its bugs</choice>
<tip require="gracefulness">This MRQ is indeed very graceful</tip>
<tip require="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip reject="bugs">Nah, there isn't any!</tip>
<message type="on-submit">Thank you for answering!</message>
<tip values="gracefulness">This MRQ is indeed very graceful</tip>
<tip values="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip values="bugs">Nah, there aren't any!</tip>
</mrq>
<message type="completed">
......
......@@ -91,9 +91,9 @@ class MentoringAssessmentTest(MentoringBaseTest):
@staticmethod
def question_text(number):
if number:
return "QUESTION %s" % number
return "Question %s" % number
else:
return "QUESTION"
return "Question"
def freeform_answer(self, number, mentoring, controls, text_input, result, saved_value="", last=False):
question = self.expect_question_visible(number, mentoring)
......
......@@ -68,8 +68,8 @@ class MCQBlockTest(MentoringBaseTest):
mcq1_legend = mcq1.find_element_by_css_selector('legend')
mcq2_legend = mcq2.find_element_by_css_selector('legend')
self.assertEqual(mcq1_legend.text, 'QUESTION 1\nDo you like this MCQ?')
self.assertEqual(mcq2_legend.text, 'QUESTION 2\nHow much do you rate this MCQ?')
self.assertEqual(mcq1_legend.text, 'Question 1\nDo you like this MCQ?')
self.assertEqual(mcq2_legend.text, 'Question 2\nHow do you rate this MCQ?')
mcq1_choices = mcq1.find_elements_by_css_selector('.choices .choice label')
mcq2_choices = mcq2.find_elements_by_css_selector('.rating .choice label')
......@@ -144,7 +144,7 @@ class MCQBlockTest(MentoringBaseTest):
self.assertFalse(submit.is_enabled())
mcq_legend = mcq.find_element_by_css_selector('legend')
self.assertEqual(mcq_legend.text, 'QUESTION\nWhat do you like in this MRQ?')
self.assertEqual(mcq_legend.text, 'Question\nWhat do you like in this MRQ?')
mcq_choices = mcq.find_elements_by_css_selector('.choices .choice label')
......@@ -195,7 +195,7 @@ class MCQBlockTest(MentoringBaseTest):
# 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"):
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
......
......@@ -5,39 +5,34 @@
<p>Please answer the questions below.</p>
</html_demo>
<answer name="goal">
<question>What is your goal?</question>
</answer>
<answer name="goal" question="What is your goal?" />
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
<mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes">
<choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice>
<tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
<tip values="yes">Great!</tip>
<tip values="maybenot">Ah, damn.</tip>
<tip values="understand"><div id="test-custom-html">Really?</div></tip>
</mcq>
<mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this MCQ?</question>
<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">
<choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip>
</mcq>
<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>
<mrq name="mrq_1_1" type="choices">
<question>What do you like in this MRQ?</question>
<mrq name="mrq_1_1" question="What do you like in this MRQ?" required_choices="gracefulness,elegance,beauty">
<choice value="elegance">Its elegance</choice>
<choice value="beauty">Its beauty</choice>
<choice value="gracefulness">Its gracefulness</choice>
<choice value="bugs">Its bugs</choice>
<tip require="gracefulness">This MRQ is indeed very graceful</tip>
<tip require="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip reject="bugs">Nah, there isn't any!</tip>
<tip values="gracefulness">This MRQ is indeed very graceful</tip>
<tip values="elegance,beauty">This is something everyone has to like about this MRQ</tip>
<tip values="bugs">Nah, there isn't any!</tip>
</mrq>
</mentoring>
......@@ -4,14 +4,13 @@
<p>Please answer the questions below.</p>
</html_demo>
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
<mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes">
<choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice>
<tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
<tip values="yes">Great!</tip>
<tip values="maybenot">Ah, damn.</tip>
<tip values="understand"><div id="test-custom-html">Really?</div></tip>
</mcq>
</mentoring>
<vertical_demo>
<mentoring url_name="mcq_1" enforce_dependency="false">
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
<mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes">
<choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice>
<tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
<tip values="yes">Great!</tip>
<tip values="maybenot">Ah, damn.</tip>
<tip values="understand"><div id="test-custom-html">Really?</div></tip>
</mcq>
<mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this MCQ?</question>
<rating name="mcq_1_2" low="Not good at all" high="Extremely good" question="How do you rate this MCQ?" correct_choices="4,5">
<choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip>
</mcq>
<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>
<message type="completed">
All is good now...
<html><p>Congratulations!</p></html>
<p>Congratulations!</p>
</message>
</mentoring>
</vertical_demo>
<vertical_demo>
<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">
<question>What do you like in this MRQ?</question>
<mrq name="mrq_1_1_7" question="What do you like in this MRQ?" required_choices="elegance,gracefulness,beauty">
<choice value="elegance">Its elegance</choice>
<choice value="beauty">Its beauty</choice>
<choice value="gracefulness">Its gracefulness</choice>
<choice value="bugs">Its bugs</choice>
<tip require="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 require="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="gracefulness" width ="200" height = "200">This MRQ is indeed very graceful</tip>
<tip values="elegance" width ="600" height = "800">This is something everyone has to like about this MRQ</tip>
<tip values="beauty" width ="400" height = "600">This is something everyone has to like about beauty</tip>
<tip values="bugs" width = "100" height = "200">Nah, there isn\'t any!</tip>
<!--<message type="on-submit">Thank you for answering!</message> -->
<!--<message type="on-submit">This is deliberately commented out to test parsing of XML comments</message> -->
</mrq>
<message type="completed">
<html><p>Congratulations!</p></html>
<p>Congratulations!</p>
</message>
<message type="incomplete">
<html><p>Still some work to do...</p></html>
<p>Still some work to do...</p>
</message>
</mentoring>
</vertical_demo>
<vertical_demo>
<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">
<question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice>
<choice value="beauty"><html><i>Its beauty</i></html></choice>
<choice value="gracefulness"><html><strong>Its gracefulness</strong></html></choice>
<choice value="bugs"><html><span style="font-color:red">Its bugs</span></html></choice>
<mcq name="mrq_1_1_7" question="What do you like in this MCQ?" correct_choices="gracefulness,elegance,beauty">
<choice value="elegance"><b>Its elegance</b></choice>
<choice value="beauty"><i>Its beauty</i></choice>
<choice value="gracefulness"><strong>Its gracefulness</strong></choice>
<choice value="bugs"><span style="font-color:red">Its bugs</span></choice>
<tip require="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 require="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="gracefulness" width ="200" height = "200">This MCQ is indeed very graceful</tip>
<tip values="elegance" width ="600" height = "800">This is something everyone has to like about this MCQ</tip>
<tip values="beauty" width ="400" height = "600">This is something everyone has to like about beauty</tip>
<tip values="bugs" width = "100" height = "200">Nah, there isn\'t any!</tip>
</mcq>
<message type="completed">
<html><p>Congratulations!</p></html>
<p>Congratulations!</p>
</message>
<message type="incomplete">
<html><p>Still some work to do...</p></html>
<p>Still some work to do...</p>
</message>
</mentoring>
</vertical_demo>
<vertical_demo>
<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">
<question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice>
<choice value="beauty"><html><i>Its beauty</i></html></choice>
<choice value="gracefulness"><html><strong>Its gracefulness</strong></html></choice>
<choice value="bugs"><html><span style="font-color:red">Its bugs</span></html></choice>
<mrq name="mrq_1_1_7" question="What do you like in this MRQ?" required_choices="elegance,beauty,gracefulness">
<choice value="elegance"><b>Its elegance</b></choice>
<choice value="beauty"><i>Its beauty</i></choice>
<choice value="gracefulness"><strong>Its gracefulness</strong></choice>
<choice value="bugs"><span style="font-color:red">Its bugs</span></choice>
<tip require="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 require="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="gracefulness" width ="200" height = "200">This MRQ is indeed very graceful</tip>
<tip values="elegance" width ="600" height = "800">This is something everyone has to like about this MRQ</tip>
<tip values="beauty" width ="400" height = "600">This is something everyone has to like about beauty</tip>
<tip values="bugs" width = "100" height = "200">Nah, there aren\'t any!</tip>
</mrq>
<message type="completed">
<html><p>Congratulations!</p></html>
<p>Congratulations!</p>
</message>
<message type="incomplete">
<html><p>Still some work to do...</p></html>
<p>Still some work to do...</p>
</message>
</mentoring>
</vertical_demo>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment