Commit c3a2af39 by Braden MacDonald

Merge pull request #15 from open-craft/fix-upgrade-bug

Fix upgrade bugs
parents a26d9d75 b448953c
...@@ -131,7 +131,8 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -131,7 +131,8 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
display_name=_("Question"), display_name=_("Question"),
help=_("Question to ask the student"), help=_("Question to ask the student"),
scope=Scope.content, scope=Scope.content,
default="" default="",
multiline_editor=True,
) )
weight = Float( weight = Float(
display_name=_("Weight"), display_name=_("Weight"),
...@@ -164,7 +165,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -164,7 +165,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
def mentoring_view(self, context=None): def mentoring_view(self, context=None):
""" Render this XBlock within a mentoring block. """ """ Render this XBlock within a mentoring block. """
context = context or {} context = context.copy() if context else {}
context['self'] = self context['self'] = self
context['hide_header'] = context.get('hide_header', False) or not self.show_title context['hide_header'] = context.get('hide_header', False) or not self.show_title
html = loader.render_template('templates/html/answer_editable.html', context) html = loader.render_template('templates/html/answer_editable.html', context)
...@@ -270,7 +271,7 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock): ...@@ -270,7 +271,7 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock):
def mentoring_view(self, context=None): def mentoring_view(self, context=None):
""" Render this XBlock within a mentoring block. """ """ Render this XBlock within a mentoring block. """
context = context or {} context = context.copy() if context else {}
context['title'] = self.display_name context['title'] = self.display_name
context['description'] = self.description context['description'] = self.description
context['student_input'] = self.student_input context['student_input'] = self.student_input
......
...@@ -121,6 +121,12 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -121,6 +121,12 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
scope=Scope.content, scope=Scope.content,
multiline_editor=True multiline_editor=True
) )
show_title = Boolean(
display_name=_("Show title"),
help=_("Display the title?"),
default=True,
scope=Scope.content
)
# Settings # Settings
weight = Float( weight = Float(
...@@ -281,6 +287,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -281,6 +287,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
fragment.add_content(loader.render_template('templates/html/mentoring.html', { fragment.add_content(loader.render_template('templates/html/mentoring.html', {
'self': self, 'self': self,
'title': self.display_name, 'title': self.display_name,
'show_title': self.show_title,
'child_content': child_content, 'child_content': child_content,
'missing_dependency_url': self.has_missing_dependency and self.next_step_url, 'missing_dependency_url': self.has_missing_dependency and self.next_step_url,
})) }))
......
...@@ -83,11 +83,13 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -83,11 +83,13 @@ function MentoringStandardView(runtime, element, mentoring) {
}; };
mentoring.initChildren(options); mentoring.initChildren(options);
mentoring.renderAttempts();
mentoring.renderDependency(); mentoring.renderDependency();
validateXBlock(); var submitPossible = submitDOM.length > 0;
if (submitPossible) {
mentoring.renderAttempts();
validateXBlock();
} // else display_submit is false and this is read-only
} }
// validate all children // validate all children
......
...@@ -68,7 +68,8 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -68,7 +68,8 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
display_name=_("Question"), display_name=_("Question"),
help=_("Question to ask the student"), help=_("Question to ask the student"),
scope=Scope.content, scope=Scope.content,
default="" default="",
multiline_editor=True,
) )
message = String( message = String(
display_name=_("Message"), display_name=_("Message"),
......
...@@ -95,12 +95,12 @@ class StepMixin(object): ...@@ -95,12 +95,12 @@ class StepMixin(object):
return self._(u"Question") return self._(u"Question")
def author_view(self, context): def author_view(self, context):
context = context or {} context = context.copy() if context else {}
context['hide_header'] = True context['hide_header'] = True
return self.mentoring_view(context) return self.mentoring_view(context)
def author_preview_view(self, context): def author_preview_view(self, context):
context = context or {} context = context.copy() if context else {}
context['hide_header'] = True context['hide_header'] = True
return self.student_view(context) return self.student_view(context)
......
...@@ -70,7 +70,7 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, ...@@ -70,7 +70,7 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
has_children = True has_children = True
def student_view(self, context): def student_view(self, context):
context = context or {} context = context.copy() if context else {}
fragment = Fragment() fragment = Fragment()
header_values = [] header_values = []
content_values = [] content_values = []
...@@ -136,7 +136,7 @@ class MentoringTableColumn(StudioEditableXBlockMixin, StudioContainerXBlockMixin ...@@ -136,7 +136,7 @@ class MentoringTableColumn(StudioEditableXBlockMixin, StudioContainerXBlockMixin
def mentoring_view(self, context=None): def mentoring_view(self, context=None):
""" Render this XBlock within a mentoring block. """ """ Render this XBlock within a mentoring block. """
context = context or {} context = context.copy() if context else {}
fragment = Fragment() fragment = Fragment()
for child_id in self.children: for child_id in self.children:
child = self.runtime.get_block(child_id) child = self.runtime.get_block(child_id)
......
<div class="xblock-answer" data-completed="{{ self.completed }}"> <div class="xblock-answer" data-completed="{{ self.completed }}">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} {% 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 <textarea
class="answer editable" cols="50" rows="10" name="input" class="answer editable" cols="50" rows="10" name="input"
data-min_characters="{{ self.min_characters }}" data-min_characters="{{ self.min_characters }}"
......
<fieldset class="choices questionnaire"> <fieldset class="choices questionnaire">
<legend class="question"> <legend class="question">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} {% 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> </legend>
<div class="choices-list"> <div class="choices-list">
{% for choice in custom_choices %} {% for choice in custom_choices %}
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
{% endwith %} {% endwith %}
</div> </div>
{% if title %} {% if show_title and title %}
<div class="title"> <div class="title">
{% if title %} <h2>{{ title }}</h2> {% endif %} <h2>{{ title }}</h2>
</div> </div>
{% endif %} {% endif %}
......
<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">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} {% 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> </legend>
<div class="choices-list"> <div class="choices-list">
{% for choice in custom_choices %} {% for choice in custom_choices %}
......
<fieldset class="rating questionnaire"> <fieldset class="rating questionnaire">
<legend class="question"> <legend class="question">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} {% 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> </legend>
<div class="choices-list"> <div class="choices-list">
{% for i in '12345' %} {% for i in '12345' %}
......
# -*- 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/>.
#
from .base_test import MentoringBaseTest
class MultipleBlockTest(MentoringBaseTest):
"""
Test that multiple Problem Builder blocks can happily co-exist on a page.
"""
default_css_selector = 'div.vertical'
def test_answer_blocks(self, expect_answer=False):
"""
Make sure that the JavaScript is working fine even though there are many blocks on the
page. In this test we check the answer blocks.
"""
vertical = self.go_to_page("Multiple Problem Builders")
blocks = vertical.find_elements_by_css_selector('.mentoring')
block_a = blocks[0]
self.assertIn("First", block_a.text)
block_b = blocks[1]
self.assertIn("Recap", block_b.text)
answer_input = block_a.find_element_by_css_selector("textarea")
answer_output = block_b.find_element_by_css_selector("blockquote")
if not expect_answer:
self.assertEqual(answer_input.text, "")
self.assertEqual(answer_output.text, "No answer yet.")
answer_input.send_keys('Hello there')
submit = block_a.find_element_by_css_selector('.submit input.input-main')
self.assertTrue(submit.is_enabled())
submit.click()
self.wait_until_disabled(submit)
self.test_answer_blocks(expect_answer=True)
else:
self.assertEqual(answer_input.text, 'Hello there')
self.assertEqual(answer_output.text, 'Hello there')
def test_mcq_mrq(self):
"""
Make sure that the JavaScript is working fine even though there are many blocks on the
page. In this test we check the MCQ and MRQ.
"""
vertical = self.go_to_page("Multiple Problem Builders")
blocks = vertical.find_elements_by_css_selector('.mentoring')
block_c = blocks[2]
self.assertIn("Third", block_c.text)
block_d = blocks[3]
self.assertIn("Fourth", block_d.text)
# Ensure that both of the blocks C and D are both working simultaneously:
for block in (block_c, block_d):
submit = block.find_element_by_css_selector('.submit input.input-main')
self.assertFalse(submit.is_enabled())
block.find_elements_by_css_selector('.choices .choice label')[0].click()
self.assertTrue(submit.is_enabled())
submit.click()
self.wait_until_disabled(submit)
...@@ -212,6 +212,13 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -212,6 +212,13 @@ class QuestionnaireBlockTest(MentoringBaseTest):
) )
def test_questionnaire_html_choices(self, page): def test_questionnaire_html_choices(self, page):
mentoring = self.go_to_page(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") choices_list = mentoring.find_element_by_css_selector(".choices-list")
messages = mentoring.find_element_by_css_selector('.messages') messages = mentoring.find_element_by_css_selector('.messages')
......
...@@ -22,6 +22,7 @@ Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings ...@@ -22,6 +22,7 @@ Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings
""" """
# Imports ########################################################### # Imports ###########################################################
import ddt
from mock import patch from mock import patch
from xblockutils.base_test import SeleniumXBlockTest from xblockutils.base_test import SeleniumXBlockTest
...@@ -29,6 +30,32 @@ from xblockutils.base_test import SeleniumXBlockTest ...@@ -29,6 +30,32 @@ from xblockutils.base_test import SeleniumXBlockTest
# Classes ########################################################### # Classes ###########################################################
@ddt.ddt
class TitleTest(SeleniumXBlockTest):
"""
Test the various display_name/show_title options for Problem Builder
"""
@ddt.data(
('<problem-builder show_title="false"><pb-answer name="a"/></problem-builder>', None),
('<problem-builder><pb-answer name="a"/></problem-builder>', "Mentoring Questions"),
('<problem-builder mode="assessment"><pb-answer name="a"/></problem-builder>', "Mentoring Questions"),
('<problem-builder display_name="A Question"><pb-answer name="a"/></problem-builder>', "A Question"),
('<problem-builder display_name="A Question" show_title="false"><pb-answer name="a"/></problem-builder>', None),
)
@ddt.unpack
def test_title(self, xml, expected_title):
self.set_scenario_xml(xml)
pb_element = self.go_to_view()
if expected_title is not None:
h2 = pb_element.find_element_by_css_selector('h2')
self.assertEqual(h2.text, expected_title)
else:
# No <h2> element should be present:
all_h2s = pb_element.find_elements_by_css_selector('h2')
self.assertEqual(len(all_h2s), 0)
class StepTitlesTest(SeleniumXBlockTest): class StepTitlesTest(SeleniumXBlockTest):
""" """
Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings work. Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings work.
......
<vertical_demo> <vertical_demo>
<problem-builder url_name="mcq_with_comments" display_name="MCQ With Resizable popups" weight="1" enforce_dependency="false"> <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="elegance"><strong>Its elegance</strong></pb-choice>
<pb-choice value="beauty"><em>Its beauty</em></pb-choice> <pb-choice value="beauty"><em>Its beauty</em></pb-choice>
<pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice> <pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice>
......
<vertical_demo> <vertical_demo>
<problem-builder url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false"> <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="elegance"><strong>Its elegance</strong></pb-choice>
<pb-choice value="beauty"><em>Its beauty</em></pb-choice> <pb-choice value="beauty"><em>Its beauty</em></pb-choice>
<pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice> <pb-choice value="gracefulness"><strong>Its gracefulness</strong></pb-choice>
......
<vertical_demo>
<html_demo>
<p>This tests that multiple Problem Builder blocks can happily co-exist on a page.</p>
</html_demo>
<problem-builder url_name="a" display_name="First: An Answer Block">
<pb-answer name="q-a" question="What is your goal?" />
</problem-builder>
<problem-builder url_name="b" display_name="Recap" display_submit="false">
<html_demo>
<p>Your answer should now appear here:</p>
</html_demo>
<pb-answer-recap name="q-a"/>
<html_demo>There should be no submit button here.</html_demo>
</problem-builder>
<problem-builder url_name="c" display_name="Third: An MCQ">
<pb-mcq name="mcq_3_1" question="Do you like this MCQ?" correct_choices='["yes"]'>
<pb-choice value="yes">Yes</pb-choice>
<pb-choice value="maybenot">Maybe not</pb-choice>
<pb-choice value="understand">I don't understand</pb-choice>
</pb-mcq>
</problem-builder>
<problem-builder url_name="d" display_name="Fourth: An MRQ Assessment" mode="assessment">
<pb-mrq name="mrq_1_1" question="What do you like in this MRQ?"
required_choices='["gracefulness","elegance","beauty"]'
>
<pb-choice value="elegance">Its elegance</pb-choice>
<pb-choice value="beauty">Its beauty</pb-choice>
<pb-choice value="gracefulness">Its gracefulness</pb-choice>
<pb-choice value="bugs">Its bugs</pb-choice>
</pb-mrq>
</problem-builder>
</vertical_demo>
...@@ -52,6 +52,7 @@ class TestUpgrade(unittest.TestCase): ...@@ -52,6 +52,7 @@ class TestUpgrade(unittest.TestCase):
"v1_upgrade_a", "v1_upgrade_a",
"v1_upgrade_b", "v1_upgrade_b",
"v1_upgrade_c", "v1_upgrade_c",
"v1_upgrade_d",
) )
@XBlock.register_temp_plugin(HtmlBlock, "html") @XBlock.register_temp_plugin(HtmlBlock, "html")
@XBlock.register_temp_plugin(MentoringBlock, "mentoring") @XBlock.register_temp_plugin(MentoringBlock, "mentoring")
......
<problem-builder enforce_dependency="false" followed_by="past_attempts"> <problem-builder enforce_dependency="false" followed_by="past_attempts" show_title="false">
<html> <html>
<h3>Checking your improvement frog</h3> <h3>Checking your improvement frog</h3>
<p>Now, let's make sure your frog meets the criteria for a strong column 1. Here is your frog:</p> <p>Now, let's make sure your frog meets the criteria for a strong column 1. Here is your frog:</p>
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
</pb-mcq> </pb-mcq>
<pb-rating name="frog-important" low="Not at all important to me" high="Very important to me" question="How important is it to you?" correct_choices='["4","5","1","2","3","understand"]'> <pb-rating name="frog-important" low="Not at all important to me" high="Very important to me" question="How important is it to you?" correct_choices='["4","5","1","2","3","understand"]'>
<pb-choice value="understand">I don't understand</pb-choice> <pb-choice value="understand">I don't understand</pb-choice>
<pb-tip values='["4","5"]'>Great!</pb-tip>
<pb-tip values='["1","2","3"]'>The Trial of Uruk-Shan helps you uncover some of the core beliefs and assumptions you have held that are preventing you from making change.</pb-tip> <pb-tip values='["1","2","3"]'>The Trial of Uruk-Shan helps you uncover some of the core beliefs and assumptions you have held that are preventing you from making change.</pb-tip>
<pb-tip values='["understand"]'>A frog is important if it is one that could make a big difference in helping you reach your frogs in your work life or your personal life (or both).</pb-tip> <pb-tip values='["understand"]'>A frog is important if it is one that could make a big difference in helping you reach your frogs in your work life or your personal life (or both).</pb-tip>
</pb-rating> </pb-rating>
......
...@@ -40,7 +40,7 @@ This contains a typical problem taken from a live course (content changed) ...@@ -40,7 +40,7 @@ This contains a typical problem taken from a live course (content changed)
<quizz name="frog-important" type="rating" low="Not at all important to me" high="Very important to me"> <quizz name="frog-important" type="rating" low="Not at all important to me" high="Very important to me">
<question>How important is it to you?</question> <question>How important is it to you?</question>
<choice value="understand">I don't understand</choice> <choice value="understand">I don't understand</choice>
<tip display="4,5">Great!</tip> <tip display="4,5"> </tip>
<tip display="1,2,3">The Trial of Uruk-Shan helps you uncover some of the core beliefs and assumptions you have held that are preventing you from making change.</tip> <tip display="1,2,3">The Trial of Uruk-Shan helps you uncover some of the core beliefs and assumptions you have held that are preventing you from making change.</tip>
<tip display="understand">A frog is important if it is one that could make a big difference in helping you reach your frogs in your work life or your personal life (or both).</tip> <tip display="understand">A frog is important if it is one that could make a big difference in helping you reach your frogs in your work life or your personal life (or both).</tip>
</quizz> </quizz>
......
<problem-builder display_submit="false" enforce_dependency="false"> <problem-builder display_submit="false" enforce_dependency="false" display_name="Table">
<pb-table type="table_test" url_name="table_2"> <pb-table type="table_test" url_name="table_2">
<pb-column header="Header Test 1"> <pb-column header="Header Test 1">
<pb-answer-recap name="table_1_answer_1" /> <pb-answer-recap name="table_1_answer_1" />
......
...@@ -6,6 +6,7 @@ This contains a table to test migration of tables from v1 schema to v2. ...@@ -6,6 +6,7 @@ This contains a table to test migration of tables from v1 schema to v2.
<option:xml_content> <option:xml_content>
<![CDATA[ <![CDATA[
<mentoring display_submit="false" enforce_dependency="false"> <mentoring display_submit="false" enforce_dependency="false">
<title>Table</title>
<mentoring-table type="table_test" url_name="table_2"> <mentoring-table type="table_test" url_name="table_2">
<column> <column>
<header>Header Test 1</header> <header>Header Test 1</header>
......
<problem-builder url_name="some_url_name" weight="1" mode="assessment" max_attempts="2" show_title="false">
<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>
...@@ -54,7 +54,7 @@ def upgrade_block(block): ...@@ -54,7 +54,7 @@ def upgrade_block(block):
warnings.simplefilter("always") warnings.simplefilter("always")
convert_xml_v1_to_v2(root) convert_xml_v1_to_v2(root)
for warning in warnings_caught: for warning in warnings_caught:
print(u" ➔ {}".format(str(warning.message))) print(u" ➔ {}".format(unicode(warning.message)))
# We need some special-case handling to deal with HTML being an XModule and not a pure XBlock: # We need some special-case handling to deal with HTML being an XModule and not a pure XBlock:
try: try:
...@@ -76,7 +76,7 @@ def upgrade_block(block): ...@@ -76,7 +76,7 @@ def upgrade_block(block):
root.attrib["xml_content"] = xml_content_str root.attrib["xml_content"] = xml_content_str
# Was block already published? # Was block already published?
parent = block.runtime.get_block(block.parent) # Don't use get_parent() as it may be an outdated cached version parent = store.get_item(block.parent) # Don't use get_parent()/get_block() as it may be an outdated cached version
parent_was_published = not store.has_changes(parent) parent_was_published = not store.has_changes(parent)
old_usage_id = block.location old_usage_id = block.location
......
...@@ -66,18 +66,32 @@ class PrefixTags(Change): ...@@ -66,18 +66,32 @@ class PrefixTags(Change):
self.node.tag = "pb-" + self.node.tag self.node.tag = "pb-" + self.node.tag
class HideTitle(Change):
"""
If no <title> element is present, set hide_title="true"
"""
@staticmethod
def applies_to(node):
return node.tag == "problem-builder" and node.find("title") is None
def apply(self):
self.node.attrib["show_title"] = "false"
class RemoveTitle(Change): class RemoveTitle(Change):
""" The old <title> element is now an attribute of <mentoring> """ """ The old <title> element is now an attribute of <problem-builder> """
@staticmethod @staticmethod
def applies_to(node): def applies_to(node):
return node.tag == "title" and node.getparent().tag == "problem-builder" return node.tag == "title" and node.getparent().tag == "problem-builder"
def apply(self): def apply(self):
title = self.node.text.strip() title = self.node.text.strip() if self.node.text else u''
p = self.node.getparent() p = self.node.getparent()
old_display_name = p.get("display_name") old_display_name = p.get("display_name")
if old_display_name and old_display_name != title: if old_display_name and old_display_name != title:
warnings.warn('Replacing display_name="{}" with <title> value "{}"'.format(p.attrib["display_name"], title)) warnings.warn(
u'Replacing display_name="{}" with <title> value "{}"'.format(p.attrib["display_name"], title)
)
p.attrib["display_name"] = title p.attrib["display_name"] = title
p.remove(self.node) p.remove(self.node)
...@@ -268,7 +282,7 @@ class TipChanges(Change): ...@@ -268,7 +282,7 @@ class TipChanges(Change):
elif self.node.attrib.get("reject"): elif self.node.attrib.get("reject"):
value = self.node.attrib.pop("reject") value = self.node.attrib.pop("reject")
else: else:
warnings.warn("Invalid <tip> element found.") warnings.warn(u"Invalid <tip> element found: {}".format(etree.tostring(self.node)))
return return
else: else:
# This is an MCQ or Rating question: # This is an MCQ or Rating question:
...@@ -277,10 +291,67 @@ class TipChanges(Change): ...@@ -277,10 +291,67 @@ class TipChanges(Change):
add_to_list("correct_choices", value) add_to_list("correct_choices", value)
elif self.node.attrib.get("reject"): elif self.node.attrib.get("reject"):
value = self.node.attrib.pop("reject") value = self.node.attrib.pop("reject")
elif self.node.attrib.get("require"):
value = self.node.attrib.pop("require")
add_to_list("correct_choices", value)
warnings.warn(u"<tip> element in an MCQ/Rating used 'require' rather than 'display'")
else: else:
warnings.warn("Invalid <tip> element found.") warnings.warn(u"Invalid <tip> element found: {}".format(etree.tostring(self.node)))
return return
self.node.attrib["values"] = value self.node.attrib["values"] = value
if (self.node.text is None or self.node.text.strip() == "") and not list(self.node):
# This tip is blank.
p.remove(self.node)
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): class SharedHeaderToHTML(Change):
...@@ -313,6 +384,7 @@ class CommaSeparatedListToJson(Change): ...@@ -313,6 +384,7 @@ class CommaSeparatedListToJson(Change):
xml_changes = ( xml_changes = (
RenameMentoringTag, RenameMentoringTag,
PrefixTags, PrefixTags,
HideTitle,
RemoveTitle, RemoveTitle,
UnwrapHTML, UnwrapHTML,
RenameTableTag, RenameTableTag,
...@@ -323,6 +395,7 @@ xml_changes = ( ...@@ -323,6 +395,7 @@ xml_changes = (
QuestionToField, QuestionToField,
QuestionSubmitMessageToField, QuestionSubmitMessageToField,
TipChanges, TipChanges,
AlternatingHTMLToQuestions,
SharedHeaderToHTML, SharedHeaderToHTML,
CommaSeparatedListToJson, 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