Commit d9c256cd by Braden MacDonald

Support alternating HTML instructions in assessments

parent cb4538c9
......@@ -131,7 +131,8 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
display_name=_("Question"),
help=_("Question to ask the student"),
scope=Scope.content,
default=""
default="",
multiline_editor=True,
)
weight = Float(
display_name=_("Weight"),
......
......@@ -68,7 +68,8 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
display_name=_("Question"),
help=_("Question to ask the student"),
scope=Scope.content,
default=""
default="",
multiline_editor=True,
)
message = String(
display_name=_("Message"),
......
<div class="xblock-answer" data-completed="{{ self.completed }}">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %}
<p>{{ self.question }}</p>
<p>{{ self.question|safe }}</p>
<textarea
class="answer editable" cols="50" rows="10" name="input"
data-min_characters="{{ self.min_characters }}"
......
<fieldset class="choices questionnaire">
<legend class="question">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %}
<p>{{ self.question }}</p>
<p>{{ self.question|safe }}</p>
</legend>
<div class="choices-list">
{% for choice in custom_choices %}
......
<fieldset class="choices questionnaire" data-hide_results="{{self.hide_results}}">
<legend class="question">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %}
<p>{{ self.question }}</p>
<p>{{ self.question|safe }}</p>
</legend>
<div class="choices-list">
{% for choice in custom_choices %}
......
<fieldset class="rating questionnaire">
<legend class="question">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %}
<p>{{ self.question }}</p>
<p>{{ self.question|safe }}</p>
</legend>
<div class="choices-list">
{% for i in '12345' %}
......
......@@ -212,6 +212,13 @@ class QuestionnaireBlockTest(MentoringBaseTest):
)
def test_questionnaire_html_choices(self, page):
mentoring = self.go_to_page(page)
question = mentoring.find_element_by_css_selector('legend p')
self.assertIn(
'What do <strong>you</strong> like in this ',
question.get_attribute('innerHTML').strip()
)
choices_list = mentoring.find_element_by_css_selector(".choices-list")
messages = mentoring.find_element_by_css_selector('.messages')
......
<vertical_demo>
<problem-builder url_name="mcq_with_comments" display_name="MCQ With Resizable popups" weight="1" enforce_dependency="false">
<pb-mcq name="mrq_1_1_7" question="What do you like in this MCQ?" correct_choices='["gracefulness","elegance","beauty"]'>
<pb-mcq name="mrq_1_1_7" question="What do &lt;strong&gt;you&lt;/strong&gt; like in this MCQ?" correct_choices='["gracefulness","elegance","beauty"]'>
<pb-choice value="elegance"><strong>Its elegance</strong></pb-choice>
<pb-choice value="beauty"><em>Its beauty</em></pb-choice>
<pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice>
......
<vertical_demo>
<problem-builder url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false">
<pb-mrq name="mrq_1_1_7" question="What do you like in this MRQ?" required_choices='["elegance","beauty","gracefulness"]'>
<pb-mrq name="mrq_1_1_7" question="What do &lt;strong&gt;you&lt;/strong&gt; like in this MRQ?" required_choices='["elegance","beauty","gracefulness"]'>
<pb-choice value="elegance"><strong>Its elegance</strong></pb-choice>
<pb-choice value="beauty"><em>Its beauty</em></pb-choice>
<pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice>
......
......@@ -52,6 +52,7 @@ class TestUpgrade(unittest.TestCase):
"v1_upgrade_a",
"v1_upgrade_b",
"v1_upgrade_c",
"v1_upgrade_d",
)
@XBlock.register_temp_plugin(HtmlBlock, "html")
@XBlock.register_temp_plugin(MentoringBlock, "mentoring")
......
<problem-builder url_name="some_url_name" weight="1" mode="assessment" max_attempts="2">
<pb-mcq name="M1" question="&lt;span&gt;&lt;i&gt;Review the following information. Then select the best answer and click &lt;b&gt;Submit.&lt;/b&gt; Click &lt;strong&gt;Next Question&lt;/strong&gt; to proceed.&lt;/i&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;If all elephants eat porridge, and some porridge is blue, do all elephants eat blue porridge?">
<pb-choice value="y">Yes</pb-choice>
<pb-choice value="n">No</pb-choice>
</pb-mcq>
<pb-mcq name="M1" question="&lt;span&gt;Great! Now, for the following question, you'll need to remember that north is the opposite of south.&lt;/span&gt;&lt;br&gt;&lt;br&gt;If the elephant heads south for 200m, which direction is he heading?">
<pb-choice value="s">Toward Antarctica</pb-choice>
<pb-choice value="n">North</pb-choice>
</pb-mcq>
</problem-builder>
<?xml version='1.0' encoding='utf-8'?>
<!--
Mentoring v1 had a strange feature where alternating HTML and MCQ/MRQ elements
would be grouped together in an assessment:
-->
<mentoring xmlns:option="http://code.edx.org/xblock/option">
<option:xml_content>
<![CDATA[
<mentoring url_name="some_url_name" weight="1" mode="assessment" max_attempts="2">
<html>
<p><i>Review the following information. Then select the best answer and click <b>Submit.</b> Click <strong>Next Question</strong> to proceed.</i></p>
</html>
<mcq name="M1" type="choices">
<question>If all elephants eat porridge, and some porridge is blue, do all elephants eat blue porridge?</question>
<choice value="y">Yes</choice>
<choice value="n">No</choice>
</mcq>
<html>
<p>Great! Now, for the following question, you'll need to remember that north is the opposite of south.</p>
</html>
<mcq name="M1" type="choices">
<question>If the elephant heads south for 200m, which direction is he heading?</question>
<choice value="s">Toward Antarctica</choice>
<choice value="n">North</choice>
</mcq>
</mentoring>
]]>
</option:xml_content>
</mentoring>
......@@ -285,6 +285,56 @@ class TipChanges(Change):
self.node.attrib["values"] = value
class AlternatingHTMLToQuestions(Change):
"""
In mentoring v1, an assessment could have XML like this:
<mentoring mode="assessment"...>
<html>Question 1 introduction text</html>
<mcq name="Q1" type="choices"><question>Question 1</question>...</mcq>
<html>Question 2 introduction text</html>
<mcq name="Q2" type="choices"><question>Question 2</question>...</mcq>
...
</mentoring>
Notice that nearest-sibling HTML and MCQ tags are meant to be displayed together.
This migration changes that to:
<mentoring mode="assessment"...>
<mcq name="Q1" question="Question 1 introduction text. Question 1">...</mcq>
<mcq name="Q2" question="Question 2 introduction text. Question 2">...</mcq>
...
</mentoring>
QuestionToField (<question> tag converted to attribute) must already be applied, and
SharedHeaderToHTML must not yet be applied.
"""
@staticmethod
def applies_to(node):
return (
node.tag == "html" and
node.getparent().attrib.get("mode") == "assessment" and
node.getnext() is not None and
node.getnext().tag in ("pb-answer", "pb-mcq", "pb-mrq", "pb-rating")
)
def apply(self):
html_content = u""
for child in list(self.node):
if child.tag == "p":
# This HTML will ultimately be rendered inside a <p> so it can't be a <p> as well:
child.tag = "span"
tail = child.tail if child.tail else u""
child.tail = u""
html_content += etree.tostring(child) + u"<br><br>" + tail
else:
html_content += etree.tostring(child)
q = self.node.getnext()
existing_question = q.attrib.get('question', '')
q.attrib["question"] = u"{}{}".format(html_content, existing_question)
p = self.node.getparent()
p.remove(self.node)
class SharedHeaderToHTML(Change):
""" <shared-header> element no longer exists. Just use <html> """
@staticmethod
......@@ -325,6 +375,7 @@ xml_changes = (
QuestionToField,
QuestionSubmitMessageToField,
TipChanges,
AlternatingHTMLToQuestions,
SharedHeaderToHTML,
CommaSeparatedListToJson,
)
......
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